[
  {
    "path": ".dockerignore",
    "content": "rocksdb.db\n.git\n.env\n*.jpg\n*.png\n*.md\n**/target"
  },
  {
    "path": ".gitignore",
    "content": "storage/\nrun/\ntest_out/\ntmp/\ndoc/\n.directory\n.loadpath\n.metadata\n*.pydevproject\n*.bak\n*.swp\n*.tmp\n*~.nib\nlocal.properties\n*/dependency-reduced-pom.xml\n\n\n# Maven #\n#########\ntarget/\n\n# Eclipse #\n###########\n.project\n.classpath\n.settings/\n.externalToolBuilders/\n*.launch\n.recommenders/\n\n# Intellij #\n############\n.idea/\n*.iml\n*.ipr\n*.iws\n\n# Compiled source #\n###################\n*.class\n*.com\n*.dll\n*.exe\n*.o\n*.so\n\n\n=======\n# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.jar\n*.war\n*.nar\n*.ear\n*.zip\n*.tar.gz\n*.rar\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\nrocksdb.db/\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Builder\nFROM maven:3-jdk-8-slim AS builder\n\nRUN mkdir /opt/jmqtt\nWORKDIR /opt/jmqtt\n\nADD . ./\n\nRUN mvn -Ppackage-all -DskipTests clean install -U\n\n# App\nFROM openjdk:8-jre-slim\n\nRUN mkdir /opt/jmqtt\nVOLUME /var/jmqtt-data\nWORKDIR /opt/jmqtt\n\nCOPY --from=builder /opt/jmqtt ./\nCOPY --from=builder /opt/jmqtt/jmqtt-distribution/conf /var/jmqtt-data/conf/\n\n\nEXPOSE 1883\nEXPOSE 1884\n\nCMD [\"./jmqtt-distribution/target/jmqtt/bin/jmqttstart\", \"-h\", \"/var/jmqtt-data\", \"-c\", \"/var/jmqtt-data/conf/jmqtt.properties\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "**English** | [中文](README_CN.md)\n#  Jmqtt\n\n![Jmqtt logo](jmqtt.png)\n\n## Features\n* Full support of mqtt3.1.1 protocol\n* Support data persistence and clustering based on MySQL\n* Support friendly secondary development, plug-in development: cluster / storage / device connection, publish subscribe authentication\n* Support tcp, websocket, SSL, WSS\n\n## Official documents\n[Official documents](https://arrogant95.github.io/jmqtt-docs/)\n\n## Quick start\n1. Download [release](https://github.com/Cicizz/jmqtt/releases) (Version above 3. X) Or `clone` this project\n2. Execute in the broker directory:`mvn -Ppackage-all -DskipTests clean install -U`\n3. Configuration file for configuration response:`/jmqtt-broker/resources/conf`\n4. Execute the start command:`java -jar jmqtt-broker-3.0.0.jar -h ${conf文件目录}` -H is followed by the configuration file directory, which needs to contain jmqtt.properties And log4j2. XML\n\n## Online trial\nServer address: 81.69.46.38\nTCP port: 1883\nWebsocket port: 8883\nSSL port: 1884\nWSS port: 8884\n\n## QQ technology exchange group\n![jmqtt技术交流群](jmqtt_qq2.png)\n\n## test report\njmqtt-doc/docs/TEST_REPORT.md\n\n\n\n"
  },
  {
    "path": "README_CN.md",
    "content": "[English](README.md) | **中文**\n##  Jmqtt\n\n![Jmqtt logo](jmqtt.png)\n\n## 功能特性\n* 完整支持mqtt3.1.1协议\n* 支持基于mysql的数据持久化和集群\n* 支持友好的二次开发，插件化开发：集群/存储/设备连接，发布订阅认证\n* 支持tcp, websocket,ssl,wss\n\n## 官方文档\n[官方文档](https://arrogant95.github.io/jmqtt-docs/)\n\n## 快速开始\n1. 下载 [release](https://github.com/Cicizz/jmqtt/releases)(3.x以上版本) 或`clone`本项目\n2. 在broker模块下执行：`mvn -Ppackage-all -DskipTests clean install -U`\n3. 配置配置文件并初始化db的sql:`/jmqtt-broker/resources/conf`目录下\n4. 执行启动命令：`java -jar jmqtt-broker-3.0.0.jar -h ${conf文件目录}` -h后是配置文件目录，里面需要包含jmqtt.properties和log4j2.xml等配置文件\n\n## 技术交流群\n欢迎小伙伴们给个star并使用。\n\n![jmqtt技术交流群](jmqtt_qq2.png)\n\n## 测试报告\nhttps://www.yuque.com/tristan-ku8np/zze/xghq80\n\n\n"
  },
  {
    "path": "jmqtt-acceptance/README.md",
    "content": "这里是存放iot mq所有的功能测试，性能测试脚本及测试结果\n## 功能测试计划(基于集群)\n### 1.连接功能项\n1. 连接\n2. 连接 with cleasession = false（mqtt）\n3. 连接 with will 消息（mqtt）\n4. 使用ws连接\n5. 连接使用ssl\n6. 重新发起连接\n### 2.发布消息\n1. 发布qos0消息（mqtt）\n2. 发布qos1消息（mqtt）\n3. 发布qos2消息（mqtt）\n4. 发布retain消息（mqtt）\n### 3. 订阅\n1. 订阅普通topic\n2. 订阅多级topic\n3. 订阅收到retain消息（mqtt）\n4. 发布订阅拉通测试发送消息\n### 4. 关闭连接\n\n\n## 性能测试计划\n## 1. 同时在线连接数压测\n1. 连接数分级：100连接数/单机，500连接数/单机，1000连接数/单机，5000连接数/单机，1W连接数/单机\n2. 同时请求连接：10/s，50/s，100/s，200/s，500/s  -- 单机\n3. 集群测试（2+服务器）：1W连接数/集群；5W连接数/集群；10W连接数/集群\n4. 集群测试（2+服务器）：1000/s，2000/s\n5. 集群测试（2+服务器）：同时在线10W设备，1000设备发送消息\n## 2.发送消息TPS\n消息字节大小：100byte[]\n1. 单机：500/s，1000/s，5000/s，10000/s\n2. 集群(2+台服务器)：10000/s，15000/s，20000/s\n## 3.订阅\n1. 单设备订阅topic：10topic/单设备，50topic/单设备，100topic/单设备\n2. 单机订阅树：10topic/单设备，50topic/单设备，100topic/单设备：分别连接1000设备，5000设备，10000设备\n## 4.消费消息\n1. 集群测试：同时在线10W设备，1000设备消费消息\n\n\n\n\n"
  },
  {
    "path": "jmqtt-acceptance/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    <parent>\n        <artifactId>jmqtt</artifactId>\n        <groupId>org.jmqtt</groupId>\n        <version>3.0.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <maven.compiler.source>1.8</maven.compiler.source>\n        <maven.compiler.target>1.8</maven.compiler.target>\n    </properties>\n\n    <artifactId>jmqtt-acceptance</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.eclipse.paho</groupId>\n            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>\n            <version>1.2.5</version>\n        </dependency>\n\n        <dependency>\n            <groupId>commons-cli</groupId>\n            <artifactId>commons-cli</artifactId>\n            <version>${commons-cli.version}</version>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-assembly-plugin</artifactId>\n                <configuration>\n                    <descriptorRefs>\n                        <descriptorRef>jar-with-dependencies</descriptorRef>\n                    </descriptorRefs>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>make-assembly</id>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>single</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "jmqtt-admin/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    <parent>\n        <artifactId>jmqtt</artifactId>\n        <groupId>org.jmqtt</groupId>\n        <version>3.0.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <version>3.0.0</version>\n    <artifactId>jmqtt-admin</artifactId>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.jmqtt</groupId>\n            <artifactId>jmqtt-broker</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-all</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "jmqtt-broker/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" 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    <parent>\n        <artifactId>jmqtt</artifactId>\n        <groupId>org.jmqtt</groupId>\n        <version>3.0.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>jmqtt-broker</artifactId>\n    <version>3.0.0</version>\n\n    <name>jmqtt-broker</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <maven.compiler.source>1.8</maven.compiler.source>\n        <maven.compiler.target>1.8</maven.compiler.target>\n        <akka.version>2.6.13</akka.version>\n        <scala.binary.version>2.13</scala.binary.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.jmqtt</groupId>\n            <artifactId>jmqtt-mqtt</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.assertj</groupId>\n            <artifactId>assertj-core</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter-api</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n    </dependencies>\n\n    <!--打包插件 -->\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <fork>true</fork>\n                    <!-- 如果没有该配置，devtools不会生效 -->\n                    <!-- 指定该Main Class为全局的唯一入口 -->\n                    <mainClass>org.jmqtt.broker.BrokerStartup</mainClass>\n                    <layout>ZIP</layout>\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal><!--可以把依赖的包都打包到生成的Jar包中-->\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerController.java",
    "content": "package org.jmqtt.broker;\n\nimport org.jmqtt.bus.BusController;\nimport org.jmqtt.mqtt.MQTTServer;\nimport org.jmqtt.support.config.BrokerConfig;\nimport org.jmqtt.support.config.NettyConfig;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\n/**\n * 具体控制类：负责加载配置文件，初始化环境，启动服务等\n */\npublic class BrokerController {\n\n    private static final Logger log = JmqttLogger.brokerLog;\n\n    private BrokerConfig brokerConfig;\n    private NettyConfig nettyConfig;\n\n    /** bus */\n    private BusController busController;\n\n    /** mqtt gateway */\n    private MQTTServer mqttServer;\n\n    /** coap gateway todo */\n\n    public BrokerController(BrokerConfig brokerConfig, NettyConfig nettyConfig){\n        this.brokerConfig = brokerConfig;\n        this.nettyConfig = nettyConfig;\n        this.busController = new BusController(brokerConfig);\n        this.mqttServer = new MQTTServer(busController,brokerConfig,nettyConfig);\n    }\n\n    public void start(){\n        this.busController.start();\n        this.mqttServer.start();\n        LogUtil.info(log,\"Jmqtt start. version:\" + brokerConfig.getVersion());\n    }\n\n\n    public void shutdown(){\n        this.mqttServer.shutdown();\n        this.busController.shutdown();\n    }\n}\n"
  },
  {
    "path": "jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerStartup.java",
    "content": "package org.jmqtt.broker;\n\nimport org.apache.commons.cli.*;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.logging.log4j.Level;\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.core.LoggerContext;\nimport org.apache.logging.log4j.core.config.Configuration;\nimport org.apache.logging.log4j.core.config.LoggerConfig;\nimport org.jmqtt.support.config.BrokerConfig;\nimport org.jmqtt.support.config.NettyConfig;\nimport org.jmqtt.support.helper.MixAll;\n\nimport java.io.*;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Properties;\n\n/**\n * 技术问题，二次开发问题：请加 qq群：578185385\n */\npublic class BrokerStartup {\n\n    public static void main(String[] args) {\n        try {\n            start(args);\n        } catch (Exception e) {\n            System.out.println(\"Jmqtt start failure,cause = \" + e);\n            e.printStackTrace();\n            System.exit(-1);\n        }\n    }\n\n    public static BrokerController start(String[] args) throws Exception {\n        Options options = buildOptions();\n        CommandLineParser parser = new DefaultParser();\n        CommandLine commandLine = parser.parse(options,args);\n        String jmqttHome = null;\n        String logLevel = null;\n        BrokerConfig brokerConfig = new BrokerConfig();\n        NettyConfig nettyConfig = new NettyConfig();\n        if(commandLine != null){\n            jmqttHome = commandLine.getOptionValue(\"h\");\n            logLevel = commandLine.getOptionValue(\"l\");\n        }\n        if(StringUtils.isEmpty(jmqttHome)){\n            jmqttHome = brokerConfig.getJmqttHome();\n        }\n        if(StringUtils.isEmpty(jmqttHome)){\n            throw new Exception(\"please set JMQTT_HOME.\");\n        }\n        String jmqttConfigPath = jmqttHome + File.separator + \"jmqtt.properties\";\n        initConfig(jmqttConfigPath,brokerConfig,nettyConfig);\n\n        // 日志配置加载\n        try {\n            LoggerContext context = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false);\n            File file = new File(jmqttHome + File.separator + \"log4j2.xml\");\n            context.setConfigLocation(file.toURI());\n            Configuration configuration = context.getConfiguration();\n            Map<String, LoggerConfig> loggerConfigMap = configuration.getLoggers();\n            Level newLevel = logLevel == null? null : Level.getLevel(logLevel);\n            if (newLevel == null) {\n                newLevel = Level.INFO;\n            }\n            for (LoggerConfig value : loggerConfigMap.values()) {\n                value.setLevel(newLevel);\n            }\n            context.updateLoggers(configuration);\n        } catch (Exception ex) {\n            System.err.print(\"Log4j2 load error,ex:\" + ex);\n        }\n\n        // 启动服务，线程等\n        BrokerController brokerController = new BrokerController(brokerConfig,nettyConfig);\n        brokerController.start();\n\n        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {\n            @Override\n            public void run() {\n                brokerController.shutdown();\n            }\n        }));\n\n        return brokerController;\n    }\n\n    private static Options buildOptions(){\n        Options options = new Options();\n        Option opt = new Option(\"h\",true,\"jmqttHome,eg: /wls/xxx\");\n        opt.setRequired(false);\n        options.addOption(opt);\n\n        opt = new Option(\"c\",true,\"jmqtt.properties path,eg: /wls/xxx/xxx.properties\");\n        opt.setRequired(false);\n        options.addOption(opt);\n\n        opt = new Option(\"l\",true,\"DEBUG\");\n        opt.setRequired(false);\n        options.addOption(opt);\n\n        return options;\n    }\n\n    /**\n     * convert properties to java config class\n     * @param jmqttConfigPath\n     * @param brokerConfig\n     * @param nettyConfig\n     */\n    private static void initConfig(String jmqttConfigPath, BrokerConfig brokerConfig, NettyConfig nettyConfig){\n        Properties properties = new Properties();\n        BufferedReader  bufferedReader = null;\n        try {\n            bufferedReader = new BufferedReader(new FileReader(jmqttConfigPath));\n            properties.load(bufferedReader);\n            MixAll.properties2POJO(properties,brokerConfig);\n            MixAll.properties2POJO(properties,nettyConfig);\n        } catch (FileNotFoundException e) {\n            System.out.println(\"jmqtt.properties cannot find,cause + \" + e + \",path:\"+jmqttConfigPath);\n        } catch (IOException e) {\n            System.out.println(\"Handle jmqttConfig IO exception,cause = \" + e);\n        } finally {\n            try {\n                if(Objects.nonNull(bufferedReader)){\n                    bufferedReader.close();\n                }\n            } catch (IOException e) {\n                System.out.println(\"Handle jmqttConfig IO exception,cause = \" + e);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-broker/src/main/resources/bin/jmqttshutdown",
    "content": "#!/bin/sh\n\npid=`ps ax | grep -i 'org.jmqtt.broker.BrokerStartup' |grep java | grep -v grep | awk '{print $1}'`\nif [ -z \"$pid\" ] ; then\n        echo \"No jmqttBroker running.\"\n        exit -1;\nfi\n\necho \"The jmqttBroker(${pid}) is running...\"\n\nkill ${pid}\n\necho \"Send shutdown request to jmqttBroker(${pid}) OK\"\n\n"
  },
  {
    "path": "jmqtt-broker/src/main/resources/bin/runbroker.sh",
    "content": "#!/bin/sh\n\n\nJAVA_OPT=\"${JAVA_OPT} -server -Xms1280m -Xmx1280m -Xmn640m\"\nJAVA_OPT=\"${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8\"\nJAVA_OPT=\"${JAVA_OPT} -verbose:gc -Xloggc:/dev/shm/mq_gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy\"\nJAVA_OPT=\"${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m\"\nJAVA_OPT=\"${JAVA_OPT} -XX:-OmitStackTraceInFastThrow\"\nJAVA_OPT=\"${JAVA_OPT} -XX:+AlwaysPreTouch\"\nJAVA_OPT=\"${JAVA_OPT} -XX:MaxDirectMemorySize=15g\"\nJAVA_OPT=\"${JAVA_OPT} -XX:-UseLargePages -XX:-UseBiasedLocking\"\n\nJAVA_OPT=\"${JAVA_OPT} -Dcom.sun.management.jmxremote.ssl=false\"\nJAVA_OPT=\"${JAVA_OPT} -Dcom.sun.management.jmxremote.authenticate=false\"\nJAVA_OPT=\"${JAVA_OPT} -Dcom.sun.management.jmxremote.port=1099\"\n\n#JAVA_OPT=\"${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n\"\nnohup java ${JAVA_OPT} -jar $@ >/dev/null 2>&1 &\necho 'jmqtt start.'\n"
  },
  {
    "path": "jmqtt-broker/src/main/resources/conf/client.cer",
    "content": "-----BEGIN CERTIFICATE-----\nMIICWDCCAcGgAwIBAgIJANmxJ6Gxp3rbMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\nBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\naWRnaXRzIFB0eSBMdGQwHhcNMTkwNjMwMDk1ODE4WhcNMjkwNjI3MDk1ODE4WjBF\nMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\nZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB\ngQCgz0+Bb1uY3TZhYx/bdBegTqLLsgmLlwHqdUuWbPEg9UdhLWWAeXpjXr+w1AjH\nwQ0aQtDPc6bK6Bu8H11fw4RfrhEAx7TVnka/88gEUx3qivFtIt5fBDKN5dEE1PfM\nkXqQV2szrJNRWsQbE8rbT26hNzc16Sdj0InLlJ9Y2pa0VQIDAQABo1AwTjAdBgNV\nHQ4EFgQUD4R4hfIOYFFyR3gIi7PGOag7tcwwHwYDVR0jBBgwFoAUD4R4hfIOYFFy\nR3gIi7PGOag7tcwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBHdaLA\nRMg67YI0rEFc24MciGE+xBMSQEGegjIfo/p5frmU0kPBrdcv3XndblMPWDoGXnsE\nj1KZHu5Mw6dg8kid4ytvqTgvVjcjwNZ8f6Uj2mhxPJa15K5FF0D3xgCes+WiA+wF\nVDmoGEemCmGTIOjN4Nwb+V4/KKhOkoWhQBSLsg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "jmqtt-broker/src/main/resources/conf/jmqtt.properties",
    "content": "# 是否开启匿名访问\nanonymousEnable=true\n\n# max mqtt message size\nmaxMsgSize=524288\n\n\n# 是否开启tcp服务\nstartTcp=true\ntcpPort=1883\n# 是否开启tcp ssl 服务\nstartSslTcp=true\nsslTcpPort=1884\n\n#集群模式：1.基于发布订阅，集群主动push消息给Jmqtt; 2.基于poll，jmqtt主动从集群拉消息\nclusterMode = 2\n# 采用2.poll方式时，一次从集群中最多拉的消息数目和间隔拉取的时间（ms）\nmaxPollEventNum = 10\npollWaitInterval = 10\n\n# 是否开启websocket服务,ws协议\nstartWebsocket=true\nwebsocketPort=8883\n\n# 是否开启websocket服务,wss协议\nstartSslWebsocket=true\nsslWebsocketPort=8884\n\n\n# db config if jmqtt run with db\ndriver=com.mysql.cj.jdbc.Driver\nurl=jdbc:mysql://127.0.0.0:3306/jmqtt?characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&useSSL=false\nusername=root\npassword=CallmeZ2013\n"
  },
  {
    "path": "jmqtt-broker/src/main/resources/conf/jmqtt.sql",
    "content": "DROP INDEX `uk_client_id` ON `jmqtt_session`;\nDROP INDEX `uk_client_id_topic` ON `jmqtt_subscription`;\nDROP INDEX `idx_client_id` ON `jmqtt_message`;\nDROP INDEX `idx_client_id_ack` ON `jmqtt_client_inbox`;\nDROP INDEX `uk_topic` ON `jmqtt_retain_message`;\n\n\nDROP TABLE `jmqtt_session`;\nDROP TABLE `jmqtt_subscription`;\nDROP TABLE `jmqtt_message`;\nDROP TABLE `jmqtt_client_inbox`;\nDROP TABLE `jmqtt_retain_message`;\nDROP TABLE `jmqtt_cluster_event`;\n\nCREATE TABLE `jmqtt_session` (\n`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',\n`client_id` varchar(64) NOT NULL COMMENT '客户端id',\n`online` varchar(12) NOT NULL COMMENT '状态：ONLINE,OFFLINE两种',\n`transport_protocol` varchar(20) NOT NULL COMMENT '传输协议：MQTT,TCP,COAP等',\n`client_ip` varchar(32) NOT NULL COMMENT '客户端ip',\n`server_ip` varchar(32) NOT NULL COMMENT '连接的服务端ip',\n`last_offline_time` timestamp NULL COMMENT '上一次离线时间',\n`online_time` timestamp NOT NULL COMMENT '最近连接在线时间',\n`properties` text NULL COMMENT '扩展信息',\nPRIMARY KEY (`id`) ,\nUNIQUE INDEX `uk_client_id` (`client_id` ASC)\n)\nCOMMENT = '客户端会话状态';\nCREATE TABLE `jmqtt_subscription` (\n`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',\n`client_id` varchar(64) NOT NULL COMMENT '客户端id',\n`topic` varchar(64) NOT NULL COMMENT '订阅的topic',\n`subscribe_time` timestamp NOT NULL COMMENT '订阅时间',\n`properties` text NULL COMMENT '订阅的额外属性',\nPRIMARY KEY (`id`) ,\nUNIQUE INDEX `uk_client_id_topic` (`client_id` ASC, `topic` ASC)\n)\nCOMMENT = '客户端订阅关系';\nCREATE TABLE `jmqtt_message` (\n`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',\n`source` varchar(32) NOT NULL COMMENT '消息来源：DEVICE/PLATFORM',\n`content` mediumblob NOT NULL COMMENT '消息体内容：字节',\n`topic` varchar(64) NULL COMMENT '发送的目标topic',\n`from_client_id` varchar(64) NULL COMMENT '消息来源若是设备，从属设备id',\n`stored_time` timestamp NOT NULL COMMENT '消息落库时间',\n`properties` text NULL COMMENT '消息额外属性',\nPRIMARY KEY (`id`) ,\nINDEX `idx_client_id` (`source` ASC)\n)\nCOMMENT = '入栈消息表';\nCREATE TABLE `jmqtt_client_inbox` (\n`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',\n`client_id` varchar(64) NOT NULL COMMENT '客户端id',\n`message_id` bigint(20) NOT NULL COMMENT '消息id',\n`ack` tinyint(2) NOT NULL COMMENT '客户端是否收到消息：0未收到，1到达',\n`stored_time` timestamp NOT NULL COMMENT '收件箱时间',\n`ack_time` timestamp NULL,\nPRIMARY KEY (`id`) ,\nINDEX `idx_client_id_ack` (`client_id` ASC, `ack` ASC)\n)\nCOMMENT = '设备消息收件箱表';\nCREATE TABLE `jmqtt_retain_message` (\n`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',\n`topic` varchar(64) NOT NULL COMMENT '所属topic',\n`content` mediumblob NOT NULL COMMENT '消息体内容',\n`from_client_id` varchar(64) NOT NULL COMMENT 'retain消息来源设备id',\n`stored_time` timestamp NOT NULL COMMENT '存储事件',\n`properties` text NOT NULL COMMENT '额外属性',\nPRIMARY KEY (`id`) ,\nUNIQUE INDEX `uk_topic` (`topic` ASC)\n)\nCOMMENT = 'mqtt协议的retain 消息表';\nCREATE TABLE `jmqtt_cluster_event` (\n`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键：也是集群节点批量拉消息的offset',\n`content` longtext NOT NULL COMMENT '消息体',\n`gmt_create` timestamp(6) NOT NULL COMMENT '创建时间',\n`node_ip` varchar(32) NOT NULL COMMENT 'jmqtt集群节点ip',\n`event_code` varchar(64) NOT NULL COMMENT '事件码：参考代码',\nPRIMARY KEY (`id`)\n)\nCOMMENT = 'jmqtt 集群事件转发表：由发送端将消息发送到该表中，其他节点批量拉取该表中的事件进行处理';\n"
  },
  {
    "path": "jmqtt-broker/src/main/resources/conf/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<configuration>\n\n    <properties>\n        <!-- log file path -->\n        <property name=\"user.dir\" value=\"./\" />\n        <property name=\"level\" value=\"INFO\"/>\n        <property name=\"pattern\" value=\"%d [%X{traceId}/%X{loginUserID}/%X{remoteAddr}/%X{clientId} - %X{requestURIWithQueryString}] %-5p %c{2} - %m%n\"/>\n    </properties>\n\n    <Appenders>\n        <!-- console -->\n        <Console name=\"console\" target=\"SYSTEM_OUT\" follow=\"true\">\n            <PatternLayout>\n                <Pattern>${pattern}</Pattern>\n            </PatternLayout>\n        </Console>\n\n        <!-- error -->\n        <RollingFile name=\"error-appender\" fileName=\"${user.dir}/jmqttlogs/error.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/error.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <ThresholdFilter level=\"ERROR\" onMatch=\"ACCEPT\" onMismatch=\"DENY\"/>\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/common-error.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n\n        <!-- brokerLog -->\n        <RollingFile name=\"brokerLog-appender\" fileName=\"${user.dir}/jmqttlogs/brokerLog.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/brokerLog.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/brokerLog.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n\n        <!-- mqttLog -->\n        <RollingFile name=\"mqttLog-appender\" fileName=\"${user.dir}/jmqttlogs/mqttLog.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/mqttLog.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/mqttLog.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n\n        <!-- busLog -->\n        <RollingFile name=\"busLog-appender\" fileName=\"${user.dir}/jmqttlogs/busLog.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/mqttLog.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/busLog.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n\n        <!-- clientTraceLog -->\n        <RollingFile name=\"clientTraceLog-appender\" fileName=\"${user.dir}/jmqttlogs/clientTraceLog.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/clientTraceLog.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/clientTraceLog.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n\n        <!-- clientTraceLog -->\n        <RollingFile name=\"messageTraceLog-appender\" fileName=\"${user.dir}/jmqttlogs/messageTraceLog.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/messageTraceLog.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/messageTraceLog.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n\n        <!-- eventLog -->\n        <RollingFile name=\"eventLog-appender\" fileName=\"${user.dir}/jmqttlogs/eventLog.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/eventLog.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/eventLog.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n\n        <!-- remotingLog -->\n        <RollingFile name=\"remotingLog-appender\" fileName=\"${user.dir}/jmqttlogs/remotingLog.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/remotingLog.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/remotingLog.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n\n        <!-- storeLog -->\n        <RollingFile name=\"storeLog-appender\" fileName=\"${user.dir}/jmqttlogs/storeLog.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/storeLog.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/storeLog.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n\n        <!-- otherLog -->\n        <RollingFile name=\"otherLog-appender\" fileName=\"${user.dir}/jmqttlogs/otherLog.log\"\n                     filePattern=\"${user.dir}/jmqttlogs/otherLog.log.%d{yyyy-MM-dd}\"\n                     append=\"true\">\n            <PatternLayout\n                    pattern=\"${pattern}\"\n                    charset=\"UTF-8\"/>\n            <TimeBasedTriggeringPolicy/>\n            <DefaultRolloverStrategy>\n                <Delete basePath=\"${user.dir}\" maxDepth=\"1\">\n                    <IfFileName glob=\"*/otherLog.log.*\"/>\n                    <IfLastModified age=\"10d\"/>\n                </Delete>\n            </DefaultRolloverStrategy>\n        </RollingFile>\n    </Appenders>\n\n    <Loggers>\n        <logger name=\"brokerLog\" level=\"${level}\" additivity=\"false\">\n            <appender-ref ref=\"brokerLog-appender\"/>\n            <appender-ref ref=\"error-appender\"/>\n            <appender-ref ref=\"console\"/>\n        </logger>\n        <logger name=\"mqttLog\" level=\"${level}\" additivity=\"false\">\n            <appender-ref ref=\"mqttLog-appender\"/>\n            <appender-ref ref=\"error-appender\"/>\n            <appender-ref ref=\"console\"/>\n        </logger>\n        <logger name=\"busLog\" level=\"${level}\" additivity=\"false\">\n            <appender-ref ref=\"busLog-appender\"/>\n            <appender-ref ref=\"error-appender\"/>\n            <appender-ref ref=\"console\"/>\n        </logger>\n        <logger name=\"clientTraceLog\" level=\"${level}\" additivity=\"false\">\n            <appender-ref ref=\"clientTraceLog-appender\"/>\n            <appender-ref ref=\"error-appender\"/>\n            <appender-ref ref=\"console\"/>\n        </logger>\n        <logger name=\"messageTraceLog\" level=\"${level}\" additivity=\"false\">\n            <appender-ref ref=\"messageTraceLog-appender\"/>\n            <appender-ref ref=\"error-appender\"/>\n            <appender-ref ref=\"console\"/>\n        </logger>\n        <logger name=\"eventLog\" level=\"${level}\" additivity=\"false\">\n            <appender-ref ref=\"eventLog-appender\"/>\n            <appender-ref ref=\"error-appender\"/>\n            <appender-ref ref=\"console\"/>\n        </logger>\n        <logger name=\"remotingLog\" level=\"${level}\" additivity=\"false\">\n            <appender-ref ref=\"remotingLog-appender\"/>\n            <appender-ref ref=\"error-appender\"/>\n            <appender-ref ref=\"console\"/>\n        </logger>\n        <logger name=\"storeLog\" level=\"${level}\" additivity=\"false\">\n            <appender-ref ref=\"storeLog-appender\"/>\n            <appender-ref ref=\"error-appender\"/>\n            <appender-ref ref=\"console\"/>\n        </logger>\n        <logger name=\"otherLog\" level=\"${level}\" additivity=\"false\">\n            <appender-ref ref=\"otherLog-appender\"/>\n            <appender-ref ref=\"error-appender\"/>\n            <appender-ref ref=\"console\"/>\n        </logger>\n\n        <root>\n            <level value=\"${level}\"/>\n            <appender-ref ref=\"error-appender\"/>\n        </root>\n    </Loggers>\n</configuration>\n"
  },
  {
    "path": "jmqtt-broker/src/test/java/org/jmqtt/broker/test/Runtime.java",
    "content": "package org.jmqtt.broker.test;\n\nimport com.sun.management.OperatingSystemMXBean;\nimport org.junit.jupiter.api.Test;\n\nimport java.lang.management.ManagementFactory;\n\npublic class Runtime {\n\n    @Test\n    public void subscribe() {\n        // 虚拟机级内存情况查询\n        long vmFree = 0;\n        long vmUse = 0;\n        long vmTotal = 0;\n        long vmMax = 0;\n        int byteToMb = 1024 * 1024;\n        java.lang.Runtime rt = java.lang.Runtime.getRuntime();\n        vmTotal = rt.totalMemory() / byteToMb;\n        vmFree = rt.freeMemory() / byteToMb;\n        vmMax = rt.maxMemory() / byteToMb;\n        vmUse = vmTotal - vmFree;\n        System.out.println(\"JVM内存已用的空间为：\" + vmUse + \" MB\");\n        System.out.println(\"JVM内存的空闲空间为：\" + vmFree + \" MB\");\n        System.out.println(\"JVM总内存空间为：\" + vmTotal + \" MB\");\n        System.out.println(\"JVM总内存空间为：\" + vmMax + \" MB\");\n\n        System.out.println(\"======================================\");\n        // 操作系统级内存情况查询\n        OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();\n        String os = System.getProperty(\"os.name\");\n        long physicalFree = osmxb.getFreePhysicalMemorySize() / byteToMb;\n        long physicalTotal = osmxb.getTotalPhysicalMemorySize() / byteToMb;\n        long physicalUse = physicalTotal - physicalFree;\n        System.out.println(\"操作系统的版本：\" + os);\n        System.out.println(\"操作系统物理内存已用的空间为：\" + physicalFree + \" MB\");\n        System.out.println(\"操作系统物理内存的空闲空间为：\" + physicalUse + \" MB\");\n        System.out.println(\"操作系统总物理内存：\" + physicalTotal + \" MB\");\n        // 获得线程总数\n        ThreadGroup parentThread;\n        int totalThread = 0;\n        for (parentThread = Thread.currentThread().getThreadGroup(); parentThread\n                .getParent() != null; parentThread = parentThread.getParent()) {\n            totalThread = parentThread.activeCount();\n        }\n        System.out.println(\"获得线程总数:\" + totalThread);\n\n        ThreadGroup currentGroup =\n                Thread.currentThread().getThreadGroup();\n        int noThreads = currentGroup.activeCount();\n        Thread[] lstThreads = new Thread[noThreads];\n        currentGroup.enumerate(lstThreads);\n        for (int i = 0; i < noThreads; i++)\n            System.out.println(\"线程号：\" + i + \" = \" + lstThreads[i].getName());\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/README.md",
    "content": "iot 消息bus\n"
  },
  {
    "path": "jmqtt-bus/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" 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    <parent>\n        <artifactId>jmqtt</artifactId>\n        <groupId>org.jmqtt</groupId>\n        <version>3.0.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>jmqtt-bus</artifactId>\n\n    <name>jmqtt-bus</name>\n\n    <dependencies>\n\n        <dependency>\n            <groupId>org.assertj</groupId>\n            <artifactId>assertj-core</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.mybatis</groupId>\n            <artifactId>mybatis</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>druid</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.jmqtt</groupId>\n            <artifactId>jmqtt-support</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter-api</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/Authenticator.java",
    "content": "package org.jmqtt.bus;\n\n\npublic interface Authenticator {\n\n\n    boolean login(String clientId,String userName,byte[] password);\n\n    boolean onBlackList(String clientId,String remoteIpAddress);\n\n    boolean clientIdVerify(String clientId);\n\n    boolean subscribeVerify(String clientId,String topic);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/BusController.java",
    "content": "package org.jmqtt.bus;\n\nimport org.jmqtt.bus.impl.*;\nimport org.jmqtt.bus.store.DBUtils;\nimport org.jmqtt.bus.subscription.CTrieSubscriptionMatcher;\nimport org.jmqtt.bus.subscription.SubscriptionMatcher;\nimport org.jmqtt.support.config.BrokerConfig;\n\npublic class BusController {\n\n    private BrokerConfig brokerConfig;\n    private Authenticator authenticator;\n    private DeviceSessionManager deviceSessionManager;\n    private DeviceMessageManager deviceMessageManager;\n    private DeviceSubscriptionManager deviceSubscriptionManager;\n    private SubscriptionMatcher subscriptionMatcher;\n    private ClusterEventManager clusterEventManager;\n\n    public BusController(BrokerConfig brokerConfig){\n        this.brokerConfig = brokerConfig;\n        this.authenticator = new DefaultAuthenticator();\n        this.deviceMessageManager = new DeviceMessageManagerImpl();\n        this.deviceSessionManager = new DeviceSessionManagerImpl();\n        //this.subscriptionMatcher = new DefaultSubscriptionTreeMatcher();\n        this.subscriptionMatcher = new CTrieSubscriptionMatcher();\n        this.deviceSubscriptionManager = new DeviceSubscriptionManagerImpl(subscriptionMatcher);\n        this.clusterEventManager = new ClusterEventManagerImpl(subscriptionMatcher);\n    }\n\n\n    public void start(){\n        DBUtils.getInstance().start(brokerConfig);\n        this.clusterEventManager.start();\n    }\n\n    public void shutdown(){\n        this.clusterEventManager.shutdown();\n        DBUtils.getInstance().shutdown();\n    }\n\n    public Authenticator getAuthenticator() {\n        return authenticator;\n    }\n\n    public DeviceSessionManager getDeviceSessionManager() {\n        return deviceSessionManager;\n    }\n\n    public DeviceMessageManager getDeviceMessageManager() {\n        return deviceMessageManager;\n    }\n\n    public DeviceSubscriptionManager getDeviceSubscriptionManager() {\n        return deviceSubscriptionManager;\n    }\n\n    public ClusterEventManager getClusterEventManager() {\n        return clusterEventManager;\n    }\n\n    public SubscriptionMatcher getSubscriptionMatcher() {\n        return subscriptionMatcher;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/ClusterEventManager.java",
    "content": "package org.jmqtt.bus;\n\nimport org.jmqtt.bus.event.GatewayListener;\nimport org.jmqtt.bus.model.ClusterEvent;\n\n/**\n * cluster event send and listen\n */\npublic interface ClusterEventManager {\n\n    void sendEvent(ClusterEvent clusterEvent);\n\n    void registerEventListener(GatewayListener listener);\n\n    void start();\n\n    void shutdown();\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceMessageManager.java",
    "content": "\npackage org.jmqtt.bus;\n\nimport org.jmqtt.bus.enums.MessageAckEnum;\nimport org.jmqtt.bus.model.DeviceMessage;\n\nimport java.util.List;\n\npublic interface DeviceMessageManager {\n\n    void clearUnAckMessage(String clientId);\n\n    /**\n     * send message to bus\n     * @param deviceMessage\n     */\n    void dispatcher(DeviceMessage deviceMessage);\n\n    Long storeMessage(DeviceMessage deviceMessage);\n\n    List<DeviceMessage> queryUnAckMessages(String clientId,int limit);\n\n    List<DeviceMessage> queryByIds(List<Long> ids);\n\n    Long addClientInBoxMsg(String clientId,Long messageId, MessageAckEnum ackEnum);\n\n    boolean ackMessage(String clientId,Long messageId);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSessionManager.java",
    "content": "\npackage org.jmqtt.bus;\n\nimport org.jmqtt.bus.model.DeviceSession;\n\npublic interface DeviceSessionManager {\n\n\n\n    DeviceSession getSession(String clientId);\n\n    void storeSession(DeviceSession deviceSession);\n\n\n    void offline(String clientId);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSubscriptionManager.java",
    "content": "package org.jmqtt.bus;\n\nimport org.jmqtt.bus.model.DeviceSubscription;\n\nimport java.util.Set;\n\npublic interface DeviceSubscriptionManager {\n\n    /**\n     * 1. Add subscription to the sub tree\n     * 2. Persistence subscription\n     * @param deviceSubscription\n     * @return true:sub success  false: sub fail\n     */\n    boolean subscribe(DeviceSubscription deviceSubscription);\n\n    /**\n     * 1. Remove subscription from the sub tree\n     * 2. Delete subscription from db\n     */\n    boolean unSubscribe(String clientId,String topic);\n\n    boolean isMatch(String pubTopic,String subTopic);\n\n    boolean onlySubscribe2Tree(DeviceSubscription deviceSubscription);\n\n    boolean onlyUnUnSubscribeFromTree(String clientId,String topic);\n\n    Set<DeviceSubscription> getAllSubscription(String clientId);\n\n    void deleteAllSubscription(String clientId);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/ClusterEventCodeEnum.java",
    "content": "package org.jmqtt.bus.enums;\n\n/**\n * cluster event code\n */\npublic enum ClusterEventCodeEnum {\n\n    MQTT_CLEAR_SESSION(\"MQTT_CLEAR_SESSION\"),\n\n    DISPATCHER_CLIENT_MESSAGE(\"DISPATCHER_CLIENT_MESSAGE\"),\n    ;\n\n    private String    code;\n\n    ClusterEventCodeEnum(String code) {\n        this.code = code;\n    }\n\n    public String getCode() {\n        return code;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/DeviceOnlineStateEnum.java",
    "content": "\npackage org.jmqtt.bus.enums;\n\npublic enum DeviceOnlineStateEnum {\n\n    ONLINE(\"ONLINE\"),\n\n    OFFLINE(\"OFFLINE\"),\n\n    ;\n\n    private String code;\n\n    DeviceOnlineStateEnum(String code) {\n        this.code = code;\n    }\n\n    public String getCode() {\n        return code;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageAckEnum.java",
    "content": "\npackage org.jmqtt.bus.enums;\n\npublic enum MessageAckEnum {\n\n    UN_ACK(0),\n\n    ACK(1),\n\n    ;\n\n    private int code;\n\n    MessageAckEnum(int code) {\n        this.code = code;\n    }\n\n    public int getCode() {\n        return code;\n    }\n\n    public static MessageAckEnum getByCode(int code) {\n        for (MessageAckEnum value : MessageAckEnum.values()) {\n            if (value.code == code) {\n                return value;\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageFlowEnum.java",
    "content": "package org.jmqtt.bus.enums;\n\n\npublic enum MessageFlowEnum {\n\n    INBOUND(\"INBOUND\"),\n\n    OUTBOUND(\"OUTBOUND\"),\n\n            ;\n\n    private String code;\n\n    MessageFlowEnum(String code) {\n        this.code = code;\n    }\n\n    public String getCode() {\n        return code;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageSourceEnum.java",
    "content": "package org.jmqtt.bus.enums;\n\n\npublic enum MessageSourceEnum {\n\n    DEVICE(\"DEVICE\"),\n\n    PLATFORM(\"PLATFORM\"),\n\n            ;\n\n    private String code;\n\n    MessageSourceEnum(String code) {\n        this.code = code;\n    }\n\n    public String getCode() {\n        return code;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/enums/TransportProtocolEnum.java",
    "content": "\npackage org.jmqtt.bus.enums;\n\n\npublic enum TransportProtocolEnum {\n\n    MQTT(\"MQTT\"),\n\n    TCP(\"TCP\"),\n\n    COAP(\"COAP\"),\n    ;\n\n    private String code;\n\n    TransportProtocolEnum(String code) {\n        this.code = code;\n    }\n\n    public String getCode() {\n        return code;\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/event/EventCenter.java",
    "content": "package org.jmqtt.bus.event;\n\nimport com.google.common.eventbus.EventBus;\nimport org.jmqtt.bus.model.ClusterEvent;\n\npublic class EventCenter {\n\n    private final EventBus eventBus;\n\n    public EventCenter(){\n        this.eventBus = new EventBus();\n    }\n\n    /**\n     * register gateway listener\n     * @param listener\n     */\n    public void register(GatewayListener listener){\n        this.eventBus.register(listener);\n    }\n\n    /**\n     * send event to bus,and gateway will consume this event\n     * @param clusterEvent\n     */\n    public void sendEvent(ClusterEvent clusterEvent){\n        this.eventBus.post(clusterEvent);\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/event/GatewayListener.java",
    "content": "\npackage org.jmqtt.bus.event;\n\nimport org.jmqtt.bus.model.ClusterEvent;\n\n/**\n * gateway listener\n * iot gateway 实现该接口，接收bus的事件进行客户端触达和处理\n */\npublic interface GatewayListener {\n\n    void consume(ClusterEvent clusterEvent);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/ClusterEventManagerImpl.java",
    "content": "package org.jmqtt.bus.impl;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.apache.ibatis.session.SqlSession;\nimport org.jmqtt.bus.ClusterEventManager;\nimport org.jmqtt.bus.enums.ClusterEventCodeEnum;\nimport org.jmqtt.bus.event.EventCenter;\nimport org.jmqtt.bus.event.GatewayListener;\nimport org.jmqtt.bus.model.ClusterEvent;\nimport org.jmqtt.bus.model.DeviceMessage;\nimport org.jmqtt.bus.store.DBCallback;\nimport org.jmqtt.bus.store.DBUtils;\nimport org.jmqtt.bus.store.daoobject.EventDO;\nimport org.jmqtt.bus.subscription.SubscriptionMatcher;\nimport org.jmqtt.bus.subscription.model.Subscription;\nimport org.jmqtt.support.helper.MixAll;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicLong;\n\n\n/**\n * send current node's event to other nodes 集群事件处理，两种方式,根据实际情况，选一个实现即可：\n * 1. 发布订阅： a. 发送消息 A -> mq系统转发 b. 第二阶段：消息订阅分发给本节点的设备 jmqtt B,C,D —> jmqtt B的连接设备\n * 2. jmqtt服务主动拉取: a. A —> 消息存储服务： b. 第二阶段:主动拉取 jmqtt B,C,D broker 定时批量拉取 —> 从消息存储服务拉取 —> 拉取后推送给 B,C,D上连接的设备\n */\npublic class ClusterEventManagerImpl implements ClusterEventManager {\n\n    private static final Logger        log        = JmqttLogger.busLog;\n    private static final AtomicLong    offset     = new AtomicLong();\n    private          AtomicBoolean pollStoped = new AtomicBoolean(false);\n    private int                    maxPollNum = 100;\n    private int                    pollWaitInterval = 10; // ms\n    private final EventCenter eventCenter;\n    private SubscriptionMatcher subscriptionMatcher;\n\n    public ClusterEventManagerImpl(SubscriptionMatcher subscriptionMatcher){\n        this.eventCenter = new EventCenter();\n        this.subscriptionMatcher = subscriptionMatcher;\n    }\n\n    @Override\n    public void start() {\n        Long maxId = DBUtils.operate(new DBCallback() {\n            @Override\n            public Long operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession,DBUtils.eventMapperClass).getMaxOffset();\n            }\n        });\n        if (maxId == null) {\n            offset.set(0);\n        } else {\n            offset.set(maxId);\n        }\n        new Thread(() -> {\n            while (!pollStoped.get()) {\n                try {\n                    List<ClusterEvent> eventList = pollEvent(maxPollNum);\n                    if (!MixAll.isEmpty(eventList)) {\n                        for (ClusterEvent event : eventList) {\n                            // Send event to iot gateway\n                            consumeEvent(event);\n                        }\n                    }\n                    if (MixAll.isEmpty(eventList) || eventList.size() < 5) {\n                        Thread.sleep(pollWaitInterval);\n                    }\n                } catch (Exception e) {\n                    LogUtil.warn(log, \"Poll event from cluster error.\", e);\n                }\n            }\n        }).start();\n\n        LogUtil.info(log,\"Cluster event server start.\");\n    }\n\n    @Override\n    public void shutdown() {\n        this.pollStoped.compareAndSet(false,true);\n    }\n\n    /**\n     * 集群消息往这里投递\n     */\n    private void consumeEvent(ClusterEvent event){\n\n        // 若是消息，进行订阅树的匹配\n        if (event.getClusterEventCode() == ClusterEventCodeEnum.DISPATCHER_CLIENT_MESSAGE){\n            DeviceMessage deviceMessage = JSONObject.parseObject(event.getContent(),DeviceMessage.class);\n            if (deviceMessage == null) {\n                LogUtil.error(log,\"[BUS EVENT] event content is empty.\");\n                return;\n            }\n            Set<Subscription> subscriptionSet = this.subscriptionMatcher.match(deviceMessage.getTopic());\n            deviceMessage.setContent(null);\n            if (subscriptionSet != null) {\n                subscriptionSet.forEach(item -> {\n                    ClusterEvent stayEvent = event.clone();\n                    stayEvent.setSubscription(item);\n\n                    eventCenter.sendEvent(stayEvent);\n                });\n            }\n            return;\n        }\n        eventCenter.sendEvent(event);\n    }\n\n\n    private List<ClusterEvent> pollEvent(int maxPollNum) {\n        // offset: min -> max\n        long currentOffset = offset.get();\n        List<EventDO> eventDOList =  DBUtils.operate(new DBCallback() {\n            @Override\n            public List<EventDO> operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession,DBUtils.eventMapperClass).consumeEvent(currentOffset,maxPollNum);\n            }\n        });\n        if (eventDOList == null || eventDOList.size() == 0) {\n            return Collections.emptyList();\n        }\n        List<ClusterEvent> events = new ArrayList<>();\n        for (EventDO eventDO : eventDOList) {\n            ClusterEvent event = new ClusterEvent();\n            event.setNodeIp(eventDO.getNodeIp());\n            event.setGmtCreate(eventDO.getGmtCreate());\n            event.setContent(eventDO.getContent());\n            event.setClusterEventCode(ClusterEventCodeEnum.valueOf(eventDO.getEventCode()));\n            events.add(event);\n        }\n\n        // reset offset\n        EventDO eventDO = eventDOList.get(eventDOList.size()-1);\n        if (!offset.compareAndSet(currentOffset,eventDO.getId())) {\n            LogUtil.warn(log,\"[RDBClusterEventHandler] pollEvent offset is wrong,expectOffset:{},currentOffset:{},maxOffset:{}\",\n                    offset.get(),currentOffset,eventDO.getId());\n            offset.set(eventDO.getId());\n        }\n        return events;\n    }\n\n    @Override\n    public void sendEvent(ClusterEvent clusterEvent) {\n        EventDO eventDO = convert(clusterEvent);\n        DBUtils.operate(new DBCallback() {\n            @Override\n            public Object operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession, DBUtils.eventMapperClass).sendEvent(eventDO);\n            }\n        });\n    }\n\n\n    public void registerEventListener(GatewayListener listener){\n        this.eventCenter.register(listener);\n    }\n\n\n    private EventDO convert(ClusterEvent clusterEvent) {\n        EventDO eventDO = new EventDO();\n        eventDO.setContent(clusterEvent.getContent());\n        eventDO.setNodeIp(clusterEvent.getNodeIp());\n        eventDO.setEventCode(clusterEvent.getClusterEventCode().getCode());\n        eventDO.setGmtCreate(clusterEvent.getGmtCreate());\n        return eventDO;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DefaultAuthenticator.java",
    "content": "package org.jmqtt.bus.impl;\n\nimport org.jmqtt.bus.Authenticator;\n\n\npublic class DefaultAuthenticator implements Authenticator {\n\n    @Override\n    public boolean login(String clientId, String userName, byte[] password) {\n        return true;\n    }\n\n    @Override\n    public boolean onBlackList(String clientId, String remoteIpAddress) {\n        return false;\n    }\n\n    @Override\n    public boolean clientIdVerify(String clientId) {\n        return true;\n    }\n\n    @Override\n    public boolean subscribeVerify(String clientId, String topic) {\n        return true;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceMessageManagerImpl.java",
    "content": "package org.jmqtt.bus.impl;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.apache.ibatis.session.SqlSession;\nimport org.jmqtt.bus.DeviceMessageManager;\nimport org.jmqtt.bus.enums.ClusterEventCodeEnum;\nimport org.jmqtt.bus.enums.MessageAckEnum;\nimport org.jmqtt.bus.enums.MessageSourceEnum;\nimport org.jmqtt.bus.model.DeviceInboxMessage;\nimport org.jmqtt.bus.model.DeviceMessage;\nimport org.jmqtt.bus.store.DBCallback;\nimport org.jmqtt.bus.store.DBUtils;\nimport org.jmqtt.bus.store.daoobject.DeviceInboxMessageDO;\nimport org.jmqtt.bus.store.daoobject.EventDO;\nimport org.jmqtt.bus.store.daoobject.MessageDO;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.jmqtt.support.remoting.RemotingHelper;\nimport org.slf4j.Logger;\n\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\n\npublic class DeviceMessageManagerImpl implements DeviceMessageManager {\n\n    private static final Logger log = JmqttLogger.busLog;\n\n    @Override\n    public void clearUnAckMessage(String clientId) {\n        DBUtils.operate(new DBCallback() {\n            @Override\n            public Integer operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession,DBUtils.clientInboxMessageMapperClass).truncateUnAck(clientId);\n            }\n        });\n    }\n\n    @Override\n    public void dispatcher(DeviceMessage deviceMessage) {\n\n        // store message and send event\n        SqlSession sqlSession = null;\n        try {\n            sqlSession = DBUtils.getSqlSessionWithTrans();\n            MessageDO messageDO = convert(deviceMessage);\n            DBUtils.getMapper(sqlSession,DBUtils.messageMapperClass).storeMessage(messageDO);\n            Long id = messageDO.getId();\n            if (id == null || id <= 0) {\n                LogUtil.error(log,\"[BUS] store message failure.\");\n                return;\n            }\n            deviceMessage.setId(id);\n            EventDO eventDO = convert2Event(deviceMessage);\n            DBUtils.getMapper(sqlSession,DBUtils.eventMapperClass).sendEvent(eventDO);\n            Long eventId = eventDO.getId();\n            if (eventId == null || eventId <= 0) {\n                LogUtil.error(log,\"[BUS] send event message failure.\");\n                sqlSession.rollback();\n                return;\n            }\n            sqlSession.commit();\n        } catch (Exception ex) {\n            LogUtil.error(log,\"[BUS] dispatcher message store caught exception,{}\",ex);\n            if (sqlSession != null) {\n                sqlSession.rollback();\n            }\n        } finally {\n            if (sqlSession != null) {\n                sqlSession.close();\n            }\n        }\n    }\n\n    @Override\n    public Long storeMessage(DeviceMessage deviceMessage) {\n        MessageDO messageDO = convert(deviceMessage);\n        DBUtils.operate(new DBCallback() {\n            @Override\n            public Long operate(SqlSession sqlSession) {\n                 return DBUtils.getMapper(sqlSession,DBUtils.messageMapperClass).storeMessage(messageDO);\n            }\n        });\n        return messageDO.getId();\n    }\n\n    @Override\n    public List<DeviceMessage> queryUnAckMessages(String clientId, int pageSize) {\n        List<DeviceInboxMessageDO> inboxMessageDOS =  DBUtils.operate(new DBCallback() {\n            @Override\n            public List<DeviceInboxMessageDO> operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession, DBUtils.clientInboxMessageMapperClass).getUnAckMessages(clientId,pageSize);\n            }\n        });\n\n        if (inboxMessageDOS == null) {\n            return null;\n        }\n        List<Long> ids = new ArrayList<>();\n        inboxMessageDOS.forEach(itemDO -> {\n            ids.add(itemDO.getMessageId());\n        });\n        return queryByIds(ids);\n    }\n\n    @Override\n    public List<DeviceMessage> queryByIds(List<Long> ids) {\n        List<MessageDO> messages =  DBUtils.operate(new DBCallback() {\n            @Override\n            public List<MessageDO> operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession, DBUtils.messageMapperClass).queryMessageByIds(ids);\n            }\n        });\n\n        List<DeviceMessage> deviceMessageList = new ArrayList<>(messages.size());\n        messages.forEach(item -> {\n            deviceMessageList.add(convert(item));\n        });\n\n        return deviceMessageList;\n    }\n\n    @Override\n    public Long addClientInBoxMsg(String clientId, Long messageId, MessageAckEnum ackEnum) {\n\n        DeviceInboxMessageDO deviceInboxMessageDO = new DeviceInboxMessageDO();\n        deviceInboxMessageDO.setAck(ackEnum.getCode());\n        deviceInboxMessageDO.setClientId(clientId);\n        deviceInboxMessageDO.setMessageId(messageId);\n        deviceInboxMessageDO.setStoredTime(new Date());\n\n        DBUtils.operate(new DBCallback() {\n            @Override\n            public Long operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession, DBUtils.clientInboxMessageMapperClass).addInboxMessage(deviceInboxMessageDO);\n            }\n        });\n        return deviceInboxMessageDO.getId();\n    }\n\n    @Override\n    public boolean ackMessage(String clientId, Long messageId) {\n        Integer effect = DBUtils.operate(new DBCallback() {\n            @Override\n            public Integer operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession, DBUtils.clientInboxMessageMapperClass).ack(clientId,messageId);\n            }\n        });\n        return effect > 0;\n    }\n\n    private EventDO convert2Event(DeviceMessage deviceMessage) {\n        EventDO eventDO = new EventDO();\n        eventDO.setNodeIp(RemotingHelper.getLocalAddr());\n        eventDO.setContent(JSONObject.toJSONString(deviceMessage));\n        eventDO.setEventCode(ClusterEventCodeEnum.DISPATCHER_CLIENT_MESSAGE.getCode());\n        eventDO.setGmtCreate(new Date());\n        return eventDO;\n    }\n\n    private MessageDO convert(DeviceMessage deviceMessage) {\n        MessageDO messageDO = new MessageDO();\n        messageDO.setContent(deviceMessage.getContent());\n        messageDO.setFromClientId(deviceMessage.getFromClientId());\n        messageDO.setSource(deviceMessage.getSource().getCode());\n        messageDO.setStoredTime(deviceMessage.getStoredTime());\n        messageDO.setTopic(deviceMessage.getTopic());\n        if (deviceMessage.getProperties() != null) {\n            messageDO.setProperties(JSONObject.toJSONString(deviceMessage.getProperties()));\n        }\n        return messageDO;\n    }\n\n    private DeviceMessage convert(MessageDO messageDO) {\n        DeviceMessage deviceMessage = new DeviceMessage();\n        deviceMessage.setTopic(messageDO.getTopic());\n        deviceMessage.setStoredTime(messageDO.getStoredTime());\n        deviceMessage.setSource(MessageSourceEnum.valueOf(messageDO.getSource()));\n        deviceMessage.setContent(messageDO.getContent());\n        deviceMessage.setId(messageDO.getId());\n        deviceMessage.setFromClientId(messageDO.getFromClientId());\n\n        String properties = messageDO.getProperties();\n        if (properties != null) {\n            deviceMessage.setProperties(JSONObject.parseObject(properties, Map.class));\n        }\n        return deviceMessage;\n    }\n\n\n    private DeviceInboxMessage convert(DeviceInboxMessageDO deviceInboxMessageDO) {\n        DeviceInboxMessage deviceInboxMessage = new DeviceInboxMessage();\n        deviceInboxMessage.setAck(deviceInboxMessageDO.getAck());\n        deviceInboxMessage.setAckTime(deviceInboxMessageDO.getAckTime());\n        deviceInboxMessage.setClientId(deviceInboxMessageDO.getClientId());\n        deviceInboxMessage.setId(deviceInboxMessageDO.getId());\n        deviceInboxMessage.setMessageId(deviceInboxMessageDO.getMessageId());\n        deviceInboxMessage.setStoredTime(deviceInboxMessageDO.getStoredTime());\n        return deviceInboxMessage;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSessionManagerImpl.java",
    "content": "package org.jmqtt.bus.impl;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.apache.ibatis.session.SqlSession;\nimport org.jmqtt.bus.DeviceSessionManager;\nimport org.jmqtt.bus.enums.DeviceOnlineStateEnum;\nimport org.jmqtt.bus.enums.TransportProtocolEnum;\nimport org.jmqtt.bus.model.DeviceSession;\nimport org.jmqtt.bus.store.DBCallback;\nimport org.jmqtt.bus.store.DBUtils;\nimport org.jmqtt.bus.store.daoobject.SessionDO;\n\nimport java.util.Date;\nimport java.util.Map;\n\npublic class DeviceSessionManagerImpl implements DeviceSessionManager {\n\n    @Override\n    public DeviceSession getSession(String clientId) {\n\n        // optimize: add cache\n        SessionDO sessionDO = (SessionDO) DBUtils.operate(new DBCallback() {\n            @Override\n            public Object operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession, DBUtils.sessionMapperClass).getSession(clientId);\n            }\n        });\n        if (sessionDO == null) {\n            return null;\n        }\n        return convert(sessionDO);\n    }\n\n    @Override\n    public void storeSession(DeviceSession deviceSession) {\n        SessionDO sessionDO = convert(deviceSession);\n        DBUtils.operate(new DBCallback() {\n            @Override\n            public Long operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession, DBUtils.sessionMapperClass).storeSession(sessionDO);\n            }\n        });\n    }\n\n    @Override\n    public void offline(String clientId) {\n        SessionDO sessionDO = new SessionDO();\n        sessionDO.setClientId(clientId);\n        sessionDO.setOnline(DeviceOnlineStateEnum.OFFLINE.getCode());\n        sessionDO.setLastOfflineTime(new Date());\n        DBUtils.operate(new DBCallback() {\n            @Override\n            public Object operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession, DBUtils.sessionMapperClass).offline(sessionDO);\n            }\n        });\n    }\n\n    private DeviceSession convert(SessionDO sessionDO) {\n        DeviceSession deviceSession = new DeviceSession();\n        deviceSession.setClientId(sessionDO.getClientId());\n        deviceSession.setClientIp(sessionDO.getClientIp());\n        deviceSession.setLastOfflineTime(sessionDO.getLastOfflineTime());\n        deviceSession.setOnline(DeviceOnlineStateEnum.valueOf(sessionDO.getOnline()));\n        deviceSession.setOnlineTime(sessionDO.getOnlineTime());\n        String properties = sessionDO.getProperties();\n        if (properties != null) {\n            deviceSession.setProperties(JSONObject.parseObject(properties, Map.class));\n        }\n        deviceSession.setServerIp(deviceSession.getServerIp());\n        deviceSession.setTransportProtocol(TransportProtocolEnum.valueOf(sessionDO.getTransportProtocol()));\n        return deviceSession;\n    }\n\n    private SessionDO convert(DeviceSession deviceSession) {\n        SessionDO sessionDO = new SessionDO();\n        sessionDO.setClientId(deviceSession.getClientId());\n        sessionDO.setClientIp(deviceSession.getClientIp());\n        sessionDO.setLastOfflineTime(deviceSession.getLastOfflineTime());\n        sessionDO.setOnline(deviceSession.getOnline().getCode());\n        sessionDO.setOnlineTime(deviceSession.getOnlineTime());\n        sessionDO.setProperties(deviceSession.getProperties() == null ? null : JSONObject.toJSONString(deviceSession.getProperties()));\n        sessionDO.setServerIp(deviceSession.getServerIp());\n        sessionDO.setTransportProtocol(deviceSession.getTransportProtocol().getCode());\n        return sessionDO;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSubscriptionManagerImpl.java",
    "content": "package org.jmqtt.bus.impl;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.apache.ibatis.session.SqlSession;\nimport org.jmqtt.bus.DeviceSubscriptionManager;\nimport org.jmqtt.bus.model.DeviceSubscription;\nimport org.jmqtt.bus.store.DBCallback;\nimport org.jmqtt.bus.store.DBUtils;\nimport org.jmqtt.bus.store.daoobject.SubscriptionDO;\nimport org.jmqtt.bus.subscription.SubscriptionMatcher;\nimport org.jmqtt.bus.subscription.Topic;\nimport org.jmqtt.bus.subscription.model.Subscription;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n\npublic class DeviceSubscriptionManagerImpl implements DeviceSubscriptionManager {\n\n    private static final Logger log = JmqttLogger.busLog;\n\n    private SubscriptionMatcher subscriptionMatcher;\n\n    public DeviceSubscriptionManagerImpl(SubscriptionMatcher subscriptionMatcher){\n        this.subscriptionMatcher = subscriptionMatcher;\n    }\n\n    @Override\n    public boolean subscribe(DeviceSubscription deviceSubscription) {\n        SqlSession sqlSession = null;\n        try {\n            sqlSession = DBUtils.getSqlSessionWithTrans();\n            SubscriptionDO subscriptionDO = convert(deviceSubscription);\n            DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).storeSubscription(subscriptionDO);\n            if (subscriptionDO.getId() == null || subscriptionDO.getId() <= 0) {\n                LogUtil.error(log,\"[SUBSCRIBE] store subscription fail.\");\n                return false;\n            }\n            Subscription subscription = convert2Sub(deviceSubscription);\n            boolean addSubTree = subscriptionMatcher.subscribe(subscription);\n            if (!addSubTree) {\n                LogUtil.error(log,\"[SUBSCRIBE] sub tree fail.\");\n                sqlSession.rollback();\n                return false;\n            }\n            sqlSession.commit();\n            return true;\n        } catch (Exception ex) {\n            LogUtil.error(log,\"[SUBSCRIBE] subscribe failure.ex:{}\",ex);\n            if (sqlSession != null) {\n                sqlSession.rollback();\n            }\n        } finally {\n            if (sqlSession != null) {\n                sqlSession.close();\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public boolean unSubscribe(String clientId,String topic) {\n        SqlSession sqlSession = null;\n        try {\n            sqlSession = DBUtils.getSqlSessionWithTrans();\n            Integer effectNum = DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).delSubscription(clientId,topic);\n            if (effectNum == null || effectNum <= 0) {\n                LogUtil.error(log,\"[UNSUBSCRIBE] del subscription fail.\");\n                return false;\n            }\n            boolean subSub = subscriptionMatcher.unSubscribe(topic,clientId);\n            if (!subSub) {\n                LogUtil.error(log,\"[SUBSUBSCRIBE] unsub from tree fail.\");\n                sqlSession.rollback();\n                return false;\n            }\n            sqlSession.commit();\n            return true;\n        } catch (Exception ex) {\n            LogUtil.error(log,\"[SUBSCRIBE] subscribe failure.ex:{}\",ex);\n            if (sqlSession != null) {\n                sqlSession.rollback();\n            }\n        } finally {\n            if (sqlSession != null) {\n                sqlSession.close();\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public boolean isMatch(String pubTopic, String subTopic) {\n        Topic topic = new Topic(pubTopic);\n        return topic.match(new Topic(subTopic));\n    }\n\n    @Override\n    public boolean onlySubscribe2Tree(DeviceSubscription deviceSubscription) {\n        Subscription subscription = convert2Sub(deviceSubscription);\n        return subscriptionMatcher.subscribe(subscription);\n    }\n\n    @Override\n    public boolean onlyUnUnSubscribeFromTree(String clientId, String topic) {\n        return subscriptionMatcher.unSubscribe(topic,clientId);\n    }\n\n    @Override\n    public Set<DeviceSubscription> getAllSubscription(String clientId) {\n        List<SubscriptionDO> subscriptionDOS = DBUtils.operate(new DBCallback() {\n            @Override\n            public List<SubscriptionDO> operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).getAllSubscription(clientId);\n            }\n        });\n        if (subscriptionDOS == null) {\n            return null;\n        }\n        Set<DeviceSubscription> subscriptions = new HashSet<>(subscriptionDOS.size());\n        subscriptionDOS.forEach(item -> {\n            subscriptions.add(convert(item));\n        });\n        return subscriptions;\n    }\n\n    @Override\n    public void deleteAllSubscription(String clientId) {\n        DBUtils.operate(new DBCallback() {\n            @Override\n            public Integer operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).clearSubscription(clientId);\n            }\n        });\n    }\n\n    private Subscription convert2Sub(DeviceSubscription deviceSubscription){\n        return new Subscription(deviceSubscription.getClientId(),deviceSubscription.getTopic(),deviceSubscription.getProperties());\n    }\n\n    private DeviceSubscription convert(SubscriptionDO subscriptionDO){\n        DeviceSubscription deviceSubscription = new DeviceSubscription();\n        deviceSubscription.setClientId(subscriptionDO.getClientId());\n        deviceSubscription.setTopic(subscriptionDO.getTopic());\n        deviceSubscription.setSubscribeTime(subscriptionDO.getSubscribeTime());\n        if (subscriptionDO.getProperties() != null) {\n            deviceSubscription.setProperties(JSONObject.parseObject(subscriptionDO.getProperties(), Map.class));\n        }\n        return deviceSubscription;\n    }\n\n    private SubscriptionDO convert(DeviceSubscription deviceSubscription){\n        SubscriptionDO subscriptionDO = new SubscriptionDO();\n        subscriptionDO.setClientId(deviceSubscription.getClientId());\n        subscriptionDO.setTopic(deviceSubscription.getTopic());\n        subscriptionDO.setProperties(deviceSubscription.getProperties() != null? JSONObject.toJSONString(deviceSubscription.getProperties()): null);\n        subscriptionDO.setSubscribeTime(deviceSubscription.getSubscribeTime());\n        return subscriptionDO;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/ClusterEvent.java",
    "content": "package org.jmqtt.bus.model;\n\nimport org.jmqtt.bus.enums.ClusterEventCodeEnum;\nimport org.jmqtt.bus.subscription.model.Subscription;\n\nimport java.util.Date;\n\npublic class ClusterEvent {\n\n    private Long id;\n\n    private String content;\n\n    private Date gmtCreate;\n\n    private String nodeIp;\n\n    private ClusterEventCodeEnum clusterEventCode;\n\n    /**\n     * 待分发消息时的订阅关系\n     */\n    private Subscription subscription;\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getContent() {\n        return content;\n    }\n\n    public void setContent(String content) {\n        this.content = content;\n    }\n\n    public Date getGmtCreate() {\n        return gmtCreate;\n    }\n\n    public void setGmtCreate(Date gmtCreate) {\n        this.gmtCreate = gmtCreate;\n    }\n\n    public String getNodeIp() {\n        return nodeIp;\n    }\n\n    public void setNodeIp(String nodeIp) {\n        this.nodeIp = nodeIp;\n    }\n\n    public ClusterEventCodeEnum getClusterEventCode() {\n        return clusterEventCode;\n    }\n\n    public void setClusterEventCode(ClusterEventCodeEnum clusterEventCode) {\n        this.clusterEventCode = clusterEventCode;\n    }\n\n    public Subscription getSubscription() {\n        return subscription;\n    }\n\n    public void setSubscription(Subscription subscription) {\n        this.subscription = subscription;\n    }\n\n    public ClusterEvent clone() {\n        ClusterEvent event = new ClusterEvent();\n        event.setClusterEventCode(this.getClusterEventCode());\n        event.setContent(this.getContent());\n        event.setGmtCreate(this.getGmtCreate());\n        event.setNodeIp(this.getNodeIp());\n        event.setId(this.getId());\n        event.setSubscription(this.getSubscription());\n        return event;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceInboxMessage.java",
    "content": "package org.jmqtt.bus.model;\n\nimport java.util.Date;\n\npublic class DeviceInboxMessage {\n\n    private Long id;\n\n    private String clientId;\n\n    private Long messageId;\n\n    private Integer ack;\n\n    private Date storedTime;\n\n    private Date ackTime;\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public void setClientId(String clientId) {\n        this.clientId = clientId;\n    }\n\n    public Long getMessageId() {\n        return messageId;\n    }\n\n    public void setMessageId(Long messageId) {\n        this.messageId = messageId;\n    }\n\n    public Integer getAck() {\n        return ack;\n    }\n\n    public void setAck(Integer ack) {\n        this.ack = ack;\n    }\n\n    public Date getStoredTime() {\n        return storedTime;\n    }\n\n    public void setStoredTime(Date storedTime) {\n        this.storedTime = storedTime;\n    }\n\n    public Date getAckTime() {\n        return ackTime;\n    }\n\n    public void setAckTime(Date ackTime) {\n        this.ackTime = ackTime;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceMessage.java",
    "content": "\npackage org.jmqtt.bus.model;\n\nimport org.jmqtt.bus.enums.MessageSourceEnum;\n\nimport java.util.Date;\nimport java.util.Map;\n\npublic class DeviceMessage {\n\n    private Long id;\n\n    private byte[] content;\n\n    private MessageSourceEnum source;\n\n    private String fromClientId;\n\n    private Date storedTime;\n\n    private String topic;\n\n    private Map<String,Object> properties;\n\n    public <T> T  getProperty(String key){\n        if (properties == null || properties.containsKey(key)) {\n            return (T) properties.get(key);\n        }\n        return null;\n    }\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public byte[] getContent() {\n        return content;\n    }\n\n    public void setContent(byte[] content) {\n        this.content = content;\n    }\n\n    public MessageSourceEnum getSource() {\n        return source;\n    }\n\n    public void setSource(MessageSourceEnum source) {\n        this.source = source;\n    }\n\n    public Date getStoredTime() {\n        return storedTime;\n    }\n\n    public void setStoredTime(Date storedTime) {\n        this.storedTime = storedTime;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public void setTopic(String topic) {\n        this.topic = topic;\n    }\n\n    public Map<String, Object> getProperties() {\n        return properties;\n    }\n\n    public void setProperties(Map<String, Object> properties) {\n        this.properties = properties;\n    }\n\n    public String getFromClientId() {\n        return fromClientId;\n    }\n\n    public void setFromClientId(String fromClientId) {\n        this.fromClientId = fromClientId;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSession.java",
    "content": "\npackage org.jmqtt.bus.model;\n\nimport org.jmqtt.bus.enums.DeviceOnlineStateEnum;\nimport org.jmqtt.bus.enums.TransportProtocolEnum;\n\nimport java.util.Date;\nimport java.util.Map;\n\n/**\n * session\n */\npublic class DeviceSession {\n\n    private String clientId;\n\n    private TransportProtocolEnum transportProtocol;\n\n    private String clientIp;\n\n    private String serverIp;\n\n    private DeviceOnlineStateEnum online;\n\n    private Date onlineTime;\n\n    private Date lastOfflineTime;\n\n    private Map<String,Object> properties;\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public void setClientId(String clientId) {\n        this.clientId = clientId;\n    }\n\n    public TransportProtocolEnum getTransportProtocol() {\n        return transportProtocol;\n    }\n\n    public void setTransportProtocol(TransportProtocolEnum transportProtocol) {\n        this.transportProtocol = transportProtocol;\n    }\n\n    public String getClientIp() {\n        return clientIp;\n    }\n\n    public void setClientIp(String clientIp) {\n        this.clientIp = clientIp;\n    }\n\n    public String getServerIp() {\n        return serverIp;\n    }\n\n    public void setServerIp(String serverIp) {\n        this.serverIp = serverIp;\n    }\n\n    public DeviceOnlineStateEnum getOnline() {\n        return online;\n    }\n\n    public void setOnline(DeviceOnlineStateEnum online) {\n        this.online = online;\n    }\n\n    public Map<String, Object> getProperties() {\n        return properties;\n    }\n\n    public void setProperties(Map<String, Object> properties) {\n        this.properties = properties;\n    }\n\n    public Date getOnlineTime() {\n        return onlineTime;\n    }\n\n    public void setOnlineTime(Date onlineTime) {\n        this.onlineTime = onlineTime;\n    }\n\n    public Date getLastOfflineTime() {\n        return lastOfflineTime;\n    }\n\n    public void setLastOfflineTime(Date lastOfflineTime) {\n        this.lastOfflineTime = lastOfflineTime;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSubscription.java",
    "content": "package org.jmqtt.bus.model;\n\nimport java.util.Date;\nimport java.util.Map;\n\npublic class DeviceSubscription {\n\n    private String clientId;\n\n    private String topic;\n\n    private Date subscribeTime;\n\n    private Map<String,Object> properties;\n\n\n    public <T> T getProperty(String key){\n        if (properties == null) {\n            return null;\n        }\n        return (T) properties.get(key);\n    }\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public void setClientId(String clientId) {\n        this.clientId = clientId;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public void setTopic(String topic) {\n        this.topic = topic;\n    }\n\n    public Date getSubscribeTime() {\n        return subscribeTime;\n    }\n\n    public void setSubscribeTime(Date subscribeTime) {\n        this.subscribeTime = subscribeTime;\n    }\n\n    public Map<String, Object> getProperties() {\n        return properties;\n    }\n\n    public void setProperties(Map<String, Object> properties) {\n        this.properties = properties;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/AbstractDBStore.java",
    "content": "package org.jmqtt.bus.store;\n\nimport org.apache.ibatis.session.SqlSession;\nimport org.jmqtt.bus.store.mapper.*;\nimport org.jmqtt.support.config.BrokerConfig;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.slf4j.Logger;\n\npublic abstract class AbstractDBStore {\n\n\n\n    protected final static Logger log = JmqttLogger.storeLog;\n\n    protected void start(BrokerConfig brokerConfig) {\n        DBUtils.getInstance().start(brokerConfig);\n    }\n\n    protected void shutdown() {\n        DBUtils.getInstance().shutdown();\n    }\n\n    protected <T> T getMapper(SqlSession sqlSession,Class<T> clazz) {\n        return sqlSession.getMapper(clazz);\n    }\n\n    protected Object operate(DBCallback dbCallback){\n       return DBUtils.getInstance().operate(dbCallback);\n    }\n\n    public SqlSession getSqlSessionWithTrans() {\n        return DBUtils.getInstance().getSqlSessionWithTrans();\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBCallback.java",
    "content": "package org.jmqtt.bus.store;\n\nimport org.apache.ibatis.session.SqlSession;\n\npublic interface DBCallback {\n    <T> T operate(SqlSession sqlSession);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBUtils.java",
    "content": "package org.jmqtt.bus.store;\n\nimport com.alibaba.druid.pool.DruidDataSource;\nimport org.apache.ibatis.datasource.DataSourceFactory;\nimport org.apache.ibatis.mapping.Environment;\nimport org.apache.ibatis.session.Configuration;\nimport org.apache.ibatis.session.SqlSession;\nimport org.apache.ibatis.session.SqlSessionFactory;\nimport org.apache.ibatis.session.SqlSessionFactoryBuilder;\nimport org.apache.ibatis.transaction.TransactionFactory;\nimport org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;\nimport org.jmqtt.bus.store.mapper.*;\nimport org.jmqtt.support.config.BrokerConfig;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport javax.sql.DataSource;\nimport java.sql.SQLException;\nimport java.util.Properties;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * db 工具类\n */\npublic class DBUtils {\n\n    private static final Logger log = JmqttLogger.storeLog;\n\n    public static final Class<SessionMapper>            sessionMapperClass           = SessionMapper.class;\n    public static final Class<SubscriptionMapper>       subscriptionMapperClass      = SubscriptionMapper.class;\n    public static final Class<EventMapper>              eventMapperClass             = EventMapper.class;\n    public static final Class<ClientInboxMessageMapper> clientInboxMessageMapperClass     = ClientInboxMessageMapper.class;\n    public static final Class<MessageMapper>            messageMapperClass    = MessageMapper.class;\n    public static final Class<RetainMessageMapper>      retainMessageMapperClass     = RetainMessageMapper.class;\n\n    private static final DBUtils dbUtils = new DBUtils();\n\n    private DBUtils(){}\n\n    private SqlSessionFactory sqlSessionFactory;\n\n    private  AtomicBoolean start = new AtomicBoolean(false);\n\n    public static DBUtils getInstance(){\n        return dbUtils;\n    }\n\n    public void start(BrokerConfig brokerConfig){\n        if (this.start.compareAndSet(false,true)) {\n            LogUtil.info(log,\"DB store start...\");\n            DataSource dataSource = new DataSourceFactory() {\n                @Override\n                public void setProperties(Properties properties) {\n                }\n\n                @Override\n                public DataSource getDataSource() {\n                    DruidDataSource dds = new DruidDataSource();\n                    dds.setDriverClassName(brokerConfig.getDriver());\n                    dds.setUrl(brokerConfig.getUrl());\n                    dds.setUsername(brokerConfig.getUsername());\n                    dds.setPassword(brokerConfig.getPassword());\n                    // 其他配置可自行补充\n                    dds.setKeepAlive(true);\n                    dds.setMinEvictableIdleTimeMillis(180000);\n                    dds.setMaxWait(10*1000);\n                    dds.setInitialSize(5);\n                    dds.setMinIdle(5);\n                    try {\n                        dds.init();\n                    } catch (SQLException e) {\n                        e.printStackTrace();\n                        System.exit(-1);\n                    }\n                    return dds;\n                }\n            }.getDataSource();\n\n            TransactionFactory transactionFactory = new JdbcTransactionFactory();\n            Environment environment = new Environment(\"development\", transactionFactory, dataSource);\n            Configuration configuration = new Configuration(environment);\n\n            // 初始化所有mapper\n            configuration.addMapper(SessionMapper.class);\n            configuration.addMapper(SubscriptionMapper.class);\n            configuration.addMapper(MessageMapper.class);\n            configuration.addMapper(EventMapper.class);\n            configuration.addMapper(ClientInboxMessageMapper.class);\n            configuration.addMapper(RetainMessageMapper.class);\n\n            configuration.setMapUnderscoreToCamelCase(true);\n            this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);\n            LogUtil.info(log,\"DB store start success...\");\n        }\n    }\n\n    public void shutdown(){}\n\n\n    public static final <T> T operate(DBCallback dbCallback) {\n        try (SqlSession sqlSession = DBUtils.getInstance().sqlSessionFactory.openSession(true)){\n            return dbCallback.operate(sqlSession);\n        }\n    }\n\n    public static final <T> T getMapper(SqlSession sqlSession,Class<T> clazz) {\n        return sqlSession.getMapper(clazz);\n    }\n\n    /**\n     * 获取关闭事物的session，需要手动提交事物\n     */\n    public static final SqlSession getSqlSessionWithTrans() {\n        SqlSession sqlSession = DBUtils.getInstance().sqlSessionFactory.openSession(false);\n        return sqlSession;\n    }\n\n\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/DeviceInboxMessageDO.java",
    "content": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class DeviceInboxMessageDO implements Serializable {\n\n    private static final long serialVersionUID = 143413131231231L;\n\n    private Long id;\n\n    private String clientId;\n\n    private Long messageId;\n\n    private Integer ack;\n\n    private Date storedTime;\n\n    private Date ackTime;\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public void setClientId(String clientId) {\n        this.clientId = clientId;\n    }\n\n    public Long getMessageId() {\n        return messageId;\n    }\n\n    public void setMessageId(Long messageId) {\n        this.messageId = messageId;\n    }\n\n    public Integer getAck() {\n        return ack;\n    }\n\n    public void setAck(Integer ack) {\n        this.ack = ack;\n    }\n\n    public Date getStoredTime() {\n        return storedTime;\n    }\n\n    public void setStoredTime(Date storedTime) {\n        this.storedTime = storedTime;\n    }\n\n    public Date getAckTime() {\n        return ackTime;\n    }\n\n    public void setAckTime(Date ackTime) {\n        this.ackTime = ackTime;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/EventDO.java",
    "content": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class EventDO implements Serializable {\n\n    private static final long serialVersionUID = 12213213131231231L;\n\n    private Long id;\n\n    private String content;\n\n    private Date gmtCreate;\n\n    private String nodeIp;\n\n    private String eventCode;\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getContent() {\n        return content;\n    }\n\n    public void setContent(String content) {\n        this.content = content;\n    }\n\n    public Date getGmtCreate() {\n        return gmtCreate;\n    }\n\n    public void setGmtCreate(Date gmtCreate) {\n        this.gmtCreate = gmtCreate;\n    }\n\n    public String getNodeIp() {\n        return nodeIp;\n    }\n\n    public void setNodeIp(String nodeIp) {\n        this.nodeIp = nodeIp;\n    }\n\n    public String getEventCode() {\n        return eventCode;\n    }\n\n    public void setEventCode(String eventCode) {\n        this.eventCode = eventCode;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/MessageDO.java",
    "content": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class MessageDO implements Serializable {\n\n    private static final long serialVersionUID = 12313131231231L;\n\n    private Long id;\n\n    private String source;\n\n    private byte[] content;\n\n    private String topic;\n\n    private String fromClientId;\n\n    private Date storedTime;\n\n    private String properties;\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getSource() {\n        return source;\n    }\n\n    public void setSource(String source) {\n        this.source = source;\n    }\n\n    public byte[] getContent() {\n        return content;\n    }\n\n    public void setContent(byte[] content) {\n        this.content = content;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public void setTopic(String topic) {\n        this.topic = topic;\n    }\n\n    public String getFromClientId() {\n        return fromClientId;\n    }\n\n    public void setFromClientId(String fromClientId) {\n        this.fromClientId = fromClientId;\n    }\n\n    public Date getStoredTime() {\n        return storedTime;\n    }\n\n    public void setStoredTime(Date storedTime) {\n        this.storedTime = storedTime;\n    }\n\n    public String getProperties() {\n        return properties;\n    }\n\n    public void setProperties(String properties) {\n        this.properties = properties;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/RetainMessageDO.java",
    "content": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class RetainMessageDO implements Serializable {\n\n    private static final long serialVersionUID = 12213131231231L;\n\n    private Long id;\n\n    private String topic;\n\n    private byte[] content;\n\n    private String fromClientId;\n\n    private Date storedTime;\n\n    private String properties;\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public void setTopic(String topic) {\n        this.topic = topic;\n    }\n\n    public byte[] getContent() {\n        return content;\n    }\n\n    public void setContent(byte[] content) {\n        this.content = content;\n    }\n\n    public String getFromClientId() {\n        return fromClientId;\n    }\n\n    public void setFromClientId(String fromClientId) {\n        this.fromClientId = fromClientId;\n    }\n\n    public Date getStoredTime() {\n        return storedTime;\n    }\n\n    public void setStoredTime(Date storedTime) {\n        this.storedTime = storedTime;\n    }\n\n    public String getProperties() {\n        return properties;\n    }\n\n    public void setProperties(String properties) {\n        this.properties = properties;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SessionDO.java",
    "content": "\npackage org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class SessionDO implements Serializable {\n\n    private static final long serialVersionUID = 12213131231231L;\n\n    private Long id;\n\n    private String clientId;\n\n    private String online;\n\n    private String transportProtocol;\n\n    private String clientIp;\n\n    private String serverIp;\n\n    private Date lastOfflineTime;\n\n    private Date onlineTime;\n\n    private String properties;\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public void setClientId(String clientId) {\n        this.clientId = clientId;\n    }\n\n    public String getOnline() {\n        return online;\n    }\n\n    public void setOnline(String online) {\n        this.online = online;\n    }\n\n    public String getTransportProtocol() {\n        return transportProtocol;\n    }\n\n    public void setTransportProtocol(String transportProtocol) {\n        this.transportProtocol = transportProtocol;\n    }\n\n    public String getClientIp() {\n        return clientIp;\n    }\n\n    public void setClientIp(String clientIp) {\n        this.clientIp = clientIp;\n    }\n\n    public String getServerIp() {\n        return serverIp;\n    }\n\n    public void setServerIp(String serverIp) {\n        this.serverIp = serverIp;\n    }\n\n    public Date getLastOfflineTime() {\n        return lastOfflineTime;\n    }\n\n    public void setLastOfflineTime(Date lastOfflineTime) {\n        this.lastOfflineTime = lastOfflineTime;\n    }\n\n    public Date getOnlineTime() {\n        return onlineTime;\n    }\n\n    public void setOnlineTime(Date onlineTime) {\n        this.onlineTime = onlineTime;\n    }\n\n    public String getProperties() {\n        return properties;\n    }\n\n    public void setProperties(String properties) {\n        this.properties = properties;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SubscriptionDO.java",
    "content": "package org.jmqtt.bus.store.daoobject;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\npublic class SubscriptionDO implements Serializable {\n\n    private static final long serialVersionUID = 12213131231231L;\n\n    private Long id;\n\n    private String clientId;\n\n    private String topic;\n\n    private Date subscribeTime;\n\n    private String properties;\n\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public void setClientId(String clientId) {\n        this.clientId = clientId;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public void setTopic(String topic) {\n        this.topic = topic;\n    }\n\n    public Date getSubscribeTime() {\n        return subscribeTime;\n    }\n\n    public void setSubscribeTime(Date subscribeTime) {\n        this.subscribeTime = subscribeTime;\n    }\n\n    public String getProperties() {\n        return properties;\n    }\n\n    public void setProperties(String properties) {\n        this.properties = properties;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/ClientInboxMessageMapper.java",
    "content": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.*;\nimport org.jmqtt.bus.store.daoobject.DeviceInboxMessageDO;\n\nimport java.util.List;\n\npublic interface ClientInboxMessageMapper {\n\n    @Insert(\"INSERT INTO jmqtt_client_inbox(client_id,message_id,ack,stored_time) VALUES (#{clientId},#{messageId},#{ack},#{storedTime})\")\n    @Options(useGeneratedKeys=true,keyProperty=\"id\")\n    Long addInboxMessage(DeviceInboxMessageDO deviceInboxMessageDO);\n\n    @Update(\"UPDATE jmqtt_client_inbox set ack = 1,ack_time = now()\"\n            + \"WHERE client_id = #{clientId} and message_id = #{messageId}\")\n    Integer ack(@Param(\"clientId\") String clientId,@Param(\"messageId\") Long messageId);\n\n\n    @Update(\"DELETE FROM jmqtt_client_inbox WHERE client_id = #{clientId} AND ack = 0\")\n    Integer truncateUnAck(@Param(\"clientId\") String clientId);\n\n    @Select(\"SELECT * FROM jmqtt_client_inbox WHERE client_id = #{clientId} and ack = 0 ORDER BY stored_time ASC LIMIT #{limit} \")\n    List<DeviceInboxMessageDO> getUnAckMessages(@Param(\"clientId\") String clientId, @Param(\"limit\") int limit);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/EventMapper.java",
    "content": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.Insert;\nimport org.apache.ibatis.annotations.Options;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Select;\nimport org.jmqtt.bus.store.daoobject.EventDO;\n\nimport java.util.List;\n\npublic interface EventMapper {\n\n    @Insert(\"insert into jmqtt_cluster_event (content,gmt_create,node_ip,event_code) values \"\n            + \"(#{content},#{gmtCreate},#{nodeIp},#{eventCode})\")\n    @Options(useGeneratedKeys=true,keyProperty=\"id\")\n    Long sendEvent(EventDO eventDO);\n\n\n    @Select(\"select id,content,gmt_create,node_ip,event_code from jmqtt_cluster_event \"\n            + \"where id > #{offset} order by id asc limit #{maxNum}\")\n    List<EventDO> consumeEvent(@Param(\"offset\") long offset, @Param(\"maxNum\") int maxNum);\n\n    @Select(\"SELECT max(id) FROM jmqtt_cluster_event\")\n    Long getMaxOffset();\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/MessageMapper.java",
    "content": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.Insert;\nimport org.apache.ibatis.annotations.Options;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Select;\nimport org.jmqtt.bus.store.daoobject.MessageDO;\n\nimport java.util.List;\n\npublic interface MessageMapper {\n\n    @Insert(\"INSERT INTO jmqtt_message(id,source,content,topic,from_client_id,stored_time,properties) VALUES(#{id},#{source},#{content}\"\n            + \",#{topic},#{fromClientId},#{storedTime},#{properties})\")\n    @Options(useGeneratedKeys=true,keyProperty=\"id\")\n    Long storeMessage(MessageDO messageDO);\n\n    @Select(\"SELECT id,source,content,topic,from_client_id,stored_time,properties FROM jmqtt_message WHERE id = #{id}\")\n    MessageDO getMessage(@Param(\"id\") long id);\n\n    @Select(\"<script>SELECT id,source,content,topic,from_client_id,stored_time,properties FROM jmqtt_message \"\n            + \"where id in \"\n            + \"<foreach collection='ids' item='id' open='(' separator=',' close=')'>\"\n            + \"#{id}\"\n            + \"</foreach>  \"\n            + \"</script>\")\n    List<MessageDO> queryMessageByIds(@Param(\"ids\")List<Long> ids);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/RetainMessageMapper.java",
    "content": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.Delete;\nimport org.apache.ibatis.annotations.Insert;\nimport org.apache.ibatis.annotations.Options;\nimport org.apache.ibatis.annotations.Select;\nimport org.jmqtt.bus.store.daoobject.RetainMessageDO;\n\nimport java.util.List;\n\npublic interface RetainMessageMapper {\n\n    @Insert(\"INSERT INTO jmqtt_retain_message(topic,content,from_client_id,stored_time,properties) VALUES\"\n            + \"(#{topic},#{content},#{fromClientId},#{storedTime},#{properties})\"\n            + \" on DUPLICATE key update content = #{content},from_client_id = #{fromClientId},stored_time=#{storedTime},properties = #{properties}\")\n    @Options(useGeneratedKeys=true,keyProperty=\"id\")\n    Long storeRetainMessage(RetainMessageDO retainMessageDO);\n\n    @Select(\"SELECT topic,content,from_client_id,stored_time,properties FROM jmqtt_retain_message\")\n    List<RetainMessageDO> getAllRetainMessage();\n\n    @Delete(\"DELETE FROM jmqtt_retain_message WHERE topic = #{topic}\")\n    Integer delRetainMessage(String topic);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SessionMapper.java",
    "content": "\npackage org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.Insert;\nimport org.apache.ibatis.annotations.Options;\nimport org.apache.ibatis.annotations.Select;\nimport org.apache.ibatis.annotations.Update;\nimport org.jmqtt.bus.store.daoobject.SessionDO;\n\npublic interface SessionMapper {\n\n    @Select(\"select client_id,online,transport_protocol,client_ip,server_ip,last_offline_time,online_time,properties from jmqtt_session where client_id = #{clientId}\")\n    SessionDO getSession(String clientId);\n\n    @Insert(\"insert into jmqtt_session(client_id,online,transport_protocol,client_ip,server_ip,last_offline_time,online_time,properties) values \"\n            + \"(#{clientId},#{online},#{transportProtocol},#{clientIp},#{serverIp},#{lastOfflineTime},#{onlineTime},#{properties}) \"\n            + \"on DUPLICATE key update online = #{online},last_offline_time = #{lastOfflineTime},online_time = #{onlineTime}\"\n            + \",client_ip=#{clientIp},server_ip=#{serverIp},transport_protocol=#{transportProtocol}\")\n    @Options(useGeneratedKeys=true,keyProperty=\"id\")\n    Long storeSession(SessionDO sessionDO);\n\n    @Update(\"update jmqtt_session set last_offline_time = #{lastOfflineTime},online = #{online} where \"\n            + \"client_id = #{clientId}\")\n    Long offline(SessionDO sessionDO);\n\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SubscriptionMapper.java",
    "content": "package org.jmqtt.bus.store.mapper;\n\nimport org.apache.ibatis.annotations.*;\nimport org.jmqtt.bus.store.daoobject.SubscriptionDO;\n\nimport java.util.List;\n\npublic interface SubscriptionMapper {\n\n    @Insert(\"INSERT INTO jmqtt_subscription (client_id,topic,properties,subscribe_time) \"\n            + \"VALUES (#{clientId},#{topic},#{properties},#{subscribeTime}) on duplicate key update properties = #{properties},subscribe_time=#{subscribeTime}\")\n    @Options(useGeneratedKeys=true,keyProperty=\"id\")\n    Long storeSubscription(SubscriptionDO subscriptionDO);\n\n    @Delete(\"DELETE FROM jmqtt_subscription WHERE client_id = #{clientId}\")\n    Integer clearSubscription(String clientId);\n\n    @Delete(\"DELETE FROM jmqtt_subscription WHERE client_id = #{clientId} AND topic = #{topic}\")\n    Integer delSubscription(@Param(\"clientId\") String clientId, @Param(\"topic\") String topic);\n\n    @Select(\"SELECT id,client_id,topic,properties,subscribe_time FROM jmqtt_subscription WHERE client_id = #{clientId}\")\n    List<SubscriptionDO> getAllSubscription(String clientId);\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CNode.java",
    "content": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\n\nimport java.util.*;\n\npublic class CNode {\n\n    private Token       token;\n    private List<INode> children;\n    Set<Subscription> subscriptions;\n\n    CNode() {\n        this.children = new ArrayList<>();\n        this.subscriptions = new HashSet<>();\n    }\n\n    //Copy constructor\n    private CNode(Token token, List<INode> children, Set<Subscription> subscriptions) {\n        this.token = token; // keep reference, root comparison in directory logic relies on it for now.\n        this.subscriptions = new HashSet<>(subscriptions);\n        this.children = new ArrayList<>(children);\n    }\n\n    public Token getToken() {\n        return token;\n    }\n\n    public void setToken(Token token) {\n        this.token = token;\n    }\n\n    boolean anyChildrenMatch(Token token) {\n        for (INode iNode : children) {\n            final CNode child = iNode.mainNode();\n            if (child.equalsToken(token)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    List<INode> allChildren() {\n        return this.children;\n    }\n\n    INode childOf(Token token) {\n        for (INode iNode : children) {\n            final CNode child = iNode.mainNode();\n            if (child.equalsToken(token)) {\n                return iNode;\n            }\n        }\n        throw new IllegalArgumentException(\"Asked for a token that doesn't exists in any child [\" + token + \"]\");\n    }\n\n    private boolean equalsToken(Token token) {\n        return token != null && this.token != null && this.token.equals(token);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(token);\n    }\n\n    CNode copy() {\n        return new CNode(this.token, this.children, this.subscriptions);\n    }\n\n    public void add(INode newINode) {\n        this.children.add(newINode);\n    }\n\n    public void remove(INode node) {\n        this.children.remove(node);\n    }\n\n    CNode addSubscription(Subscription newSubscription) {\n        // if already contains one with same topic and same client, keep that with higher QoS\n        if (subscriptions.contains(newSubscription)) {\n            final Subscription existing = subscriptions.stream()\n                    .filter(s -> s.equals(newSubscription))\n                    .findFirst().get();\n            subscriptions.remove(existing);\n            subscriptions.add(new Subscription(newSubscription));\n        } else {\n            this.subscriptions.add(new Subscription(newSubscription));\n        }\n        return this;\n    }\n\n    boolean containsOnly(String clientId) {\n        for (Subscription sub : this.subscriptions) {\n            if (!sub.getClientId().equals(clientId)) {\n                return false;\n            }\n        }\n        return !this.subscriptions.isEmpty();\n    }\n\n    public boolean contains(String clientId) {\n        for (Subscription sub : this.subscriptions) {\n            if (sub.getClientId().equals(clientId)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    void removeSubscriptionsFor(String clientId) {\n        Set<Subscription> toRemove = new HashSet<>();\n        for (Subscription sub : this.subscriptions) {\n            if (sub.getClientId().equals(clientId)) {\n                toRemove.add(sub);\n            }\n        }\n        this.subscriptions.removeAll(toRemove);\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrie.java",
    "content": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\n\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Optional;\nimport java.util.Set;\n\npublic class CTrie {\n\n    interface IVisitor<T> {\n\n        void visit(CNode node, int deep);\n\n        T getResult();\n    }\n\n    private static final Token ROOT = new Token(\"root\");\n    private static final INode NO_PARENT = null;\n\n    private enum Action {\n        OK, REPEAT\n    }\n\n    INode root;\n\n    public CTrie() {\n        final CNode mainNode = new CNode();\n        mainNode.setToken(ROOT);\n        this.root = new INode(mainNode);\n    }\n\n    public Optional<CNode> lookup(Topic topic) {\n        INode inode = this.root;\n        Token token = topic.headToken();\n        while (!topic.isEmpty() && (inode.mainNode().anyChildrenMatch(token))) {\n            topic = topic.exceptHeadToken();\n            inode = inode.mainNode().childOf(token);\n            token = topic.headToken();\n        }\n        if (inode == null || !topic.isEmpty()) {\n            return Optional.empty();\n        }\n        return Optional.of(inode.mainNode());\n    }\n\n    enum NavigationAction {\n        MATCH, GODEEP, STOP\n    }\n\n    private NavigationAction evaluate(Topic topic, CNode cnode) {\n        if (Token.MULTI.equals(cnode.getToken())) {\n            return NavigationAction.MATCH;\n        }\n        if (topic.isEmpty()) {\n            return NavigationAction.STOP;\n        }\n        final Token token = topic.headToken();\n        if (!(Token.SINGLE.equals(cnode.getToken()) || cnode.getToken().equals(token) || ROOT.equals(cnode.getToken()))) {\n            return NavigationAction.STOP;\n        }\n        return NavigationAction.GODEEP;\n    }\n\n    public Set<Subscription> recursiveMatch(Topic topic) {\n        return recursiveMatch(topic, this.root);\n    }\n\n    private Set<Subscription> recursiveMatch(Topic topic, INode inode) {\n        CNode cnode = inode.mainNode();\n        if (cnode instanceof TNode) {\n            return Collections.emptySet();\n        }\n        NavigationAction action = evaluate(topic, cnode);\n        if (action == NavigationAction.MATCH) {\n            return cnode.subscriptions;\n        }\n        if (action == NavigationAction.STOP) {\n            return Collections.emptySet();\n        }\n        Topic remainingTopic = (ROOT.equals(cnode.getToken())) ? topic : topic.exceptHeadToken();\n        Set<Subscription> subscriptions = new HashSet<>();\n        if (remainingTopic.isEmpty()) {\n            subscriptions.addAll(cnode.subscriptions);\n        }\n        for (INode subInode : cnode.allChildren()) {\n            subscriptions.addAll(recursiveMatch(remainingTopic, subInode));\n        }\n        return subscriptions;\n    }\n\n    public void addToTree(Subscription newSubscription) {\n        Action res;\n        do {\n            res = insert(new Topic(newSubscription.getTopic()), this.root, newSubscription);\n        } while (res == Action.REPEAT);\n    }\n\n    private Action insert(Topic topic, final INode inode, Subscription newSubscription) {\n        Token token = topic.headToken();\n        if (!topic.isEmpty() && inode.mainNode().anyChildrenMatch(token)) {\n            Topic remainingTopic = topic.exceptHeadToken();\n            INode nextInode = inode.mainNode().childOf(token);\n            return insert(remainingTopic, nextInode, newSubscription);\n        } else {\n            if (topic.isEmpty()) {\n                return insertSubscription(inode, newSubscription);\n            } else {\n                return createNodeAndInsertSubscription(topic, inode, newSubscription);\n            }\n        }\n    }\n\n    private Action insertSubscription(INode inode, Subscription newSubscription) {\n        CNode cnode = inode.mainNode();\n        CNode updatedCnode = cnode.copy().addSubscription(newSubscription);\n        if (inode.compareAndSet(cnode, updatedCnode)) {\n            return Action.OK;\n        } else {\n            return Action.REPEAT;\n        }\n    }\n\n    private Action createNodeAndInsertSubscription(Topic topic, INode inode, Subscription newSubscription) {\n        INode newInode = createPathRec(topic, newSubscription);\n        CNode cnode = inode.mainNode();\n        CNode updatedCnode = cnode.copy();\n        updatedCnode.add(newInode);\n\n        return inode.compareAndSet(cnode, updatedCnode) ? Action.OK : Action.REPEAT;\n    }\n\n    private INode createPathRec(Topic topic, Subscription newSubscription) {\n        Topic remainingTopic = topic.exceptHeadToken();\n        if (!remainingTopic.isEmpty()) {\n            INode inode = createPathRec(remainingTopic, newSubscription);\n            CNode cnode = new CNode();\n            cnode.setToken(topic.headToken());\n            cnode.add(inode);\n            return new INode(cnode);\n        } else {\n            return createLeafNodes(topic.headToken(), newSubscription);\n        }\n    }\n\n    private INode createLeafNodes(Token token, Subscription newSubscription) {\n        CNode newLeafCnode = new CNode();\n        newLeafCnode.setToken(token);\n        newLeafCnode.addSubscription(newSubscription);\n\n        return new INode(newLeafCnode);\n    }\n\n    public void removeFromTree(Topic topic, String clientID) {\n        Action res;\n        do {\n            res = remove(clientID, topic, this.root, NO_PARENT);\n        } while (res == Action.REPEAT);\n    }\n\n    private Action remove(String clientId, Topic topic, INode inode, INode iParent) {\n        Token token = topic.headToken();\n        if (!topic.isEmpty() && (inode.mainNode().anyChildrenMatch(token))) {\n            Topic remainingTopic = topic.exceptHeadToken();\n            INode nextInode = inode.mainNode().childOf(token);\n            return remove(clientId, remainingTopic, nextInode, inode);\n        } else {\n            final CNode cnode = inode.mainNode();\n            if (cnode instanceof TNode) {\n                // this inode is a tomb, has no clients and should be cleaned up\n                // Because we implemented cleanTomb below, this should be rare, but possible\n                // Consider calling cleanTomb here too\n                return Action.OK;\n            }\n            if (cnode.containsOnly(clientId) && topic.isEmpty() && cnode.allChildren().isEmpty()) {\n                // last client to leave this node, AND there are no downstream children, remove via TNode tomb\n                if (inode == this.root) {\n                    return inode.compareAndSet(cnode, inode.mainNode().copy()) ? Action.OK : Action.REPEAT;\n                }\n                TNode tnode = new TNode();\n                return inode.compareAndSet(cnode, tnode) ? cleanTomb(inode, iParent) : Action.REPEAT;\n            } else if (cnode.contains(clientId) && topic.isEmpty()) {\n                CNode updatedCnode = cnode.copy();\n                updatedCnode.removeSubscriptionsFor(clientId);\n                return inode.compareAndSet(cnode, updatedCnode) ? Action.OK : Action.REPEAT;\n            } else {\n                //someone else already removed\n                return Action.OK;\n            }\n        }\n    }\n\n    /**\n     *\n     * Cleans Disposes of TNode in separate Atomic CAS operation per\n     * http://bravenewgeek.com/breaking-and-entering-lose-the-lock-while-embracing-concurrency/\n     *\n     * We roughly follow this theory above, but we allow CNode with no Subscriptions to linger (for now).\n     *\n     *\n     * @param inode inode that handle to the tomb node.\n     * @param iParent inode parent.\n     * @return REPEAT if the this methods wasn't successful or OK.\n     */\n    private Action cleanTomb(INode inode, INode iParent) {\n        CNode updatedCnode = iParent.mainNode().copy();\n        updatedCnode.remove(inode);\n        return iParent.compareAndSet(iParent.mainNode(), updatedCnode) ? Action.OK : Action.REPEAT;\n    }\n\n    public int size() {\n        SubscriptionCounterVisitor visitor = new SubscriptionCounterVisitor();\n        dfsVisit(this.root, visitor, 0);\n        return visitor.getResult();\n    }\n\n    public String dumpTree() {\n        DumpTreeVisitor visitor = new DumpTreeVisitor();\n        dfsVisit(this.root, visitor, 0);\n        return visitor.getResult();\n    }\n\n    private void dfsVisit(INode node, IVisitor<?> visitor, int deep) {\n        if (node == null) {\n            return;\n        }\n\n        visitor.visit(node.mainNode(), deep);\n        ++deep;\n        for (INode child : node.mainNode().allChildren()) {\n            dfsVisit(child, visitor, deep);\n        }\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcher.java",
    "content": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.slf4j.Logger;\n\nimport java.util.Set;\n\npublic class CTrieSubscriptionMatcher implements SubscriptionMatcher {\n\n    private static final Logger log = JmqttLogger.busLog;\n\n    private CTrie ctrie;\n\n    public CTrieSubscriptionMatcher(){\n        this.ctrie = new CTrie();\n    }\n\n    @Override\n    public Set<Subscription> match(String topicFilter) {\n        Topic topic = new Topic(topicFilter);\n        return ctrie.recursiveMatch(topic);\n    }\n\n\n    @Override\n    public boolean subscribe(Subscription newSubscription) {\n        ctrie.addToTree(newSubscription);\n        return true;\n    }\n\n\n    @Override\n    public boolean unSubscribe(String topicFilter, String clientID) {\n        ctrie.removeFromTree(new Topic(topicFilter), clientID);\n        return true;\n    }\n\n    @Override\n    public int size() {\n        return ctrie.size();\n    }\n\n    @Override\n    public String dumpTree() {\n        return ctrie.dumpTree();\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DefaultSubscriptionTreeMatcher.java",
    "content": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\npublic class DefaultSubscriptionTreeMatcher implements SubscriptionMatcher {\n\n    private static final Logger log = JmqttLogger.mqttLog;\n\n    private final Object   lock   = new Object();\n    private       TreeNode root   = new TreeNode(new Token(\"root\"));\n    private       Token    EMPTY  = new Token(\"\");\n    private       Token    SINGLE = new Token(\"+\");\n    private       Token    MULTY  = new Token(\"#\");\n\n    public DefaultSubscriptionTreeMatcher() {\n    }\n\n    @Override\n    public boolean subscribe(Subscription subscription) {\n        try {\n            String topic = subscription.getTopic();\n            TreeNode currentNode = recursionGetTreeNode(topic, root);\n            Set<Subscription> subscriptions = currentNode.getSubscribers();\n            if (subscriptions.contains(subscription)) {\n                for (Subscription sub : subscriptions) {\n                    if (sub.equals(subscription)) {\n                        sub.setProperties(subscription.getProperties());\n                        return true;\n                    }\n                }\n            }\n            currentNode.addSubscriber(subscription);\n        } catch (Exception ex) {\n            LogUtil.warn(log, \"[Subscription] -> Subscribe failed,clientId={},topic={},qos={}\",\n                    subscription.getClientId(), subscription.getTopic(), subscription.getProperties());\n            return true;\n        }\n        return true;\n    }\n\n    @Override\n    public boolean unSubscribe(String topic, String clientId) {\n        TreeNode currentNode = recursionGetTreeNode(topic, root);\n        currentNode.getSubscribers().remove(new Subscription(clientId, topic, null));\n        return true;\n    }\n\n    private TreeNode recursionGetTreeNode(String topic, TreeNode node) {\n        String[] tokens = topic.split(\"/\");\n        Token token = new Token(tokens[0]);\n        TreeNode matchNode = node.getChildNodeByToken(token);\n        if (Objects.isNull(matchNode)) {\n            synchronized (lock) {\n                matchNode = node.getChildNodeByToken(token);\n                if (Objects.isNull(matchNode)) {\n                    matchNode = new TreeNode(token);\n                    node.addChild(matchNode);\n                }\n            }\n        }\n        if (tokens.length > 1) {\n            String childTopic = topic.substring(topic.indexOf(\"/\") + 1);\n            return recursionGetTreeNode(childTopic, matchNode);\n        } else {\n            return matchNode;\n        }\n    }\n\n    @Override\n    public Set<Subscription> match(String topic) {\n        Set<Subscription> subscriptions = new HashSet<>();\n        recursionMatch(topic, root, subscriptions);\n        return subscriptions;\n    }\n\n\n    @Override\n    public int size() {\n        return 0;\n    }\n\n    @Override\n    public String dumpTree() {\n        return null;\n    }\n\n    private boolean innerIsMatch(String pubTopic, String subTopic) {\n        if (pubTopic.equals(subTopic)) {\n            return true;\n        }\n        String[] pubTokenStr = pubTopic.split(\"/\");\n        String[] subTokenStr = subTopic.split(\"/\");\n        int pubLen = pubTokenStr.length;\n        int subLen = subTokenStr.length;\n        if (pubLen != subLen) {\n            Token lastSubToken = new Token(subTokenStr[subLen - 1]);\n            if (subLen > pubLen || (!lastSubToken.equals(MULTY))) {\n                return false;\n            }\n        }\n        for (int i = 0; i < pubLen; i++) {\n            Token pubToken = new Token(pubTokenStr[i]);\n            Token subToken = new Token(subTokenStr[i]);\n            if (subToken.equals(MULTY)) {\n                return true;\n            } else if ((!pubToken.equals(subToken)) && (!subToken.equals(SINGLE))) {\n                return false;\n            }\n            if (i == subLen - 1) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private void recursionMatch(String topic, TreeNode node,\n                                Set<Subscription> subscriptions) {\n        String[] topics = topic.split(\"/\");\n        Token token = new Token(topics[0]);\n        List<TreeNode> childNodes = node.getChildren();\n        if (topics.length > 1) {\n            String nextTopic = topic.substring(topic.indexOf(\"/\") + 1);\n            for (TreeNode itemNode : childNodes) {\n                if (itemNode.getToken().equals(token) || itemNode.getToken().equals(SINGLE)) {\n                    recursionMatch(nextTopic, itemNode, subscriptions);\n                }\n                if (itemNode.getToken().equals(MULTY)) {\n                    subscriptions.addAll(itemNode.getSubscribers());\n                }\n            }\n        } else {\n            for (TreeNode itemNode : childNodes) {\n                if (itemNode.getToken().equals(token) || itemNode.getToken().equals(SINGLE)\n                        || itemNode.getToken().equals(MULTY)) {\n                    subscriptions.addAll(itemNode.getSubscribers());\n                }\n            }\n        }\n    }\n\n    class Token {\n        String token;\n\n        public Token(String token) {\n            this.token = token;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) {\n                return true;\n            }\n            if (o == null || getClass() != o.getClass()) {\n                return false;\n            }\n            Token token1 = (Token) o;\n            return Objects.equals(token, token1.token);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(token);\n        }\n    }\n\n    class TreeNode {\n\n        private Token             token;\n        private Set<Subscription> subscribers = new CopyOnWriteArraySet<>();\n        private List<TreeNode>    children    = new CopyOnWriteArrayList<>();\n\n        public TreeNode(Token token) {\n            this.token = token;\n        }\n\n        public void addSubscriber(Subscription subscription) {\n            this.subscribers.add(subscription);\n        }\n\n        public void addChild(TreeNode treeNode) {\n            this.children.add(treeNode);\n        }\n\n        public Token getToken() {\n            return token;\n        }\n\n        public Set<Subscription> getSubscribers() {\n            return subscribers;\n        }\n\n        public List<TreeNode> getChildren() {\n            return this.children;\n        }\n\n        ;\n\n        public TreeNode getChildNodeByToken(Token token) {\n            for (TreeNode childNode : this.children) {\n                if (childNode.getToken().equals(token)) {\n                    return childNode;\n                }\n            }\n            return null;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DumpTreeVisitor.java",
    "content": "package org.jmqtt.bus.subscription;\n\nimport io.netty.util.internal.StringUtil;\nimport org.jmqtt.bus.subscription.model.Subscription;\n\nclass DumpTreeVisitor implements CTrie.IVisitor<String> {\n\n    String s = \"\";\n\n    @Override\n    public void visit(CNode node, int deep) {\n        String indentTabs = indentTabs(deep);\n        s += indentTabs + (node.getToken() == null ? \"''\" : node.getToken().toString()) + prettySubscriptions(node) + \"\\n\";\n    }\n\n    private String prettySubscriptions(CNode node) {\n        if (node instanceof TNode) {\n            return \"TNode\";\n        }\n        if (node.subscriptions.isEmpty()) {\n            return StringUtil.EMPTY_STRING;\n        }\n        StringBuilder subScriptionsStr = new StringBuilder(\" ~~[\");\n        int counter = 0;\n        for (Subscription couple : node.subscriptions) {\n            subScriptionsStr\n                .append(\"{filter=\").append(couple.getTopic()).append(\", \")\n                .append(\"properties=\").append(couple.getProperties()).append(\", \")\n                .append(\"client='\").append(couple.getClientId()).append(\"'}\");\n            counter++;\n            if (counter < node.subscriptions.size()) {\n                subScriptionsStr.append(\";\");\n            }\n        }\n        return subScriptionsStr.append(\"]\").toString();\n    }\n\n    private String indentTabs(int deep) {\n        StringBuilder s = new StringBuilder();\n        if (deep > 0) {\n            s.append(\"    \");\n            for (int i = 0; i < deep - 1; i++) {\n                s.append(\"| \");\n            }\n            s.append(\"|-\");\n        }\n        return s.toString();\n    }\n\n    @Override\n    public String getResult() {\n        return s;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/INode.java",
    "content": "package org.jmqtt.bus.subscription;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic class INode {\n    private AtomicReference<CNode> mainNode = new AtomicReference<>();\n\n    INode(CNode mainNode) {\n        this.mainNode.set(mainNode);\n        if (mainNode instanceof TNode) { // this should never happen\n            throw new IllegalStateException(\"TNode should not be set on mainNnode\");\n        }\n    }\n\n    boolean compareAndSet(CNode old, CNode newNode) {\n        return mainNode.compareAndSet(old, newNode);\n    }\n\n    boolean compareAndSet(CNode old, TNode newNode) {\n        return mainNode.compareAndSet(old, newNode);\n    }\n\n    CNode mainNode() {\n        return this.mainNode.get();\n    }\n\n    boolean isTombed() {\n        return this.mainNode() instanceof TNode;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionCounterVisitor.java",
    "content": "package org.jmqtt.bus.subscription;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\nclass SubscriptionCounterVisitor implements CTrie.IVisitor<Integer> {\n\n    private AtomicInteger accumulator = new AtomicInteger(0);\n\n    @Override\n    public void visit(CNode node, int deep) {\n        accumulator.addAndGet(node.subscriptions.size());\n    }\n\n    @Override\n    public Integer getResult() {\n        return accumulator.get();\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionMatcher.java",
    "content": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\n\nimport java.util.Set;\n\n/**\n * Subscription tree\n * 订阅树处理\n *  TODO 1.支持共享订阅\n *  TODO 2.支持 p2p消息(jmqtt 特有)\n *  TODO 2.支持系统topic $SYS -> 需要定时采集本机节点等信息下发\n */\npublic interface SubscriptionMatcher {\n\n    /**\n     * add subscribe\n     * @return  true：new subscribe,dispatcher retain message\n     *           false：no need to dispatcher retain message\n     */\n    boolean subscribe(Subscription subscription);\n\n    boolean unSubscribe(String topic,String clientId);\n\n    /**\n     * 获取匹配该topic下的非共享订阅者\n     */\n    Set<Subscription> match(String topic);\n\n\n    int size();\n\n    String dumpTree();\n\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/TNode.java",
    "content": "/*\n * Copyright (c) 2012-2018 The original author or authors\n * ------------------------------------------------------\n * All rights reserved. This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License v1.0\n * and Apache License v2.0 which accompanies this distribution.\n *\n * The Eclipse Public License is available at\n * http://www.eclipse.org/legal/epl-v10.html\n *\n * The Apache License v2.0 is available at\n * http://www.opensource.org/licenses/apache2.0.php\n *\n * You may elect to redistribute this code under either of these licenses.\n */\npackage org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\n\nclass TNode extends CNode {\n\n    @Override\n    public Token getToken() {\n        throw new IllegalStateException(\"Can't be invoked on TNode\");\n    }\n\n    @Override\n    public void setToken(Token token) {\n        throw new IllegalStateException(\"Can't be invoked on TNode\");\n    }\n\n    @Override\n    INode childOf(Token token) {\n        throw new IllegalStateException(\"Can't be invoked on TNode\");\n    }\n\n    @Override\n    CNode copy() {\n        throw new IllegalStateException(\"Can't be invoked on TNode\");\n    }\n\n    @Override\n    public void add(INode newINode) {\n        throw new IllegalStateException(\"Can't be invoked on TNode\");\n    }\n\n    @Override\n    CNode addSubscription(Subscription newSubscription) {\n        throw new IllegalStateException(\"Can't be invoked on TNode\");\n    }\n\n    @Override\n    boolean containsOnly(String clientId) {\n        throw new IllegalStateException(\"Can't be invoked on TNode\");\n    }\n\n    @Override\n    public boolean contains(String clientId) {\n        throw new IllegalStateException(\"Can't be invoked on TNode\");\n    }\n\n    @Override\n    void removeSubscriptionsFor(String clientId) {\n        throw new IllegalStateException(\"Can't be invoked on TNode\");\n    }\n\n    @Override\n    boolean anyChildrenMatch(Token token) {\n        return false;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Token.java",
    "content": "package org.jmqtt.bus.subscription;\n\n\npublic class Token {\n\n    public static final Token EMPTY = new Token(\"\");\n    public static final Token MULTI = new Token(\"#\");\n    public static final Token SINGLE = new Token(\"+\");\n    final String name;\n\n    public Token(String s) {\n        name = s;\n    }\n\n    protected String name() {\n        return name;\n    }\n\n    protected boolean match(Token t) {\n        if (MULTI.equals(t) || SINGLE.equals(t)) {\n            return false;\n        }\n\n        if (MULTI.equals(this) || SINGLE.equals(this)) {\n            return true;\n        }\n\n        return equals(t);\n    }\n\n    @Override\n    public int hashCode() {\n        int hash = 7;\n        hash = 29 * hash + (this.name != null ? this.name.hashCode() : 0);\n        return hash;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == null) {\n            return false;\n        }\n        if (getClass() != obj.getClass()) {\n            return false;\n        }\n        final Token other = (Token) obj;\n        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {\n            return false;\n        }\n        return true;\n    }\n\n    @Override\n    public String toString() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Topic.java",
    "content": "package org.jmqtt.bus.subscription;\n\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport java.io.Serializable;\nimport java.text.ParseException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\npublic class Topic implements Serializable , Comparable<Topic> {\n\n    private static final Logger log = JmqttLogger.busLog;\n\n    private static final long serialVersionUID = 2438799283749822L;\n\n    private final String topic;\n\n    private transient List<Token> tokens;\n\n    private transient boolean valid;\n\n    /**\n     * Factory method\n     *\n     * @param s the topic string (es \"/a/b\").\n     * @return the created Topic instance.\n     * */\n    public static Topic asTopic(String s) {\n        return new Topic(s);\n    }\n\n    public Topic(String topic) {\n        this.topic = topic;\n    }\n\n    Topic(List<Token> tokens) {\n        this.tokens = tokens;\n        List<String> strTokens = tokens.stream().map(Token::toString).collect(Collectors.toList());\n        this.topic = String.join(\"/\", strTokens);\n        this.valid = true;\n    }\n\n    public List<Token> getTokens() {\n        if (tokens == null) {\n            try {\n                tokens = parseTopic(topic);\n                valid = true;\n            } catch (ParseException e) {\n                valid = false;\n                LogUtil.error(log,\"Error parsing the topic: {}, message: {}\", topic, e.getMessage());\n            }\n        }\n\n        return tokens;\n    }\n\n    private List<Token> parseTopic(String topic) throws ParseException {\n        if (topic.length() == 0) {\n            throw new ParseException(\"Bad format of topic, topic MUST be at least 1 character [MQTT-4.7.3-1] and \" +\n                    \"this was empty\", 0);\n        }\n        List<Token> res = new ArrayList<>();\n        String[] splitted = topic.split(\"/\");\n\n        if (splitted.length == 0) {\n            res.add(Token.EMPTY);\n        }\n\n        if (topic.endsWith(\"/\")) {\n            // Add a fictious space\n            String[] newSplitted = new String[splitted.length + 1];\n            System.arraycopy(splitted, 0, newSplitted, 0, splitted.length);\n            newSplitted[splitted.length] = \"\";\n            splitted = newSplitted;\n        }\n\n        for (int i = 0; i < splitted.length; i++) {\n            String s = splitted[i];\n            if (s.isEmpty()) {\n                // if (i != 0) {\n                // throw new ParseException(\"Bad format of topic, expetec topic name between\n                // separators\", i);\n                // }\n                res.add(Token.EMPTY);\n            } else if (s.equals(\"#\")) {\n                // check that multi is the last symbol\n                if (i != splitted.length - 1) {\n                    throw new ParseException(\n                            \"Bad format of topic, the multi symbol (#) has to be the last one after a separator\",\n                            i);\n                }\n                res.add(Token.MULTI);\n            } else if (s.contains(\"#\")) {\n                throw new ParseException(\"Bad format of topic, invalid subtopic name: \" + s, i);\n            } else if (s.equals(\"+\")) {\n                res.add(Token.SINGLE);\n            } else if (s.contains(\"+\")) {\n                throw new ParseException(\"Bad format of topic, invalid subtopic name: \" + s, i);\n            } else {\n                res.add(new Token(s));\n            }\n        }\n\n        return res;\n    }\n\n    public Token headToken() {\n        final List<Token> tokens = getTokens();\n        if (tokens.isEmpty()) {\n            return null;\n        }\n        return tokens.get(0);\n    }\n\n    public boolean isEmpty() {\n        final List<Token> tokens = getTokens();\n        return tokens == null || tokens.isEmpty();\n    }\n\n    public Topic exceptHeadToken() {\n        List<Token> tokens = getTokens();\n        if (tokens.isEmpty()) {\n            return new Topic(Collections.emptyList());\n        }\n        List<Token> tokensCopy = new ArrayList<>(tokens);\n        tokensCopy.remove(0);\n        return new Topic(tokensCopy);\n    }\n\n    public boolean isValid() {\n        if (tokens == null)\n            getTokens();\n\n        return valid;\n    }\n\n    public boolean match(Topic subscriptionTopic) {\n        List<Token> msgTokens = getTokens();\n        List<Token> subscriptionTokens = subscriptionTopic.getTokens();\n        int i = 0;\n        for (; i < subscriptionTokens.size(); i++) {\n            Token subToken = subscriptionTokens.get(i);\n            if (!Token.MULTI.equals(subToken) && !Token.SINGLE.equals(subToken)) {\n                if (i >= msgTokens.size()) {\n                    return false;\n                }\n                Token msgToken = msgTokens.get(i);\n                if (!msgToken.equals(subToken)) {\n                    return false;\n                }\n            } else {\n                if (Token.MULTI.equals(subToken)) {\n                    return true;\n                }\n            }\n        }\n        return i == msgTokens.size();\n    }\n\n    @Override\n    public String toString() {\n        return topic;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == null) {\n            return false;\n        }\n        if (getClass() != obj.getClass()) {\n            return false;\n        }\n        Topic other = (Topic) obj;\n\n        return Objects.equals(this.topic, other.topic);\n    }\n\n    @Override\n    public int hashCode() {\n        return topic.hashCode();\n    }\n\n    @Override\n    public int compareTo(Topic o) {\n        return topic.compareTo(o.topic);\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/model/Subscription.java",
    "content": "package org.jmqtt.bus.subscription.model;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 订阅关系\n */\npublic class Subscription {\n    private String             clientId;\n    private String             topic;\n    private Map<String,Object> properties;\n\n    public Subscription(String clientId,String topic){\n        this.clientId = clientId;\n        this.topic = topic;\n    }\n\n    public Subscription(String clientId,String topic,Map<String,Object> properties){\n        this.clientId = clientId;\n        this.topic = topic;\n        this.properties = properties;\n    }\n\n    public Subscription(Subscription origin){\n        this.clientId = origin.getClientId();\n        this.topic = origin.getTopic();\n        this.properties = origin.getProperties();\n    }\n\n    public <T> T getProperty(String key){\n        if (properties == null) {\n            return null;\n        }\n        return (T) properties.get(key);\n    }\n\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public void setClientId(String clientId) {\n        this.clientId = clientId;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public void setTopic(String topic) {\n        this.topic = topic;\n    }\n\n    public Map<String, Object> getProperties() {\n        return properties;\n    }\n\n    public void setProperties(Map<String, Object> properties) {\n        this.properties = properties;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        Subscription that = (Subscription) o;\n        return Objects.equals(clientId, that.clientId) &&\n                Objects.equals(topic, that.topic);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(clientId, topic);\n    }\n\n    @Override\n    public String toString() {\n        return \"Subscription{\" +\n                \"clientId='\" + clientId + '\\'' +\n                \", topic='\" + topic + '\\'' +\n                \", properties=\" + properties +\n                '}';\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/test/java/org/jmqtt/AppTest.java",
    "content": "package org.jmqtt;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Unit test for simple App.\n */\npublic class AppTest\n{\n    /**\n     * Rigorous Test :-)\n     */\n    @Test\n    public void shouldAnswerWithTrue()\n    {\n        assertTrue( true );\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcherMatchingTest.java",
    "content": "/*\n * Copyright (c) 2012-2018 The original author or authors\n * ------------------------------------------------------\n * All rights reserved. This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License v1.0\n * and Apache License v2.0 which accompanies this distribution.\n *\n * The Eclipse Public License is available at\n * http://www.eclipse.org/legal/epl-v10.html\n *\n * The Apache License v2.0 is available at\n * http://www.opensource.org/licenses/apache2.0.php\n *\n * You may elect to redistribute this code under either of these licenses.\n */\npackage org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Optional;\nimport java.util.Set;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.jmqtt.bus.subscription.CTrieTest.clientSubOnTopic;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nclass CTrieSubscriptionMatcherMatchingTest {\n\n    private CTrieSubscriptionMatcher sut;\n\n    @BeforeEach\n    public void setUp() {\n        sut = new CTrieSubscriptionMatcher();\n    }\n\n    @Test\n    public void testMatchSimple() {\n        Subscription slashSub = clientSubOnTopic(\"TempSensor1\", \"/\");\n        sut.subscribe(slashSub);\n        assertThat(sut.match(\"finance\")).isEmpty();\n\n        Subscription slashFinanceSub = clientSubOnTopic(\"TempSensor1\", \"/finance\");\n        sut.subscribe(slashFinanceSub);\n        assertThat(sut.match(\"finance\")).isEmpty();\n\n        assertThat(sut.match(\"/finance\")).contains(slashFinanceSub);\n        assertThat(sut.match(\"/\")).contains(slashSub);\n    }\n\n    @Test\n    public void testMatchSimpleMulti() {\n        Subscription anySub = clientSubOnTopic(\"TempSensor1\", \"#\");\n        sut.subscribe(anySub);\n        assertThat(sut.match(\"finance\")).contains(anySub);\n\n        Subscription financeAnySub = clientSubOnTopic(\"TempSensor1\", \"finance/#\");\n        sut.subscribe(financeAnySub);\n        assertThat(sut.match(\"finance\")).containsExactlyInAnyOrder(financeAnySub, anySub);\n    }\n\n    @Test\n    public void testMatchingDeepMulti_one_layer() {\n        Subscription anySub = clientSubOnTopic(\"AllSensor1\", \"#\");\n        Subscription financeAnySub = clientSubOnTopic(\"FinanceSensor\", \"finance/#\");\n        sut.subscribe(anySub);\n        sut.subscribe(financeAnySub);\n\n        // Verify\n        assertThat(sut.match(\"finance/stock\"))\n            .containsExactlyInAnyOrder(financeAnySub, anySub);\n        assertThat(sut.match(\"finance/stock/ibm\"))\n            .containsExactlyInAnyOrder(financeAnySub, anySub);\n//        System.out.println(sut.dumpTree());\n    }\n\n    @Test\n    public void testMatchingDeepMulti_two_layer() {\n        Subscription financeAnySub = clientSubOnTopic(\"FinanceSensor\", \"finance/stock/#\");\n        sut.subscribe(financeAnySub);\n\n        // Verify\n        assertThat(sut.match(\"finance/stock/ibm\")).containsExactly(financeAnySub);\n    }\n\n    @Test\n    public void testMatchSimpleSingle() {\n        Subscription anySub = clientSubOnTopic(\"AnySensor\", \"+\");\n        sut.subscribe(anySub);\n        assertThat(sut.match(\"finance\")).containsExactly(anySub);\n\n        Subscription financeOne = clientSubOnTopic(\"AnySensor\", \"finance/+\");\n        sut.subscribe(financeOne);\n        assertThat(sut.match(\"finance/stock\")).containsExactly(financeOne);\n    }\n\n    @Test\n    public void testMatchManySingle() {\n        Subscription manySub = clientSubOnTopic(\"AnySensor\", \"+/+\");\n        sut.subscribe(manySub);\n\n        // verify\n        assertThat(sut.match(\"/finance\")).contains(manySub);\n    }\n\n    @Test\n    public void testMatchSlashSingle() {\n        Subscription slashPlusSub = clientSubOnTopic(\"AnySensor\", \"/+\");\n        sut.subscribe(slashPlusSub);\n        Subscription anySub = clientSubOnTopic(\"AnySensor\", \"+\");\n        sut.subscribe(anySub);\n\n        // Verify\n        assertThat(sut.match(\"/finance\")).containsOnly(slashPlusSub);\n        assertThat(sut.match(\"/finance\")).doesNotContain(anySub);\n    }\n\n    @Test\n    public void testMatchManyDeepSingle() {\n        Subscription slashPlusSub = clientSubOnTopic(\"FinanceSensor1\", \"/finance/+/ibm\");\n        sut.subscribe(slashPlusSub);\n        Subscription slashPlusDeepSub = clientSubOnTopic(\"FinanceSensor2\", \"/+/stock/+\");\n        sut.subscribe(slashPlusDeepSub);\n\n        // Verify\n        assertThat(sut.match(\"/finance/stock/ibm\"))\n            .containsExactlyInAnyOrder(slashPlusSub, slashPlusDeepSub);\n    }\n\n    @Test\n    public void testMatchSimpleMulti_allTheTree() {\n        Subscription sub = clientSubOnTopic(\"AnySensor1\", \"#\");\n        sut.subscribe(sub);\n\n        assertThat(sut.match(\"finance\")).isNotEmpty();\n        assertThat(sut.match(\"finance/ibm\")).isNotEmpty();\n    }\n\n    @Test\n    public void rogerLightTopicMatches() {\n        assertMatch(\"foo/bar\", \"foo/bar\");\n        assertMatch(\"foo/bar\", \"foo/bar\");\n        assertMatch(\"foo/+\", \"foo/bar\");\n        assertMatch(\"foo/+/baz\", \"foo/bar/baz\");\n        assertMatch(\"foo/+/#\", \"foo/bar/baz\");\n        assertMatch(\"#\", \"foo/bar/baz\");\n\n        assertNotMatch(\"foo/bar\", \"foo\");\n        assertNotMatch(\"foo/+\", \"foo/bar/baz\");\n        assertNotMatch(\"foo/+/baz\", \"foo/bar/bar\");\n        assertNotMatch(\"foo/+/#\", \"fo2/bar/baz\");\n\n        assertMatch(\"#\", \"/foo/bar\");\n        assertMatch(\"/#\", \"/foo/bar\");\n        assertNotMatch(\"/#\", \"foo/bar\");\n\n        assertMatch(\"foo//bar\", \"foo//bar\");\n        assertMatch(\"foo//+\", \"foo//bar\");\n        assertMatch(\"foo/+/+/baz\", \"foo///baz\");\n        assertMatch(\"foo/bar/+\", \"foo/bar/\");\n    }\n\n    private void assertMatch(String s, String t) {\n        sut = new CTrieSubscriptionMatcher();\n\n        Subscription sub = clientSubOnTopic(\"AnySensor1\", s);\n        sut.subscribe(sub);\n\n        assertThat(sut.match(t)).isNotEmpty();\n    }\n\n    private void assertNotMatch(String subscription, String topic) {\n        sut = new CTrieSubscriptionMatcher();\n\n        Subscription sub = clientSubOnTopic(\"AnySensor1\", subscription);\n        sut.subscribe(sub);\n\n        assertThat(sut.match(topic)).isEmpty();\n    }\n\n    @Test\n    public void testOverlappingSubscriptions() {\n        Subscription genericSub = new Subscription(\"Sensor1\", \"a/+\", null);\n        sut.subscribe(genericSub);\n\n        Subscription specificSub = new Subscription(\"Sensor1\", \"a/b\", null);\n        sut.subscribe(specificSub);\n\n        //Exercise\n        final Set<Subscription> matchingForSpecific = sut.match(\"a/b\");\n\n        // Verify\n        assertThat(matchingForSpecific.size()).isEqualTo(1);\n    }\n\n    @Test\n    public void removeSubscription_withDifferentClients_subscribedSameTopic() {\n        Subscription slashSub = clientSubOnTopic(\"Sensor1\", \"/topic\");\n        sut.subscribe(slashSub);\n        Subscription slashSub2 = clientSubOnTopic(\"Sensor2\", \"/topic\");\n        sut.subscribe(slashSub2);\n\n        // Exercise\n        sut.unSubscribe(\"/topic\", slashSub2.getClientId());\n\n        // Verify\n        Subscription remainedSubscription = sut.match(\"/topic\").iterator().next();\n        assertThat(remainedSubscription.getClientId()).isEqualTo(slashSub.getClientId());\n        assertEquals(slashSub.getClientId(), remainedSubscription.getClientId());\n    }\n\n    @Test\n    public void removeSubscription_sameClients_subscribedSameTopic() {\n        Subscription slashSub = clientSubOnTopic(\"Sensor1\", \"/topic\");\n        sut.subscribe(slashSub);\n\n        // Exercise\n        sut.unSubscribe(\"/topic\", slashSub.getClientId());\n\n        // Verify\n        final Set<Subscription> matchingSubscriptions = sut.match(\"/topic\");\n        assertThat(matchingSubscriptions).isEmpty();\n    }\n\n    /*\n     * Test for Issue #49\n     */\n    @Test\n    public void duplicatedSubscriptionsWithDifferentQos() {\n        Subscription client2Sub = new Subscription(\"client2\", \"client/test/b\", null);\n        this.sut.subscribe(client2Sub);\n        Subscription client1SubQoS0 = new Subscription(\"client1\", \"client/test/b\", null);\n        this.sut.subscribe(client1SubQoS0);\n\n        Subscription client1SubQoS2 = new Subscription(\"client1\", \"client/test/b\", null);\n        this.sut.subscribe(client1SubQoS2);\n\n        // Verify\n        Set<Subscription> subscriptions = this.sut.match(\"client/test/b\");\n        assertThat(subscriptions).contains(client1SubQoS2);\n        assertThat(subscriptions).contains(client2Sub);\n\n        final Optional<Subscription> matchingClient1Sub = subscriptions\n            .stream()\n            .filter(s -> s.equals(client1SubQoS0))\n            .findFirst();\n        assertTrue(matchingClient1Sub.isPresent());\n        Subscription client1Sub = matchingClient1Sub.get();\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieTest.java",
    "content": "/*\n * Copyright (c) 2012-2018 The original author or authors\n * ------------------------------------------------------\n * All rights reserved. This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License v1.0\n * and Apache License v2.0 which accompanies this distribution.\n *\n * The Eclipse Public License is available at\n * http://www.eclipse.org/legal/epl-v10.html\n *\n * The Apache License v2.0 is available at\n * http://www.opensource.org/licenses/apache2.0.php\n *\n * You may elect to redistribute this code under either of these licenses.\n */\npackage org.jmqtt.bus.subscription;\n\nimport org.jmqtt.bus.subscription.model.Subscription;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Optional;\nimport java.util.Set;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class CTrieTest {\n\n    private CTrie sut;\n\n    @BeforeEach\n    public void setUp() {\n        sut = new CTrie();\n    }\n\n    @Test\n    public void testAddOnSecondLayerWithEmptyTokenOnEmptyTree() {\n        //Exercise\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/\"));\n\n        //Verify\n        final Optional<CNode> matchedNode = sut.lookup(new Topic(\"/\"));\n        assertTrue(matchedNode.isPresent(), \"Node on path / must be present\");\n        //verify structure, only root INode and the first CNode should be present\n        assertThat(this.sut.root.mainNode().subscriptions).isEmpty();\n        assertThat(this.sut.root.mainNode().allChildren()).isNotEmpty();\n\n        INode firstLayer = this.sut.root.mainNode().allChildren().get(0);\n        assertThat(firstLayer.mainNode().subscriptions).isEmpty();\n        assertThat(firstLayer.mainNode().allChildren()).isNotEmpty();\n\n        INode secondLayer = firstLayer.mainNode().allChildren().get(0);\n        assertThat(secondLayer.mainNode().subscriptions).isNotEmpty();\n        assertThat(secondLayer.mainNode().allChildren()).isEmpty();\n    }\n\n    @Test\n    public void testAddFirstLayerNodeOnEmptyTree() {\n        //Exercise\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/temp\"));\n\n        //Verify\n        final Optional<CNode> matchedNode = sut.lookup(new Topic(\"/temp\"));\n        assertTrue(matchedNode.isPresent(), \"Node on path /temp must be present\");\n        assertFalse(matchedNode.get().subscriptions.isEmpty());\n    }\n\n    @Test\n    public void testLookup() {\n        final Subscription existingSubscription = clientSubOnTopic(\"TempSensor1\", \"/temp\");\n        sut.addToTree(existingSubscription);\n\n        //Exercise\n        final Optional<CNode> matchedNode = sut.lookup(new Topic(\"/humidity\"));\n\n        //Verify\n        assertFalse(matchedNode.isPresent(), \"Node on path /humidity can't be present\");\n    }\n\n    @Test\n    public void testAddNewSubscriptionOnExistingNode() {\n        final Subscription existingSubscription = clientSubOnTopic(\"TempSensor1\", \"/temp\");\n        sut.addToTree(existingSubscription);\n\n        //Exercise\n        final Subscription newSubscription = clientSubOnTopic(\"TempSensor2\", \"/temp\");\n        sut.addToTree(newSubscription);\n\n        //Verify\n        final Optional<CNode> matchedNode = sut.lookup(new Topic(\"/temp\"));\n        assertTrue(matchedNode.isPresent(), \"Node on path /temp must be present\");\n        final Set<Subscription> subscriptions = matchedNode.get().subscriptions;\n        assertTrue(subscriptions.contains(newSubscription));\n    }\n\n    @Test\n    public void testAddNewDeepNodes() {\n        sut.addToTree(clientSubOnTopic(\"TempSensorRM\", \"/italy/roma/temp\"));\n        sut.addToTree(clientSubOnTopic(\"TempSensorFI\", \"/italy/firenze/temp\"));\n        sut.addToTree(clientSubOnTopic(\"HumSensorFI\", \"/italy/roma/humidity\"));\n        final Subscription happinessSensor = clientSubOnTopic(\"HappinessSensor\", \"/italy/happiness\");\n        sut.addToTree(happinessSensor);\n\n        //Verify\n        final Optional<CNode> matchedNode = sut.lookup(new Topic(\"/italy/happiness\"));\n        assertTrue(matchedNode.isPresent(), \"Node on path /italy/happiness must be present\");\n        final Set<Subscription> subscriptions = matchedNode.get().subscriptions;\n        assertTrue(subscriptions.contains(happinessSensor));\n    }\n\n    static Subscription clientSubOnTopic(String clientID, String topicName) {\n        return new Subscription(clientID, topicName, null);\n    }\n\n    @Test\n    public void givenTreeWithSomeNodeWhenRemoveContainedSubscriptionThenNodeIsUpdated() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/temp\"));\n\n        //Exercise\n        sut.removeFromTree(new Topic(\"/temp\"), \"TempSensor1\");\n\n        //Verify\n        final Optional<CNode> matchedNode = sut.lookup(new Topic(\"/temp\"));\n        assertFalse(matchedNode.isPresent(), \"Node on path /temp can't be present\");\n    }\n\n    @Test\n    public void givenTreeWithSomeNodeUnsubscribeAndResubscribeCleanTomb() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"test\"));\n        sut.removeFromTree(new Topic(\"test\"), \"TempSensor1\");\n\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"test\"));\n        assertEquals(1, sut.root.mainNode().allChildren().size());  // looking to see if TNode is cleaned up\n    }\n\n    @Test\n    public void givenTreeWithSomeNodeWhenRemoveMultipleTimes() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"test\"));\n\n        // make sure no TNode exceptions\n        sut.removeFromTree(new Topic(\"test\"), \"TempSensor1\");\n        sut.removeFromTree(new Topic(\"test\"), \"TempSensor1\");\n        sut.removeFromTree(new Topic(\"test\"), \"TempSensor1\");\n        sut.removeFromTree(new Topic(\"test\"), \"TempSensor1\");\n\n        //Verify\n        final Optional<CNode> matchedNode = sut.lookup(new Topic(\"/temp\"));\n        assertFalse(matchedNode.isPresent(), \"Node on path /temp can't be present\");\n    }\n\n    @Test\n    public void givenTreeWithSomeDeepNodeWhenRemoveMultipleTimes() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/test/me/1/2/3\"));\n\n        // make sure no TNode exceptions\n        sut.removeFromTree(new Topic(\"/test/me/1/2/3\"), \"TempSensor1\");\n        sut.removeFromTree(new Topic(\"/test/me/1/2/3\"), \"TempSensor1\");\n        sut.removeFromTree(new Topic(\"/test/me/1/2/3\"), \"TempSensor1\");\n\n        //Verify\n        final Optional<CNode> matchedNode = sut.lookup(new Topic(\"/temp\"));\n        assertFalse(matchedNode.isPresent(), \"Node on path /temp can't be present\");\n    }\n\n    @Test\n    public void givenTreeWithSomeNodeHierarchyWhenRemoveContainedSubscriptionThenNodeIsUpdated() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/temp/1\"));\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/temp/2\"));\n\n        //Exercise\n        sut.removeFromTree(new Topic(\"/temp/1\"), \"TempSensor1\");\n\n        sut.removeFromTree(new Topic(\"/temp/1\"), \"TempSensor1\");\n        final Set<Subscription> matchingSubs = sut.recursiveMatch(new Topic(\"/temp/2\"));\n\n        //Verify\n        final Subscription expectedMatchingsub = new Subscription(\"TempSensor1\", \"/temp/2\");\n        assertThat(matchingSubs).contains(expectedMatchingsub);\n    }\n\n    @Test\n    public void givenTreeWithSomeNodeHierarchWhenRemoveContainedSubscriptionSmallerThenNodeIsNotUpdated() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/temp/1\"));\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/temp/2\"));\n\n        //Exercise\n        sut.removeFromTree(new Topic(\"/temp\"), \"TempSensor1\");\n\n        final Set<Subscription> matchingSubs1 = sut.recursiveMatch(new Topic(\"/temp/1\"));\n        final Set<Subscription> matchingSubs2 = sut.recursiveMatch(new Topic(\"/temp/2\"));\n\n        //Verify\n        // not clear to me, but I believe /temp unsubscribe should not unsub you from downstream /temp/1 or /temp/2\n        final Subscription expectedMatchingsub1 = new Subscription(\"TempSensor1\", \"/temp/1\");\n        assertThat(matchingSubs1).contains(expectedMatchingsub1);\n        final Subscription expectedMatchingsub2 = new Subscription(\"TempSensor1\", \"/temp/2\");\n        assertThat(matchingSubs2).contains(expectedMatchingsub2);\n    }\n\n    @Test\n    public void givenTreeWithDeepNodeWhenRemoveContainedSubscriptionThenNodeIsUpdated() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/bah/bin/bash\"));\n\n        sut.removeFromTree(new Topic(\"/bah/bin/bash\"), \"TempSensor1\");\n\n        //Verify\n        final Optional<CNode> matchedNode = sut.lookup(new Topic(\"/bah/bin/bash\"));\n        assertFalse(matchedNode.isPresent(), \"Node on path /temp can't be present\");\n    }\n\n    @Test\n    public void testMatchSubscriptionNoWildcards() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"/temp\"));\n\n        //Exercise\n        final Set<Subscription> matchingSubs = sut.recursiveMatch(new Topic(\"/temp\"));\n\n        //Verify\n        final Subscription expectedMatchingsub = new Subscription(\"TempSensor1\", \"/temp\", null);\n        assertThat(matchingSubs).contains(expectedMatchingsub);\n    }\n\n    @Test\n    public void testRemovalInnerTopicOffRootSameClient() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"temp\"));\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"temp/1\"));\n\n        //Exercise\n        final Set<Subscription> matchingSubs1 = sut.recursiveMatch(new Topic(\"temp\"));\n        final Set<Subscription> matchingSubs2 = sut.recursiveMatch(new Topic(\"temp/1\"));\n\n        //Verify\n        final Subscription expectedMatchingsub1 = new Subscription(\"TempSensor1\", \"temp\", null);\n        final Subscription expectedMatchingsub2 = new Subscription(\"TempSensor1\", \"temp/1\", null);\n\n        assertThat(matchingSubs1).contains(expectedMatchingsub1);\n        assertThat(matchingSubs2).contains(expectedMatchingsub2);\n\n        sut.removeFromTree(new Topic(\"temp\"), \"TempSensor1\");\n\n        //Exercise\n        final Set<Subscription> matchingSubs3 = sut.recursiveMatch(new Topic(\"temp\"));\n        final Set<Subscription> matchingSubs4 = sut.recursiveMatch(new Topic(\"temp/1\"));\n\n        assertThat(matchingSubs3).doesNotContain(expectedMatchingsub1);\n        assertThat(matchingSubs4).contains(expectedMatchingsub2);\n    }\n\n    @Test\n    public void testRemovalInnerTopicOffRootDiffClient() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"temp\"));\n        sut.addToTree(clientSubOnTopic(\"TempSensor2\", \"temp/1\"));\n\n        //Exercise\n        final Set<Subscription> matchingSubs1 = sut.recursiveMatch(new Topic(\"temp\"));\n        final Set<Subscription> matchingSubs2 = sut.recursiveMatch(new Topic(\"temp/1\"));\n\n        //Verify\n        final Subscription expectedMatchingsub1 = new Subscription(\"TempSensor1\", \"temp\", null);\n        final Subscription expectedMatchingsub2 = new Subscription(\"TempSensor2\", \"temp/1\", null);\n\n        assertThat(matchingSubs1).contains(expectedMatchingsub1);\n        assertThat(matchingSubs2).contains(expectedMatchingsub2);\n\n        sut.removeFromTree(new Topic(\"temp\"), \"TempSensor1\");\n\n        //Exercise\n        final Set<Subscription> matchingSubs3 = sut.recursiveMatch(new Topic(\"temp\"));\n        final Set<Subscription> matchingSubs4 = sut.recursiveMatch(new Topic(\"temp/1\"));\n\n        assertThat(matchingSubs3).doesNotContain(expectedMatchingsub1);\n        assertThat(matchingSubs4).contains(expectedMatchingsub2);\n    }\n\n    @Test\n    public void testRemovalOuterTopicOffRootDiffClient() {\n        sut.addToTree(clientSubOnTopic(\"TempSensor1\", \"temp\"));\n        sut.addToTree(clientSubOnTopic(\"TempSensor2\", \"temp/1\"));\n\n        //Exercise\n        final Set<Subscription> matchingSubs1 = sut.recursiveMatch(new Topic(\"temp\"));\n        final Set<Subscription> matchingSubs2 = sut.recursiveMatch(new Topic(\"temp/1\"));\n\n        //Verify\n        final Subscription expectedMatchingsub1 = new Subscription(\"TempSensor1\", \"temp\", null);\n        final Subscription expectedMatchingsub2 = new Subscription(\"TempSensor2\", \"temp/1\", null);\n\n        assertThat(matchingSubs1).contains(expectedMatchingsub1);\n        assertThat(matchingSubs2).contains(expectedMatchingsub2);\n\n        sut.removeFromTree(new Topic(\"temp/1\"), \"TempSensor2\");\n\n        //Exercise\n        final Set<Subscription> matchingSubs3 = sut.recursiveMatch(new Topic(\"temp\"));\n        final Set<Subscription> matchingSubs4 = sut.recursiveMatch(new Topic(\"temp/1\"));\n\n        assertThat(matchingSubs3).contains(expectedMatchingsub1);\n        assertThat(matchingSubs4).doesNotContain(expectedMatchingsub2);\n    }\n}\n"
  },
  {
    "path": "jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/TopicTest.java",
    "content": "/*\n * Copyright (c) 2012-2018 The original author or authors\n * ------------------------------------------------------\n * All rights reserved. This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License v1.0\n * and Apache License v2.0 which accompanies this distribution.\n *\n * The Eclipse Public License is available at\n * http://www.eclipse.org/legal/epl-v10.html\n *\n * The Apache License v2.0 is available at\n * http://www.opensource.org/licenses/apache2.0.php\n *\n * You may elect to redistribute this code under either of these licenses.\n */\npackage org.jmqtt.bus.subscription;\n\nimport org.assertj.core.api.AbstractAssert;\nimport org.assertj.core.api.Assertions;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class TopicTest {\n\n    @Test\n    public void testParseTopic() {\n        assertThat(new Topic(\"finance/stock/ibm\")).containsToken(\"finance\", \"stock\", \"ibm\");\n\n        assertThat(new Topic(\"/finance/stock/ibm\")).containsToken(Token.EMPTY, \"finance\", \"stock\", \"ibm\");\n\n        assertThat(new Topic(\"/\")).containsToken(Token.EMPTY, Token.EMPTY);\n    }\n\n    @Test\n    public void testParseTopicMultiValid() {\n        assertThat(new Topic(\"finance/stock/#\")).containsToken(\"finance\", \"stock\", Token.MULTI);\n\n        assertThat(new Topic(\"#\")).containsToken(Token.MULTI);\n    }\n\n    @Test\n    public void testValidationProcess() {\n        // TopicMultiInTheMiddle\n        assertThat(new Topic(\"finance/#/closingprice\")).isInValid();\n\n        // MultiNotAfterSeparator\n        assertThat(new Topic(\"finance#\")).isInValid();\n\n        // TopicMultiNotAlone\n        assertThat(new Topic(\"/finance/#closingprice\")).isInValid();\n\n        // SingleNotAferSeparator\n        assertThat(new Topic(\"finance+\")).isInValid();\n\n        assertThat(new Topic(\"finance/+\")).isValid();\n    }\n\n    @Test\n    public void testParseTopicSingleValid() {\n        assertThat(new Topic(\"finance/stock/+\")).containsToken(\"finance\", \"stock\", Token.SINGLE);\n\n        assertThat(new Topic(\"+\")).containsToken(Token.SINGLE);\n\n        assertThat(new Topic(\"finance/+/ibm\")).containsToken(\"finance\", Token.SINGLE, \"ibm\");\n    }\n\n    @Test\n    public void testMatchTopics_simple() {\n        assertThat(new Topic(\"/\")).matches(\"/\");\n        assertThat(new Topic(\"/finance\")).matches(\"/finance\");\n    }\n\n    @Test\n    public void testMatchTopics_multi() {\n        assertThat(new Topic(\"finance\")).matches(\"#\");\n        assertThat(new Topic(\"finance\")).matches(\"finance/#\");\n        assertThat(new Topic(\"finance/stock\")).matches(\"finance/#\");\n        assertThat(new Topic(\"finance/stock/ibm\")).matches(\"finance/#\");\n    }\n\n    @Test\n    public void testMatchTopics_single() {\n        assertThat(new Topic(\"finance\")).matches(\"+\");\n        assertThat(new Topic(\"finance/stock\")).matches(\"finance/+\");\n        assertThat(new Topic(\"finance\")).doesNotMatch(\"finance/+\");\n        assertThat(new Topic(\"/finance\")).matches(\"/+\");\n        assertThat(new Topic(\"/finance\")).doesNotMatch(\"+\");\n        assertThat(new Topic(\"/finance\")).matches(\"+/+\");\n        assertThat(new Topic(\"/finance/stock/ibm\")).matches(\"/finance/+/ibm\");\n        assertThat(new Topic(\"/\")).matches(\"+/+\");\n        assertThat(new Topic(\"sport/\")).matches(\"sport/+\");\n        assertThat(new Topic(\"/finance/stock\")).doesNotMatch(\"+\");\n    }\n\n    @Test\n    public void rogerLightMatchTopics() {\n        assertThat(new Topic(\"foo/bar\")).matches(\"foo/bar\");\n        assertThat(new Topic(\"foo/bar\")).matches(\"foo/+\");\n        assertThat(new Topic(\"foo/bar/baz\")).matches(\"foo/+/baz\");\n        assertThat(new Topic(\"foo/bar/baz\")).matches(\"foo/+/#\");\n        assertThat(new Topic(\"foo/bar/baz\")).matches(\"#\");\n\n        assertThat(new Topic(\"foo\")).doesNotMatch(\"foo/bar\");\n        assertThat(new Topic(\"foo/bar/baz\")).doesNotMatch(\"foo/+\");\n        assertThat(new Topic(\"foo/bar/bar\")).doesNotMatch(\"foo/+/baz\");\n        assertThat(new Topic(\"fo2/bar/baz\")).doesNotMatch(\"foo/+/#\");\n\n        assertThat(new Topic(\"/foo/bar\")).matches(\"#\");\n        assertThat(new Topic(\"/foo/bar\")).matches(\"/#\");\n        assertThat(new Topic(\"foo/bar\")).doesNotMatch(\"/#\");\n\n        assertThat(new Topic(\"foo//bar\")).matches(\"foo//bar\");\n        assertThat(new Topic(\"foo//bar\")).matches(\"foo//+\");\n        assertThat(new Topic(\"foo///baz\")).matches(\"foo/+/+/baz\");\n        assertThat(new Topic(\"foo/bar/\")).matches(\"foo/bar/+\");\n    }\n\n    @Test\n    public void exceptHeadToken() {\n        assertEquals(Topic.asTopic(\"token\"), Topic.asTopic(\"/token\").exceptHeadToken());\n        assertEquals(Topic.asTopic(\"a/b\"), Topic.asTopic(\"/a/b\").exceptHeadToken());\n    }\n\n    public static TopicAssert assertThat(Topic topic) {\n        return new TopicAssert(topic);\n    }\n\n    static class TopicAssert extends AbstractAssert<TopicAssert, Topic> {\n\n        TopicAssert(Topic actual) {\n            super(actual, TopicAssert.class);\n        }\n\n        public TopicAssert matches(String topic) {\n            Assertions.assertThat(actual.match(new Topic(topic))).isTrue();\n\n            return myself;\n        }\n\n        public TopicAssert doesNotMatch(String topic) {\n            Assertions.assertThat(actual.match(new Topic(topic))).isFalse();\n\n            return myself;\n        }\n\n        public TopicAssert containsToken(Object... tokens) {\n            Assertions.assertThat(actual.getTokens()).containsExactly(asArray(tokens));\n\n            return myself;\n        }\n\n        private Token[] asArray(Object... l) {\n            Token[] tokens = new Token[l.length];\n            for (int i = 0; i < l.length; i++) {\n                Object o = l[i];\n                if (o instanceof Token) {\n                    tokens[i] = (Token) o;\n                } else {\n                    tokens[i] = new Token(o.toString());\n                }\n            }\n\n            return tokens;\n        }\n\n        public TopicAssert isValid() {\n            Assertions.assertThat(actual.isValid()).isTrue();\n\n            return myself;\n        }\n\n        public TopicAssert isInValid() {\n            Assertions.assertThat(actual.isValid()).isFalse();\n\n            return myself;\n        }\n    }\n}\n"
  },
  {
    "path": "jmqtt-doc/README.md",
    "content": "官网搭建指导文件\n\n1. clone本项目\n2. 安装npm或yarn，具体参考：https://www.vuepress.cn/guide/getting-started.html\n3. 执行：yarn add -D vuepress\n4. 本地启动：yarn dev\n5. 构建：yarn build\n\n\n"
  },
  {
    "path": "jmqtt-doc/docs/README.md",
    "content": "## 快速上手\n\n### 安装主题\n\n1. 下载 [release](https://github.com/Cicizz/jmqtt/releases)(3.x以上版本) 或`clone`本项目：\n2. 在jmqtt根目录执行：：\n```bash\nmvn -Ppackage-all -DskipTests clean install -U\n```\n3. 配置相应的配置文件,初始化db的sql文件:`/jmqtt-broker/resources/conf`目录下\n4. 执行启动命令：`java -jar jmqtt-broker-3.0.0.jar -h ${conf文件目录}` -h后是配置文件目录，里面需要包含jmqtt.properties和log4j2.xml等配置文件\n\n### 测试\n下载客户端：[mqtt客户端](https://mqttx.app/cn/)\n或 直接使用websocket测试：`/jmqtt/jmqtt-examples`\n\n\n\n\n<p>&nbsp; </p>  \n\n[我也想为贡献者之一？](https://github.com/Cicizz/jmqtt/pulls)\n\n<p>&nbsp; </p> \n\n\n<Msg />\n"
  },
  {
    "path": "jmqtt-doc/docs/TEST_REPORT.md",
    "content": "# Jmqtt 最新版功能及性能测试报告\n线上版连接：https://www.yuque.com/tristan-ku8np/zze/xghq80\n最新版链接：[https://github.com/Cicizz/jmqtt](https://github.com/Cicizz/jmqtt)\n\n# 一、目标\n\n1. 检测Jmqtt功能及性能运行情况\n1. 为使用者提供参考\n\n说明：以下测试为Jmqtt作者闲暇之时进行的测试，仅供参考，再线上使用Jmqtt时，请自行测试，感谢 朱霄 同学提供的服务器等测试资源\n\n# 二、测试机配置\n\n|                 | 数量        | 操作系统        | 配置                          |\n| --------------- | ----------- | --------------- | ----------------------------- |\n| Jmqtt运行服务器 | 2台（集群） | linux centos 7  | 8C16G                         |\n| Jmqtt压测机     | 2台         | linux centos 7  | 8C16G                         |\n| Mysql           | 单库单表    | mysql5.7        | 阿里云rds基础版：ESSD OL1云盘 |\n| SLB             | 1           | 支持4层负载均衡 | 支持20万长连接                |\n\n测试脚本：\n\n1. jmeter\n1. emqx-bench\n\n# 三、功能测试报告\n\n| 功能项                      | 是否满足 | 备注                                                      |\n| --------------------------- | -------- | --------------------------------------------------------- |\n| 集群连接                    | ✅        |                                                           |\n| 集群cleansession为false连接 | ✅        |                                                           |\n| 设备在集群互发消息          | ✅        |                                                           |\n| retain消息                  | ✅        |                                                           |\n| will消息                    | ✅        |                                                           |\n| qos0                        | ✅        |                                                           |\n| qos1                        | ✅        |                                                           |\n| qos2                        | ✅        |                                                           |\n| 离线消息                    | ✅        | 不限离线消息数量                                          |\n| 设备信息持久化              | ✅        | 见sql表：jmqtt_session                                    |\n| 订阅关系持久化              | ✅        | 见sql表：jmqtt_subscription                               |\n| 消息持久化                  | ✅        | 见sql表：jmqtt_message                                    |\n| 集群事件消息                | ✅        | 包含集群间转发消息，连接事件，见sql表：jmqtt_cluser_event |\n| 设备消息接收状态            | ✅        | 见sql表：jmqtt_client_inbox                               |\n| 监控topic                   | ❎        | 不支持，需自行实现                                        |\n| 各个消息桥接                | ❎        | 不支持，需自行实现                                        |\n| 规则引擎                    | ❎        | 不支持，需自行实现                                        |\n\n# 四、单机性能测试报告\n\n## 4.1 连接数性能报告\n\n连接数单机测试达到10W级连接，连接tps1000，未出现报错，\n注意：因测试机资源问题，未压测到上限。详细截图报告见下图\n\n### 4.1.1 连接数200，连接持续3min\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746010873-3806f744-eebd-4a06-b638-a5c9030a913a.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=111&id=u3f625a5c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=222&originWidth=1704&originalType=binary&ratio=1&rotation=0&showTitle=false&size=129250&status=done&style=none&taskId=u1e953a0b-157d-45d3-9bd0-886c7d1982c&title=&width=852)\n\n### 4.1.2 连接数1000，连接持续3min\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746046914-ac14a7be-c248-4dfd-9244-3b3b6bf50bfd.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=114&id=ude67f41c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=228&originWidth=1754&originalType=binary&ratio=1&rotation=0&showTitle=false&size=113132&status=done&style=none&taskId=u96d8e2f0-5487-4386-a565-467f58186d3&title=&width=877)\n\n### 4.1.3 连接数2000，连接持续3min\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746065295-8a158413-36ac-4e88-b67c-1c6f6f565bc7.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=313&id=u67a9620e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=626&originWidth=2236&originalType=binary&ratio=1&rotation=0&showTitle=false&size=390748&status=done&style=none&taskId=u44a649b4-aa01-4ad7-906e-e55f1b7f21e&title=&width=1118)\n\n### 4.1.4 连接数5000，连接持续3min\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746088650-4e2c8fab-4185-41db-b406-89571492fb79.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=279&id=u8a9a70e7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=558&originWidth=2192&originalType=binary&ratio=1&rotation=0&showTitle=false&size=257112&status=done&style=none&taskId=u68f785bb-6e9b-4668-9fa0-23ce4914e1a&title=&width=1096)\n\n### 4.1.5 连接数1W，连接持续3min\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746108655-757645fb-6951-4dc4-ac6e-97946f35e980.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=42&id=u4b3542f2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=84&originWidth=945&originalType=binary&ratio=1&rotation=0&showTitle=false&size=49950&status=done&style=none&taskId=ue2cf34cc-2cef-4ef0-88db-3826ecda806&title=&width=472.5)\n\n### 4.1.6 连接数2W，连接持续3min\n\n超过2W采用开源emqx-bench进行性能压测\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746146826-bc37501b-4b08-4566-a461-02ced5d72cfe.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=23&id=ue6ccf234&margin=%5Bobject%20Object%5D&name=image.png&originHeight=46&originWidth=634&originalType=binary&ratio=1&rotation=0&showTitle=false&size=22600&status=done&style=none&taskId=u33aaf8db-a098-40a5-8036-e6f2065e970&title=&width=317)\n\n### 4.1.7 连接数5W，连接持续10min\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746200835-34532619-4370-4a54-9f57-607b6eb20916.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=20&id=ub9254472&margin=%5Bobject%20Object%5D&name=image.png&originHeight=39&originWidth=738&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18207&status=done&style=none&taskId=ud0a3ce29-ca11-482e-83f5-71b4c49f78c&title=&width=369)\n\n### 4.1.8 连接数10W，连接持续10min\n\n压到10W后，未持续压测，尚未压到连接数上限，两台测试机截图如下：\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746254255-83829c1c-0ebc-46cb-a252-68febb726b2f.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=24&id=u55c0540d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=48&originWidth=1491&originalType=binary&ratio=1&rotation=0&showTitle=false&size=43616&status=done&style=none&taskId=ue7fdb9c3-50d5-4e16-9f5e-c70b6b430b0&title=&width=745.5)\n服务器load截图：\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746280704-1784ae08-ffeb-4fdd-94f1-98206c444c54.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=70&id=udcb79ca0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=140&originWidth=716&originalType=binary&ratio=1&rotation=0&showTitle=false&size=71157&status=done&style=none&taskId=u6a9c56d6-706e-4f0e-9813-5c3bf2a1adb&title=&width=358)\n\n## 4.2 发送消息性能报告\n\n### 4.2.1 设备连接2W，再启动1000连接持续发送消息\n\n消息大小：256byte\nqos：0\n每隔10ms发送1条消息\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747253961-4180a124-1185-4aac-8442-781b044fb609.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=251&id=ua5ae4796&margin=%5Bobject%20Object%5D&name=image.png&originHeight=302&originWidth=438&originalType=binary&ratio=1&rotation=0&showTitle=false&size=104022&status=done&style=none&taskId=u06b96fe2-eac6-404c-a96b-5f353408b1f&title=&width=364)\n对比emq服务（没有2W长连接设备保持）：broker.emqx.io\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747299016-50f125b1-fe67-4379-8858-37ac0cdec74b.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=u73d17b7e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=255&originWidth=459&originalType=binary&ratio=1&rotation=0&showTitle=false&size=85760&status=done&style=none&taskId=u61141867-fb4f-4047-ae97-9400e851490&title=&width=364.5)\n\n### 4.2.2 设备连接2W，再启动200连接持续发送消息\n\n消息大小：256byte\nqos：1\n每隔10ms发送1条消息\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747478891-b7ec2f4a-012d-4c9d-9dbc-f2bf7112cae8.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=ue0cc314a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=284&originWidth=403&originalType=binary&ratio=1&rotation=0&showTitle=false&size=91808&status=done&style=none&taskId=u236295c7-4c40-4247-b975-e31b2558b3b&title=&width=287.5)\n\n### 4.2.3 设备连接2W，再启动200连接持续发送消息\n\n消息大小：100byte\nqos：1\n每隔10ms发送1条消息\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747586254-5d5b40f9-4cef-4633-9a3d-94ab46837bd6.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=176&id=uf4feec24&margin=%5Bobject%20Object%5D&name=image.png&originHeight=301&originWidth=561&originalType=binary&ratio=1&rotation=0&showTitle=false&size=99190&status=done&style=none&taskId=u7428afd4-7a26-4449-9433-47fc1af1230&title=&width=327.5)\n\n## 4.3 订阅性能报告\n\n### 4.3.1 启动2W个设备，订阅2W个topic\n\n### ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747707443-c70722d5-e4f6-4674-a9ab-3ba8490899d9.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=255&id=u22f47aba&margin=%5Bobject%20Object%5D&name=image.png&originHeight=333&originWidth=285&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80501&status=done&style=none&taskId=u39a72cd5-3b32-4b86-9490-28e3815563d&title=&width=218.5) \n\n# 五、集群性能测试报告\n\n## 5.1 连接数性能报告\n\nJmqtt服务器两台，设备连接数10W，未压测到上限\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748056231-f1d72eeb-0dac-4e33-8ba2-0111e9f11387.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=314&id=u09e9c4d3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=628&originWidth=1928&originalType=binary&ratio=1&rotation=0&showTitle=false&size=589189&status=done&style=none&taskId=ue4369d89-e4b7-4dac-979b-4514e76acdb&title=&width=964)\n\n## 5.2 发送消息性能报告\n\n发送消息强依赖db进行保存，性能瓶颈在db侧，故tps上不去\n\n### 5.2.1 设备连接2W，启动200连接持续发送消息\n\n消息大小：256byte\nqos：1\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748182652-85094ef1-740e-4067-85ac-1d089961994b.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=271&id=u5a016123&margin=%5Bobject%20Object%5D&name=image.png&originHeight=542&originWidth=658&originalType=binary&ratio=1&rotation=0&showTitle=false&size=239858&status=done&style=none&taskId=udb8c34ff-f596-4ef8-bedc-5343205e3ab&title=&width=329)\n\n### 5.2.2 设备连接2W，启动200连接持续发送消息\n\n消息大小：100byte\nqos：1\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748243207-6bd4b28d-818c-4f71-9759-11edfe7ffbac.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=251&id=ud1655849&margin=%5Bobject%20Object%5D&name=image.png&originHeight=502&originWidth=694&originalType=binary&ratio=1&rotation=0&showTitle=false&size=230671&status=done&style=none&taskId=u9e328faa-fd3f-48f8-884c-7104ba74757&title=&width=347)\n\n## 5.3 订阅性能报告\n\n### 5.3.1 启动2W个设备，订阅2W个topic\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748307769-aba52973-ef70-4c53-b1d1-b0e8b81ab18f.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=ua71378ae&margin=%5Bobject%20Object%5D&name=image.png&originHeight=406&originWidth=920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=143298&status=done&style=none&taskId=u0c19253a-4c2b-4ecc-adef-b7ea8893f21&title=&width=460)\n\n### 5.3.2 启动5W个设备，订阅5W个topic\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748324754-a92522f8-395d-4f70-a2f1-ff143bb3bb37.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=275&id=u048d5dd1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=550&originWidth=718&originalType=binary&ratio=1&rotation=0&showTitle=false&size=183115&status=done&style=none&taskId=ue28001b0-b3a9-407d-9c6d-1fc15a813d3&title=&width=359)\n\n# 六、性能测试说明\n\n## 6.1 关于连接数\n\n1. 单机和集群都未压测到上线，受限于时间和测试机问题\n1. 实际使用时，单机连接数不要超过5w，方式服务器重启时，大量重连请求导致不可预知的问题\n\n## 6.2 关于消息发送tps\n\n1. 整体看消息tps与emq的测试服务器消息tps差不多\n1. 为什么集群tps上不去？\n   1. 因为消息保存强依赖mysql进行存储，mysql存储tps已达上限，这也是消息发送的可优化项\n\n## 6.3 关于订阅\n\n1. 目前订阅强依赖db存储，不存在订阅关系丢失的问题\n1. 本地采用tri树进行订阅关系的管理\n\n## 6.3 Jmqtt性能可优化项指南\n\n1. 升级mysql\n1. 集群事件转发器 用 mq替代（kafka或其他的mq都可以），减少集群服务器从db long pull的模式\n1. 消息存储采用其他存储中间件，例如时序数据库，甚至kafka都行\n\n# 七、测试常见问题\n\n## 7.1 测试机需要修改端口限制，否则无法启动5W长连接\n\nlinux centos7默认限制了端口可用范围，需要修改一下，不然连接数无法达到5w\n查看端口范围：cat /proc/sys/net/ipv4/ip_local_port_range\n\n## 7.2 jmqtt服务器需要修改文件句柄数\n\nlinux 万物皆文件，需要修改文件句柄数，否则无法支持那么大的长连接\nlinux默认为65535个文件句柄数，需要修改两个地方：\nulimit -n 和vim /etc/security/limits.conf\n\n## 7.3 jmqtt集群的负载均衡需要升级\n\nmqtt协议的复杂均衡需要4层的负载均衡代理，\n默认购买的SLB一般只支持5W长连接，故需要升级\n\n# 八、附：Jmqtt启动问题\n\n1. 目前jmqtt尽量减少各种依赖，代码简单，很容易进行二次开发和开箱即用\n1. 请使用最新发布版本或master 分支代码\n1. 建议从源码构建\n1. 本地启动，直接在BrokerStartup执行main方法\n\n## 8.1 结构介绍\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749307002-ed2ad593-44d3-4dac-a62e-f9db215e5fe5.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=312&id=u7cac4a06&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1290&originWidth=1886&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1369802&status=done&style=none&taskId=ua523a209-b0b0-4450-965a-f60ff3ca631&title=&width=456)\n\n## 8.2 在db库中初始化好脚本\nt默认使用的是mysql驱动，依赖其他db需要自行修改\n\n1. 在自己的库中，执行jmqtt.sql\n1. 执行后如截图所示：\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749458236-93fdeb96-9e87-40d7-9152-cdfd8dfbe1bb.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=309&id=ud2bd07e1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=662&originWidth=594&originalType=binary&ratio=1&rotation=0&showTitle=false&size=150608&status=done&style=none&taskId=u74608728-bee1-4b58-acc8-e64fb9d1b8d&title=&width=277)\n\n## 8.3 打包\n\n在broker模块下，执行 ：mvn -Ppackage-all -DskipTests clean install -U  \n打包后：\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749594691-b2ac7265-1947-4694-b834-8285f5acc1da.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=360&id=u9e9f6e8c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=720&originWidth=1374&originalType=binary&ratio=1&rotation=0&showTitle=false&size=640584&status=done&style=none&taskId=ua743bfc9-2d04-4761-8c85-d8a5b18b6f5&title=&width=687)\n\n## 8.4 修改配置文件\n\n如截图：这里修改为自己的db连接串\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749722171-23d6363a-9bcd-425a-a682-c36d878cdca2.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=278&id=u60f8b443&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1242&originWidth=2758&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1430587&status=done&style=none&taskId=u54a006e0-9619-468f-9deb-2a45f16b9de&title=&width=617)\n\n## 8.5 上传资源到服务器\n\n1. 将jar，conf下的资源，bin下的脚本都上传到服务器：\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749874476-b29816f0-2c04-4ab5-bac2-2f771e8c4130.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=80&id=u809bdae2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=160&originWidth=1314&originalType=binary&ratio=1&rotation=0&showTitle=false&size=131296&status=done&style=none&taskId=u3491c4a5-645f-4ae6-bc72-10edd16469b&title=&width=657)\n其中 config为conf下的配置文件\n\n2. 执行启动命令：./runbroker.sh jmqtt-broker-3.0.0.jar -h config/\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749926168-c1ff93fb-523f-4569-b6e6-231db8d3c50c.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=62&id=u440600e3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=124&originWidth=1226&originalType=binary&ratio=1&rotation=0&showTitle=false&size=91843&status=done&style=none&taskId=ue93dc841-1df2-4ae5-bbf9-3cf8117dfc0&title=&width=613)\n\n3. 查看启动日志：\n   1. cd jmqttlogs\n   1. tailf -200 brokerLog.log : 显示如下截图说明启动成功\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749978459-d4df9171-0c11-4e05-a877-dc3eeeed2fa8.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=168&id=ucb54e4fc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=336&originWidth=2268&originalType=binary&ratio=1&rotation=0&showTitle=false&size=259176&status=done&style=none&taskId=uafca868b-afe8-45a8-b2bb-42fa7003c9e&title=&width=1134)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642750011254-87d99be8-40f4-40a8-bbaf-8a3cb67f8f14.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=67&id=u9268067d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=134&originWidth=1308&originalType=binary&ratio=1&rotation=0&showTitle=false&size=61787&status=done&style=none&taskId=ub9ebb91e-a64c-4e63-8601-48e82931c6e&title=&width=654)\n\njmqt\n"
  },
  {
    "path": "jmqtt-doc/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" 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    <parent>\n        <artifactId>jmqtt</artifactId>\n        <groupId>org.jmqtt</groupId>\n        <version>3.0.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>jmqtt-doc</artifactId>\n\n    <name>jmqtt-doc</name>\n\n\n    <properties>\n\n    </properties>\n\n    <dependencies>\n\n    </dependencies>\n</project>\n"
  },
  {
    "path": "jmqtt-example/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" 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    <parent>\n        <artifactId>jmqtt</artifactId>\n        <groupId>org.jmqtt</groupId>\n        <version>3.0.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>jmqtt-example</artifactId>\n    <name>jmqtt-example</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <maven.compiler.source>1.8</maven.compiler.source>\n        <maven.compiler.target>1.8</maven.compiler.target>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.eclipse.paho</groupId>\n            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>\n            <version>1.2.5</version>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/java/Consumer.java",
    "content": "package org.jmqtt.java;\n\nimport org.eclipse.paho.client.mqttv3.*;\nimport org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;\n\npublic class Consumer {\n    private static final String broker = \"tcp://127.0.0.1:1883\";\n    private static final String topic = \"MQTT/TOPIC\";\n    private static final String clientId = \"MQTT_SUB_CLIENT\";\n\n    public static void main(String[] args) throws MqttException {\n        MqttClient subClient = getMqttClient();\n        subClient.setCallback(new MqttCallback() {\n            @Override\n            public void connectionLost(Throwable throwable) {\n                System.out.println(\"Connect lost,do some thing to solve it\");\n            }\n\n            @Override\n            public void messageArrived(String s, MqttMessage mqttMessage) {\n                System.out.println(\"From topic: \" + s);\n                System.out.println(\"Message content: \" + new String(mqttMessage.getPayload()));\n            }\n\n            @Override\n            public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {\n                System.out.println(\"deliveryComplete\");\n            }\n\n        });\n        subClient.subscribe(topic);\n    }\n\n\n    private static MqttClient getMqttClient() {\n        try {\n            MqttClient pubClient = new MqttClient(broker, clientId, new MemoryPersistence());\n            MqttConnectOptions connectOptions = new MqttConnectOptions();\n            connectOptions.setCleanSession(false);\n            System.out.println(\"Connecting to broker: \" + broker);\n            pubClient.connect(connectOptions);\n            return pubClient;\n        } catch (MqttException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/java/Producer.java",
    "content": "package org.jmqtt.java;\n\nimport org.eclipse.paho.client.mqttv3.MqttClient;\nimport org.eclipse.paho.client.mqttv3.MqttConnectOptions;\nimport org.eclipse.paho.client.mqttv3.MqttException;\nimport org.eclipse.paho.client.mqttv3.MqttMessage;\nimport org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;\n\npublic class Producer {\n    private static final String broker = \"tcp://8.142.122.137:1883\";\n    private static final String content = \"Message from MqttProducer\";\n    private static final int qos = 1;\n    private static final String topic = \"MQTT/TOPIC\";\n    private static final String clientId = \"MQTT_PUB_CLIENT\";\n\n    public static void main(String[] args) throws MqttException, InterruptedException {\n        MqttClient pubClient = getMqttClient();\n        for (int i = 0; i < 3; i++) {\n            MqttMessage mqttMessage = getMqttMessage();\n            pubClient.publish(topic, mqttMessage);\n            System.out.println(\"Send message success.\");\n        }\n    }\n\n    private static MqttMessage getMqttMessage() {\n        MqttMessage mqttMessage = new MqttMessage(content.getBytes());\n        mqttMessage.setQos(qos);\n        return mqttMessage;\n    }\n\n    private static MqttClient getMqttClient() {\n        try {\n            MqttClient pubClient = new MqttClient(broker, clientId, new MemoryPersistence());\n            MqttConnectOptions connectOptions = new MqttConnectOptions();\n            connectOptions.setWill(\"lwt\", \"this is a will message\".getBytes(), 1, false);\n            connectOptions.setCleanSession(false);\n            System.out.println(\"Connecting to broker: \" + broker);\n            pubClient.connect(connectOptions);\n            return pubClient;\n        } catch (MqttException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/websocket/paho-mqtt-min.js",
    "content": "/*******************************************************************************\n * Copyright (c) 2013, 2014 IBM Corp.\n *\n * All rights reserved. This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License v1.0\n * and Eclipse Distribution License v1.0 which accompany this distribution.\n *\n * The Eclipse Public License is available at\n *    http://www.eclipse.org/legal/epl-v10.html\n * and the Eclipse Distribution License is available at\n *   http://www.eclipse.org/org/documents/edl-v10.php.\n *\n *******************************************************************************/\n\n\"undefined\"===typeof Paho&&(Paho={});\nPaho.MQTT=function(u){function y(a,b,c){b[c++]=a>>8;b[c++]=a%256;return c}function r(a,b,c,h){h=y(b,c,h);F(a,c,h);return h+b}function m(a){for(var b=0,c=0;c<a.length;c++){var h=a.charCodeAt(c);2047<h?(55296<=h&&56319>=h&&(c++,b++),b+=3):127<h?b+=2:b++}return b}function F(a,b,c){for(var h=0;h<a.length;h++){var e=a.charCodeAt(h);if(55296<=e&&56319>=e){var d=a.charCodeAt(++h);if(isNaN(d))throw Error(f(g.MALFORMED_UNICODE,[e,d]));e=(e-55296<<10)+(d-56320)+65536}127>=e?b[c++]=e:(2047>=e?b[c++]=e>>6&31|\n    192:(65535>=e?b[c++]=e>>12&15|224:(b[c++]=e>>18&7|240,b[c++]=e>>12&63|128),b[c++]=e>>6&63|128),b[c++]=e&63|128)}return b}function G(a,b,c){for(var h=\"\",e,d=b;d<b+c;){e=a[d++];if(!(128>e)){var p=a[d++]-128;if(0>p)throw Error(f(g.MALFORMED_UTF,[e.toString(16),p.toString(16),\"\"]));if(224>e)e=64*(e-192)+p;else{var t=a[d++]-128;if(0>t)throw Error(f(g.MALFORMED_UTF,[e.toString(16),p.toString(16),t.toString(16)]));if(240>e)e=4096*(e-224)+64*p+t;else{var l=a[d++]-128;if(0>l)throw Error(f(g.MALFORMED_UTF,\n    [e.toString(16),p.toString(16),t.toString(16),l.toString(16)]));if(248>e)e=262144*(e-240)+4096*p+64*t+l;else throw Error(f(g.MALFORMED_UTF,[e.toString(16),p.toString(16),t.toString(16),l.toString(16)]));}}}65535<e&&(e-=65536,h+=String.fromCharCode(55296+(e>>10)),e=56320+(e&1023));h+=String.fromCharCode(e)}return h}var A=function(a,b){for(var c in a)if(a.hasOwnProperty(c))if(b.hasOwnProperty(c)){if(typeof a[c]!==b[c])throw Error(f(g.INVALID_TYPE,[typeof a[c],c]));}else{var h=\"Unknown property, \"+c+\n    \". Valid properties are:\";for(c in b)b.hasOwnProperty(c)&&(h=h+\" \"+c);throw Error(h);}},q=function(a,b){return function(){return a.apply(b,arguments)}},g={OK:{code:0,text:\"AMQJSC0000I OK.\"},CONNECT_TIMEOUT:{code:1,text:\"AMQJSC0001E Connect timed out.\"},SUBSCRIBE_TIMEOUT:{code:2,text:\"AMQJS0002E Subscribe timed out.\"},UNSUBSCRIBE_TIMEOUT:{code:3,text:\"AMQJS0003E Unsubscribe timed out.\"},PING_TIMEOUT:{code:4,text:\"AMQJS0004E Ping timed out.\"},INTERNAL_ERROR:{code:5,text:\"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}\"},\n    CONNACK_RETURNCODE:{code:6,text:\"AMQJS0006E Bad Connack return code:{0} {1}.\"},SOCKET_ERROR:{code:7,text:\"AMQJS0007E Socket error:{0}.\"},SOCKET_CLOSE:{code:8,text:\"AMQJS0008I Socket closed.\"},MALFORMED_UTF:{code:9,text:\"AMQJS0009E Malformed UTF data:{0} {1} {2}.\"},UNSUPPORTED:{code:10,text:\"AMQJS0010E {0} is not supported by this browser.\"},INVALID_STATE:{code:11,text:\"AMQJS0011E Invalid state {0}.\"},INVALID_TYPE:{code:12,text:\"AMQJS0012E Invalid type {0} for {1}.\"},INVALID_ARGUMENT:{code:13,text:\"AMQJS0013E Invalid argument {0} for {1}.\"},\n    UNSUPPORTED_OPERATION:{code:14,text:\"AMQJS0014E Unsupported operation.\"},INVALID_STORED_DATA:{code:15,text:\"AMQJS0015E Invalid data in local storage key={0} value={1}.\"},INVALID_MQTT_MESSAGE_TYPE:{code:16,text:\"AMQJS0016E Invalid MQTT message type {0}.\"},MALFORMED_UNICODE:{code:17,text:\"AMQJS0017E Malformed Unicode string:{0} {1}.\"}},J={0:\"Connection Accepted\",1:\"Connection Refused: unacceptable protocol version\",2:\"Connection Refused: identifier rejected\",3:\"Connection Refused: server unavailable\",\n    4:\"Connection Refused: bad user name or password\",5:\"Connection Refused: not authorized\"},f=function(a,b){var c=a.text;if(b)for(var h,e,d=0;d<b.length;d++)if(h=\"{\"+d+\"}\",e=c.indexOf(h),0<e)var g=c.substring(0,e),c=c.substring(e+h.length),c=g+b[d]+c;return c},B=[0,6,77,81,73,115,100,112,3],C=[0,4,77,81,84,84,4],n=function(a,b){this.type=a;for(var c in b)b.hasOwnProperty(c)&&(this[c]=b[c])};n.prototype.encode=function(){var a=(this.type&15)<<4,b=0,c=[],h=0;void 0!=this.messageIdentifier&&(b+=2);switch(this.type){case 1:switch(this.mqttVersion){case 3:b+=\n    B.length+3;break;case 4:b+=C.length+3}b+=m(this.clientId)+2;if(void 0!=this.willMessage){var b=b+(m(this.willMessage.destinationName)+2),e=this.willMessage.payloadBytes;e instanceof Uint8Array||(e=new Uint8Array(g));b+=e.byteLength+2}void 0!=this.userName&&(b+=m(this.userName)+2);void 0!=this.password&&(b+=m(this.password)+2);break;case 8:for(var a=a|2,d=0;d<this.topics.length;d++)c[d]=m(this.topics[d]),b+=c[d]+2;b+=this.requestedQos.length;break;case 10:a|=2;for(d=0;d<this.topics.length;d++)c[d]=\n    m(this.topics[d]),b+=c[d]+2;break;case 6:a|=2;break;case 3:this.payloadMessage.duplicate&&(a|=8);a=a|=this.payloadMessage.qos<<1;this.payloadMessage.retained&&(a|=1);var h=m(this.payloadMessage.destinationName),g=this.payloadMessage.payloadBytes,b=b+(h+2)+g.byteLength;g instanceof ArrayBuffer?g=new Uint8Array(g):g instanceof Uint8Array||(g=new Uint8Array(g.buffer))}var f=b,d=Array(1),l=0;do{var z=f%128,f=f>>7;0<f&&(z|=128);d[l++]=z}while(0<f&&4>l);f=d.length+1;b=new ArrayBuffer(b+f);l=new Uint8Array(b);\n    l[0]=a;l.set(d,1);if(3==this.type)f=r(this.payloadMessage.destinationName,h,l,f);else if(1==this.type){switch(this.mqttVersion){case 3:l.set(B,f);f+=B.length;break;case 4:l.set(C,f),f+=C.length}a=0;this.cleanSession&&(a=2);void 0!=this.willMessage&&(a=a|4|this.willMessage.qos<<3,this.willMessage.retained&&(a|=32));void 0!=this.userName&&(a|=128);void 0!=this.password&&(a|=64);l[f++]=a;f=y(this.keepAliveInterval,l,f)}void 0!=this.messageIdentifier&&(f=y(this.messageIdentifier,l,f));switch(this.type){case 1:f=\n        r(this.clientId,m(this.clientId),l,f);void 0!=this.willMessage&&(f=r(this.willMessage.destinationName,m(this.willMessage.destinationName),l,f),f=y(e.byteLength,l,f),l.set(e,f),f+=e.byteLength);void 0!=this.userName&&(f=r(this.userName,m(this.userName),l,f));void 0!=this.password&&r(this.password,m(this.password),l,f);break;case 3:l.set(g,f);break;case 8:for(d=0;d<this.topics.length;d++)f=r(this.topics[d],c[d],l,f),l[f++]=this.requestedQos[d];break;case 10:for(d=0;d<this.topics.length;d++)f=r(this.topics[d],\n        c[d],l,f)}return b};var H=function(a,b,c){this._client=a;this._window=b;this._keepAliveInterval=1E3*c;this.isReset=!1;var h=(new n(12)).encode(),e=function(a){return function(){return d.apply(a)}},d=function(){this.isReset?(this.isReset=!1,this._client._trace(\"Pinger.doPing\",\"send PINGREQ\"),this._client.socket.send(h),this.timeout=this._window.setTimeout(e(this),this._keepAliveInterval)):(this._client._trace(\"Pinger.doPing\",\"Timed out\"),this._client._disconnected(g.PING_TIMEOUT.code,f(g.PING_TIMEOUT)))};\n    this.reset=function(){this.isReset=!0;this._window.clearTimeout(this.timeout);0<this._keepAliveInterval&&(this.timeout=setTimeout(e(this),this._keepAliveInterval))};this.cancel=function(){this._window.clearTimeout(this.timeout)}},D=function(a,b,c,f,e){this._window=b;c||(c=30);this.timeout=setTimeout(function(a,b,c){return function(){return a.apply(b,c)}}(f,a,e),1E3*c);this.cancel=function(){this._window.clearTimeout(this.timeout)}},k=function(a,b,c,h,e){if(!(\"WebSocket\"in u&&null!==u.WebSocket))throw Error(f(g.UNSUPPORTED,\n    [\"WebSocket\"]));if(!(\"localStorage\"in u&&null!==u.localStorage))throw Error(f(g.UNSUPPORTED,[\"localStorage\"]));if(!(\"ArrayBuffer\"in u&&null!==u.ArrayBuffer))throw Error(f(g.UNSUPPORTED,[\"ArrayBuffer\"]));this._trace(\"Paho.MQTT.Client\",a,b,c,h,e);this.host=b;this.port=c;this.path=h;this.uri=a;this.clientId=e;this._localKey=b+\":\"+c+(\"/mqtt\"!=h?\":\"+h:\"\")+\":\"+e+\":\";this._msg_queue=[];this._sentMessages={};this._receivedMessages={};this._notify_msg_sent={};this._message_identifier=1;this._sequence=0;for(var d in localStorage)0!=\nd.indexOf(\"Sent:\"+this._localKey)&&0!=d.indexOf(\"Received:\"+this._localKey)||this.restore(d)};k.prototype.host;k.prototype.port;k.prototype.path;k.prototype.uri;k.prototype.clientId;k.prototype.socket;k.prototype.connected=!1;k.prototype.maxMessageIdentifier=65536;k.prototype.connectOptions;k.prototype.hostIndex;k.prototype.onConnectionLost;k.prototype.onMessageDelivered;k.prototype.onMessageArrived;k.prototype.traceFunction;k.prototype._msg_queue=null;k.prototype._connectTimeout;k.prototype.sendPinger=\n    null;k.prototype.receivePinger=null;k.prototype.receiveBuffer=null;k.prototype._traceBuffer=null;k.prototype._MAX_TRACE_ENTRIES=100;k.prototype.connect=function(a){var b=this._traceMask(a,\"password\");this._trace(\"Client.connect\",b,this.socket,this.connected);if(this.connected)throw Error(f(g.INVALID_STATE,[\"already connected\"]));if(this.socket)throw Error(f(g.INVALID_STATE,[\"already connected\"]));this.connectOptions=a;a.uris?(this.hostIndex=0,this._doConnect(a.uris[0])):this._doConnect(this.uri)};\n    k.prototype.subscribe=function(a,b){this._trace(\"Client.subscribe\",a,b);if(!this.connected)throw Error(f(g.INVALID_STATE,[\"not connected\"]));var c=new n(8);c.topics=[a];c.requestedQos=void 0!=b.qos?[b.qos]:[0];b.onSuccess&&(c.onSuccess=function(a){b.onSuccess({invocationContext:b.invocationContext,grantedQos:a})});b.onFailure&&(c.onFailure=function(a){b.onFailure({invocationContext:b.invocationContext,errorCode:a})});b.timeout&&(c.timeOut=new D(this,window,b.timeout,b.onFailure,[{invocationContext:b.invocationContext,\n        errorCode:g.SUBSCRIBE_TIMEOUT.code,errorMessage:f(g.SUBSCRIBE_TIMEOUT)}]));this._requires_ack(c);this._schedule_message(c)};k.prototype.unsubscribe=function(a,b){this._trace(\"Client.unsubscribe\",a,b);if(!this.connected)throw Error(f(g.INVALID_STATE,[\"not connected\"]));var c=new n(10);c.topics=[a];b.onSuccess&&(c.callback=function(){b.onSuccess({invocationContext:b.invocationContext})});b.timeout&&(c.timeOut=new D(this,window,b.timeout,b.onFailure,[{invocationContext:b.invocationContext,errorCode:g.UNSUBSCRIBE_TIMEOUT.code,\n        errorMessage:f(g.UNSUBSCRIBE_TIMEOUT)}]));this._requires_ack(c);this._schedule_message(c)};k.prototype.send=function(a){this._trace(\"Client.send\",a);if(!this.connected)throw Error(f(g.INVALID_STATE,[\"not connected\"]));wireMessage=new n(3);wireMessage.payloadMessage=a;0<a.qos?this._requires_ack(wireMessage):this.onMessageDelivered&&(this._notify_msg_sent[wireMessage]=this.onMessageDelivered(wireMessage.payloadMessage));this._schedule_message(wireMessage)};k.prototype.disconnect=function(){this._trace(\"Client.disconnect\");\n        if(!this.socket)throw Error(f(g.INVALID_STATE,[\"not connecting or connected\"]));wireMessage=new n(14);this._notify_msg_sent[wireMessage]=q(this._disconnected,this);this._schedule_message(wireMessage)};k.prototype.getTraceLog=function(){if(null!==this._traceBuffer){this._trace(\"Client.getTraceLog\",new Date);this._trace(\"Client.getTraceLog in flight messages\",this._sentMessages.length);for(var a in this._sentMessages)this._trace(\"_sentMessages \",a,this._sentMessages[a]);for(a in this._receivedMessages)this._trace(\"_receivedMessages \",\n        a,this._receivedMessages[a]);return this._traceBuffer}};k.prototype.startTrace=function(){null===this._traceBuffer&&(this._traceBuffer=[]);this._trace(\"Client.startTrace\",new Date,\"@VERSION@\")};k.prototype.stopTrace=function(){delete this._traceBuffer};k.prototype._doConnect=function(a){this.connectOptions.useSSL&&(a=a.split(\":\"),a[0]=\"wss\",a=a.join(\":\"));this.connected=!1;this.socket=4>this.connectOptions.mqttVersion?new WebSocket(a,[\"mqttv3.1\"]):new WebSocket(a,[\"mqtt\"]);this.socket.binaryType=\n        \"arraybuffer\";this.socket.onopen=q(this._on_socket_open,this);this.socket.onmessage=q(this._on_socket_message,this);this.socket.onerror=q(this._on_socket_error,this);this.socket.onclose=q(this._on_socket_close,this);this.sendPinger=new H(this,window,this.connectOptions.keepAliveInterval);this.receivePinger=new H(this,window,this.connectOptions.keepAliveInterval);this._connectTimeout=new D(this,window,this.connectOptions.timeout,this._disconnected,[g.CONNECT_TIMEOUT.code,f(g.CONNECT_TIMEOUT)])};k.prototype._schedule_message=\n        function(a){this._msg_queue.push(a);this.connected&&this._process_queue()};k.prototype.store=function(a,b){var c={type:b.type,messageIdentifier:b.messageIdentifier,version:1};switch(b.type){case 3:b.pubRecReceived&&(c.pubRecReceived=!0);c.payloadMessage={};for(var h=\"\",e=b.payloadMessage.payloadBytes,d=0;d<e.length;d++)h=15>=e[d]?h+\"0\"+e[d].toString(16):h+e[d].toString(16);c.payloadMessage.payloadHex=h;c.payloadMessage.qos=b.payloadMessage.qos;c.payloadMessage.destinationName=b.payloadMessage.destinationName;\n        b.payloadMessage.duplicate&&(c.payloadMessage.duplicate=!0);b.payloadMessage.retained&&(c.payloadMessage.retained=!0);0==a.indexOf(\"Sent:\")&&(void 0===b.sequence&&(b.sequence=++this._sequence),c.sequence=b.sequence);break;default:throw Error(f(g.INVALID_STORED_DATA,[key,c]));}localStorage.setItem(a+this._localKey+b.messageIdentifier,JSON.stringify(c))};k.prototype.restore=function(a){var b=localStorage.getItem(a),c=JSON.parse(b),h=new n(c.type,c);switch(c.type){case 3:for(var b=c.payloadMessage.payloadHex,\n                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         e=new ArrayBuffer(b.length/2),e=new Uint8Array(e),d=0;2<=b.length;){var k=parseInt(b.substring(0,2),16),b=b.substring(2,b.length);e[d++]=k}b=new Paho.MQTT.Message(e);b.qos=c.payloadMessage.qos;b.destinationName=c.payloadMessage.destinationName;c.payloadMessage.duplicate&&(b.duplicate=!0);c.payloadMessage.retained&&(b.retained=!0);h.payloadMessage=b;break;default:throw Error(f(g.INVALID_STORED_DATA,[a,b]));}0==a.indexOf(\"Sent:\"+this._localKey)?(h.payloadMessage.duplicate=!0,this._sentMessages[h.messageIdentifier]=\n        h):0==a.indexOf(\"Received:\"+this._localKey)&&(this._receivedMessages[h.messageIdentifier]=h)};k.prototype._process_queue=function(){for(var a=null,b=this._msg_queue.reverse();a=b.pop();)this._socket_send(a),this._notify_msg_sent[a]&&(this._notify_msg_sent[a](),delete this._notify_msg_sent[a])};k.prototype._requires_ack=function(a){var b=Object.keys(this._sentMessages).length;if(b>this.maxMessageIdentifier)throw Error(\"Too many messages:\"+b);for(;void 0!==this._sentMessages[this._message_identifier];)this._message_identifier++;\n        a.messageIdentifier=this._message_identifier;this._sentMessages[a.messageIdentifier]=a;3===a.type&&this.store(\"Sent:\",a);this._message_identifier===this.maxMessageIdentifier&&(this._message_identifier=1)};k.prototype._on_socket_open=function(){var a=new n(1,this.connectOptions);a.clientId=this.clientId;this._socket_send(a)};k.prototype._on_socket_message=function(a){this._trace(\"Client._on_socket_message\",a.data);this.receivePinger.reset();a=this._deframeMessages(a.data);for(var b=0;b<a.length;b+=\n        1)this._handleMessage(a[b])};k.prototype._deframeMessages=function(a){a=new Uint8Array(a);if(this.receiveBuffer){var b=new Uint8Array(this.receiveBuffer.length+a.length);b.set(this.receiveBuffer);b.set(a,this.receiveBuffer.length);a=b;delete this.receiveBuffer}try{for(var b=0,c=[];b<a.length;){var h;a:{var e=a,d=b,k=d,t=e[d],l=t>>4,z=t&15,d=d+1,v=void 0,E=0,m=1;do{if(d==e.length){h=[null,k];break a}v=e[d++];E+=(v&127)*m;m*=128}while(0!=(v&128));v=d+E;if(v>e.length)h=[null,k];else{var w=new n(l);switch(l){case 2:e[d++]&\n    1&&(w.sessionPresent=!0);w.returnCode=e[d++];break;case 3:var k=z>>1&3,r=256*e[d]+e[d+1],d=d+2,u=G(e,d,r),d=d+r;0<k&&(w.messageIdentifier=256*e[d]+e[d+1],d+=2);var q=new Paho.MQTT.Message(e.subarray(d,v));1==(z&1)&&(q.retained=!0);8==(z&8)&&(q.duplicate=!0);q.qos=k;q.destinationName=u;w.payloadMessage=q;break;case 4:case 5:case 6:case 7:case 11:w.messageIdentifier=256*e[d]+e[d+1];break;case 9:w.messageIdentifier=256*e[d]+e[d+1],d+=2,w.returnCode=e.subarray(d,v)}h=[w,v]}}var x=h[0],b=h[1];if(null!==\n        x)c.push(x);else break}b<a.length&&(this.receiveBuffer=a.subarray(b))}catch(y){this._disconnected(g.INTERNAL_ERROR.code,f(g.INTERNAL_ERROR,[y.message,y.stack.toString()]));return}return c};k.prototype._handleMessage=function(a){this._trace(\"Client._handleMessage\",a);try{switch(a.type){case 2:this._connectTimeout.cancel();if(this.connectOptions.cleanSession){for(var b in this._sentMessages){var c=this._sentMessages[b];localStorage.removeItem(\"Sent:\"+this._localKey+c.messageIdentifier)}this._sentMessages=\n        {};for(b in this._receivedMessages){var h=this._receivedMessages[b];localStorage.removeItem(\"Received:\"+this._localKey+h.messageIdentifier)}this._receivedMessages={}}if(0===a.returnCode)this.connected=!0,this.connectOptions.uris&&(this.hostIndex=this.connectOptions.uris.length);else{this._disconnected(g.CONNACK_RETURNCODE.code,f(g.CONNACK_RETURNCODE,[a.returnCode,J[a.returnCode]]));break}a=[];for(var e in this._sentMessages)this._sentMessages.hasOwnProperty(e)&&a.push(this._sentMessages[e]);a=a.sort(function(a,\n                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          b){return a.sequence-b.sequence});e=0;for(var d=a.length;e<d;e++)if(c=a[e],3==c.type&&c.pubRecReceived){var k=new n(6,{messageIdentifier:c.messageIdentifier});this._schedule_message(k)}else this._schedule_message(c);if(this.connectOptions.onSuccess)this.connectOptions.onSuccess({invocationContext:this.connectOptions.invocationContext});this._process_queue();break;case 3:this._receivePublish(a);break;case 4:if(c=this._sentMessages[a.messageIdentifier])if(delete this._sentMessages[a.messageIdentifier],\n        localStorage.removeItem(\"Sent:\"+this._localKey+a.messageIdentifier),this.onMessageDelivered)this.onMessageDelivered(c.payloadMessage);break;case 5:if(c=this._sentMessages[a.messageIdentifier])c.pubRecReceived=!0,k=new n(6,{messageIdentifier:a.messageIdentifier}),this.store(\"Sent:\",c),this._schedule_message(k);break;case 6:h=this._receivedMessages[a.messageIdentifier];localStorage.removeItem(\"Received:\"+this._localKey+a.messageIdentifier);h&&(this._receiveMessage(h),delete this._receivedMessages[a.messageIdentifier]);\n        var m=new n(7,{messageIdentifier:a.messageIdentifier});this._schedule_message(m);break;case 7:c=this._sentMessages[a.messageIdentifier];delete this._sentMessages[a.messageIdentifier];localStorage.removeItem(\"Sent:\"+this._localKey+a.messageIdentifier);if(this.onMessageDelivered)this.onMessageDelivered(c.payloadMessage);break;case 9:if(c=this._sentMessages[a.messageIdentifier]){c.timeOut&&c.timeOut.cancel();a.returnCode.indexOf=Array.prototype.indexOf;if(-1!==a.returnCode.indexOf(128)){if(c.onFailure)c.onFailure(a.returnCode)}else if(c.onSuccess)c.onSuccess(a.returnCode);\n        delete this._sentMessages[a.messageIdentifier]}break;case 11:if(c=this._sentMessages[a.messageIdentifier])c.timeOut&&c.timeOut.cancel(),c.callback&&c.callback(),delete this._sentMessages[a.messageIdentifier];break;case 13:this.sendPinger.reset();break;case 14:this._disconnected(g.INVALID_MQTT_MESSAGE_TYPE.code,f(g.INVALID_MQTT_MESSAGE_TYPE,[a.type]));break;default:this._disconnected(g.INVALID_MQTT_MESSAGE_TYPE.code,f(g.INVALID_MQTT_MESSAGE_TYPE,[a.type]))}}catch(l){this._disconnected(g.INTERNAL_ERROR.code,\n        f(g.INTERNAL_ERROR,[l.message,l.stack.toString()]))}};k.prototype._on_socket_error=function(a){this._disconnected(g.SOCKET_ERROR.code,f(g.SOCKET_ERROR,[a.data]))};k.prototype._on_socket_close=function(){this._disconnected(g.SOCKET_CLOSE.code,f(g.SOCKET_CLOSE))};k.prototype._socket_send=function(a){if(1==a.type){var b=this._traceMask(a,\"password\");this._trace(\"Client._socket_send\",b)}else this._trace(\"Client._socket_send\",a);this.socket.send(a.encode());this.sendPinger.reset()};k.prototype._receivePublish=\n        function(a){switch(a.payloadMessage.qos){case \"undefined\":case 0:this._receiveMessage(a);break;case 1:var b=new n(4,{messageIdentifier:a.messageIdentifier});this._schedule_message(b);this._receiveMessage(a);break;case 2:this._receivedMessages[a.messageIdentifier]=a;this.store(\"Received:\",a);a=new n(5,{messageIdentifier:a.messageIdentifier});this._schedule_message(a);break;default:throw Error(\"Invaild qos=\"+wireMmessage.payloadMessage.qos);}};k.prototype._receiveMessage=function(a){if(this.onMessageArrived)this.onMessageArrived(a.payloadMessage)};\n    k.prototype._disconnected=function(a,b){this._trace(\"Client._disconnected\",a,b);this.sendPinger.cancel();this.receivePinger.cancel();this._connectTimeout&&this._connectTimeout.cancel();this._msg_queue=[];this._notify_msg_sent={};this.socket&&(this.socket.onopen=null,this.socket.onmessage=null,this.socket.onerror=null,this.socket.onclose=null,1===this.socket.readyState&&this.socket.close(),delete this.socket);if(this.connectOptions.uris&&this.hostIndex<this.connectOptions.uris.length-1)this.hostIndex++,\n        this._doConnect(this.connectOptions.uris[this.hostIndex]);else if(void 0===a&&(a=g.OK.code,b=f(g.OK)),this.connected){if(this.connected=!1,this.onConnectionLost)this.onConnectionLost({errorCode:a,errorMessage:b})}else if(4===this.connectOptions.mqttVersion&&!1===this.connectOptions.mqttVersionExplicit)this._trace(\"Failed to connect V4, dropping back to V3\"),this.connectOptions.mqttVersion=3,this.connectOptions.uris?(this.hostIndex=0,this._doConnect(this.connectOptions.uris[0])):this._doConnect(this.uri);\n    else if(this.connectOptions.onFailure)this.connectOptions.onFailure({invocationContext:this.connectOptions.invocationContext,errorCode:a,errorMessage:b})};k.prototype._trace=function(){if(this.traceFunction){for(var a in arguments)\"undefined\"!==typeof arguments[a]&&(arguments[a]=JSON.stringify(arguments[a]));a=Array.prototype.slice.call(arguments).join(\"\");this.traceFunction({severity:\"Debug\",message:a})}if(null!==this._traceBuffer){a=0;for(var b=arguments.length;a<b;a++)this._traceBuffer.length==\n    this._MAX_TRACE_ENTRIES&&this._traceBuffer.shift(),0===a?this._traceBuffer.push(arguments[a]):\"undefined\"===typeof arguments[a]?this._traceBuffer.push(arguments[a]):this._traceBuffer.push(\"  \"+JSON.stringify(arguments[a]))}};k.prototype._traceMask=function(a,b){var c={},f;for(f in a)a.hasOwnProperty(f)&&(c[f]=f==b?\"******\":a[f]);return c};var I=function(a,b,c,h){var e;if(\"string\"!==typeof a)throw Error(f(g.INVALID_TYPE,[typeof a,\"host\"]));if(2==arguments.length){h=b;e=a;var d=e.match(/^(wss?):\\/\\/((\\[(.+)\\])|([^\\/]+?))(:(\\d+))?(\\/.*)$/);\n        if(d)a=d[4]||d[2],b=parseInt(d[7]),c=d[8];else throw Error(f(g.INVALID_ARGUMENT,[a,\"host\"]));}else{3==arguments.length&&(h=c,c=\"/mqtt\");if(\"number\"!==typeof b||0>b)throw Error(f(g.INVALID_TYPE,[typeof b,\"port\"]));if(\"string\"!==typeof c)throw Error(f(g.INVALID_TYPE,[typeof c,\"path\"]));e=\"ws://\"+(-1!=a.indexOf(\":\")&&\"[\"!=a.slice(0,1)&&\"]\"!=a.slice(-1)?\"[\"+a+\"]\":a)+\":\"+b+c}for(var p=d=0;p<h.length;p++){var m=h.charCodeAt(p);55296<=m&&56319>=m&&p++;d++}if(\"string\"!==typeof h||65535<d)throw Error(f(g.INVALID_ARGUMENT,\n        [h,\"clientId\"]));var l=new k(e,a,b,c,h);this._getHost=function(){return a};this._setHost=function(){throw Error(f(g.UNSUPPORTED_OPERATION));};this._getPort=function(){return b};this._setPort=function(){throw Error(f(g.UNSUPPORTED_OPERATION));};this._getPath=function(){return c};this._setPath=function(){throw Error(f(g.UNSUPPORTED_OPERATION));};this._getURI=function(){return e};this._setURI=function(){throw Error(f(g.UNSUPPORTED_OPERATION));};this._getClientId=function(){return l.clientId};this._setClientId=\n        function(){throw Error(f(g.UNSUPPORTED_OPERATION));};this._getOnConnectionLost=function(){return l.onConnectionLost};this._setOnConnectionLost=function(a){if(\"function\"===typeof a)l.onConnectionLost=a;else throw Error(f(g.INVALID_TYPE,[typeof a,\"onConnectionLost\"]));};this._getOnMessageDelivered=function(){return l.onMessageDelivered};this._setOnMessageDelivered=function(a){if(\"function\"===typeof a)l.onMessageDelivered=a;else throw Error(f(g.INVALID_TYPE,[typeof a,\"onMessageDelivered\"]));};this._getOnMessageArrived=\n        function(){return l.onMessageArrived};this._setOnMessageArrived=function(a){if(\"function\"===typeof a)l.onMessageArrived=a;else throw Error(f(g.INVALID_TYPE,[typeof a,\"onMessageArrived\"]));};this._getTrace=function(){return l.traceFunction};this._setTrace=function(a){if(\"function\"===typeof a)l.traceFunction=a;else throw Error(f(g.INVALID_TYPE,[typeof a,\"onTrace\"]));};this.connect=function(a){a=a||{};A(a,{timeout:\"number\",userName:\"string\",password:\"string\",willMessage:\"object\",keepAliveInterval:\"number\",\n        cleanSession:\"boolean\",useSSL:\"boolean\",invocationContext:\"object\",onSuccess:\"function\",onFailure:\"function\",hosts:\"object\",ports:\"object\",mqttVersion:\"number\"});void 0===a.keepAliveInterval&&(a.keepAliveInterval=60);if(4<a.mqttVersion||3>a.mqttVersion)throw Error(f(g.INVALID_ARGUMENT,[a.mqttVersion,\"connectOptions.mqttVersion\"]));void 0===a.mqttVersion?(a.mqttVersionExplicit=!1,a.mqttVersion=4):a.mqttVersionExplicit=!0;if(void 0===a.password&&void 0!==a.userName)throw Error(f(g.INVALID_ARGUMENT,\n        [a.password,\"connectOptions.password\"]));if(a.willMessage){if(!(a.willMessage instanceof x))throw Error(f(g.INVALID_TYPE,[a.willMessage,\"connectOptions.willMessage\"]));a.willMessage.stringPayload;if(\"undefined\"===typeof a.willMessage.destinationName)throw Error(f(g.INVALID_TYPE,[typeof a.willMessage.destinationName,\"connectOptions.willMessage.destinationName\"]));}\"undefined\"===typeof a.cleanSession&&(a.cleanSession=!0);if(a.hosts){if(!(a.hosts instanceof Array))throw Error(f(g.INVALID_ARGUMENT,[a.hosts,\n        \"connectOptions.hosts\"]));if(1>a.hosts.length)throw Error(f(g.INVALID_ARGUMENT,[a.hosts,\"connectOptions.hosts\"]));for(var b=!1,d=0;d<a.hosts.length;d++){if(\"string\"!==typeof a.hosts[d])throw Error(f(g.INVALID_TYPE,[typeof a.hosts[d],\"connectOptions.hosts[\"+d+\"]\"]));if(/^(wss?):\\/\\/((\\[(.+)\\])|([^\\/]+?))(:(\\d+))?(\\/.*)$/.test(a.hosts[d]))if(0==d)b=!0;else{if(!b)throw Error(f(g.INVALID_ARGUMENT,[a.hosts[d],\"connectOptions.hosts[\"+d+\"]\"]));}else if(b)throw Error(f(g.INVALID_ARGUMENT,[a.hosts[d],\"connectOptions.hosts[\"+\n    d+\"]\"]));}if(b)a.uris=a.hosts;else{if(!a.ports)throw Error(f(g.INVALID_ARGUMENT,[a.ports,\"connectOptions.ports\"]));if(!(a.ports instanceof Array))throw Error(f(g.INVALID_ARGUMENT,[a.ports,\"connectOptions.ports\"]));if(a.hosts.length!=a.ports.length)throw Error(f(g.INVALID_ARGUMENT,[a.ports,\"connectOptions.ports\"]));a.uris=[];for(d=0;d<a.hosts.length;d++){if(\"number\"!==typeof a.ports[d]||0>a.ports[d])throw Error(f(g.INVALID_TYPE,[typeof a.ports[d],\"connectOptions.ports[\"+d+\"]\"]));var b=a.hosts[d],h=\n        a.ports[d];e=\"ws://\"+(-1!=b.indexOf(\":\")?\"[\"+b+\"]\":b)+\":\"+h+c;a.uris.push(e)}}}l.connect(a)};this.subscribe=function(a,b){if(\"string\"!==typeof a)throw Error(\"Invalid argument:\"+a);b=b||{};A(b,{qos:\"number\",invocationContext:\"object\",onSuccess:\"function\",onFailure:\"function\",timeout:\"number\"});if(b.timeout&&!b.onFailure)throw Error(\"subscribeOptions.timeout specified with no onFailure callback.\");if(\"undefined\"!==typeof b.qos&&0!==b.qos&&1!==b.qos&&2!==b.qos)throw Error(f(g.INVALID_ARGUMENT,[b.qos,\n        \"subscribeOptions.qos\"]));l.subscribe(a,b)};this.unsubscribe=function(a,b){if(\"string\"!==typeof a)throw Error(\"Invalid argument:\"+a);b=b||{};A(b,{invocationContext:\"object\",onSuccess:\"function\",onFailure:\"function\",timeout:\"number\"});if(b.timeout&&!b.onFailure)throw Error(\"unsubscribeOptions.timeout specified with no onFailure callback.\");l.unsubscribe(a,b)};this.send=function(a,b,c,d){var e;if(0==arguments.length)throw Error(\"Invalid argument.length\");if(1==arguments.length){if(!(a instanceof x)&&\n        \"string\"!==typeof a)throw Error(\"Invalid argument:\"+typeof a);e=a;if(\"undefined\"===typeof e.destinationName)throw Error(f(g.INVALID_ARGUMENT,[e.destinationName,\"Message.destinationName\"]));}else e=new x(b),e.destinationName=a,3<=arguments.length&&(e.qos=c),4<=arguments.length&&(e.retained=d);l.send(e)};this.disconnect=function(){l.disconnect()};this.getTraceLog=function(){return l.getTraceLog()};this.startTrace=function(){l.startTrace()};this.stopTrace=function(){l.stopTrace()};this.isConnected=function(){return l.connected}};\n    I.prototype={get host(){return this._getHost()},set host(a){this._setHost(a)},get port(){return this._getPort()},set port(a){this._setPort(a)},get path(){return this._getPath()},set path(a){this._setPath(a)},get clientId(){return this._getClientId()},set clientId(a){this._setClientId(a)},get onConnectionLost(){return this._getOnConnectionLost()},set onConnectionLost(a){this._setOnConnectionLost(a)},get onMessageDelivered(){return this._getOnMessageDelivered()},set onMessageDelivered(a){this._setOnMessageDelivered(a)},\n        get onMessageArrived(){return this._getOnMessageArrived()},set onMessageArrived(a){this._setOnMessageArrived(a)},get trace(){return this._getTrace()},set trace(a){this._setTrace(a)}};var x=function(a){var b;if(\"string\"===typeof a||a instanceof ArrayBuffer||a instanceof Int8Array||a instanceof Uint8Array||a instanceof Int16Array||a instanceof Uint16Array||a instanceof Int32Array||a instanceof Uint32Array||a instanceof Float32Array||a instanceof Float64Array)b=a;else throw f(g.INVALID_ARGUMENT,[a,\"newPayload\"]);\n        this._getPayloadString=function(){return\"string\"===typeof b?b:G(b,0,b.length)};this._getPayloadBytes=function(){if(\"string\"===typeof b){var a=new ArrayBuffer(m(b)),a=new Uint8Array(a);F(b,a,0);return a}return b};var c=void 0;this._getDestinationName=function(){return c};this._setDestinationName=function(a){if(\"string\"===typeof a)c=a;else throw Error(f(g.INVALID_ARGUMENT,[a,\"newDestinationName\"]));};var h=0;this._getQos=function(){return h};this._setQos=function(a){if(0===a||1===a||2===a)h=a;else throw Error(\"Invalid argument:\"+\n            a);};var e=!1;this._getRetained=function(){return e};this._setRetained=function(a){if(\"boolean\"===typeof a)e=a;else throw Error(f(g.INVALID_ARGUMENT,[a,\"newRetained\"]));};var d=!1;this._getDuplicate=function(){return d};this._setDuplicate=function(a){d=a}};x.prototype={get payloadString(){return this._getPayloadString()},get payloadBytes(){return this._getPayloadBytes()},get destinationName(){return this._getDestinationName()},set destinationName(a){this._setDestinationName(a)},get qos(){return this._getQos()},\n        set qos(a){this._setQos(a)},get retained(){return this._getRetained()},set retained(a){this._setRetained(a)},get duplicate(){return this._getDuplicate()},set duplicate(a){this._setDuplicate(a)}};return{Client:I,Message:x}}(window);\n"
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/websocket/paho-mqtt.js",
    "content": "/*******************************************************************************\n * Copyright (c) 2013 IBM Corp.\n *\n * All rights reserved. This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License v1.0\n * and Eclipse Distribution License v1.0 which accompany this distribution.\n *\n * The Eclipse Public License is available at\n *    http://www.eclipse.org/legal/epl-v10.html\n * and the Eclipse Distribution License is available at\n *   http://www.eclipse.org/org/documents/edl-v10.php.\n *\n * Contributors:\n *    Andrew Banks - initial API and implementation and initial documentation\n *******************************************************************************/\n\n\n// Only expose a single object name in the global namespace.\n// Everything must go through this module. Global Paho.MQTT module\n// only has a single public function, client, which returns\n// a Paho.MQTT client object given connection details.\n\n/**\n * Send and receive messages using web browsers.\n * <p>\n * This programming interface lets a JavaScript client application use the MQTT V3.1 or\n * V3.1.1 protocol to connect to an MQTT-supporting messaging server.\n *\n * The function supported includes:\n * <ol>\n * <li>Connecting to and disconnecting from a server. The server is identified by its host name and port number.\n * <li>Specifying options that relate to the communications link with the server,\n * for example the frequency of keep-alive heartbeats, and whether SSL/TLS is required.\n * <li>Subscribing to and receiving messages from MQTT Topics.\n * <li>Publishing messages to MQTT Topics.\n * </ol>\n * <p>\n * The API consists of two main objects:\n * <dl>\n * <dt><b>{@link Paho.MQTT.Client}</b></dt>\n * <dd>This contains methods that provide the functionality of the API,\n * including provision of callbacks that notify the application when a message\n * arrives from or is delivered to the messaging server,\n * or when the status of its connection to the messaging server changes.</dd>\n * <dt><b>{@link Paho.MQTT.Message}</b></dt>\n * <dd>This encapsulates the payload of the message along with various attributes\n * associated with its delivery, in particular the destination to which it has\n * been (or is about to be) sent.</dd>\n * </dl>\n * <p>\n * The programming interface validates parameters passed to it, and will throw\n * an Error containing an error message intended for developer use, if it detects\n * an error with any parameter.\n * <p>\n * Example:\n *\n * <code><pre>\n client = new Paho.MQTT.Client(location.hostname, Number(location.port), \"clientId\");\n client.onConnectionLost = onConnectionLost;\n client.onMessageArrived = onMessageArrived;\n client.connect({onSuccess:onConnect});\n\n function onConnect() {\n  // Once a connection has been made, make a subscription and send a message.\n  console.log(\"onConnect\");\n  client.subscribe(\"/World\");\n  message = new Paho.MQTT.Message(\"Hello\");\n  message.destinationName = \"/World\";\n  client.send(message);\n};\n function onConnectionLost(responseObject) {\n  if (responseObject.errorCode !== 0)\n\tconsole.log(\"onConnectionLost:\"+responseObject.errorMessage);\n};\n function onMessageArrived(message) {\n  console.log(\"onMessageArrived:\"+message.payloadString);\n  client.disconnect();\n};\n * </pre></code>\n * @namespace Paho.MQTT\n */\n\nif (typeof Paho === \"undefined\") {\n\tPaho = {};\n}\n\nPaho.MQTT = (function (global) {\n\n\t// Private variables below, these are only visible inside the function closure\n\t// which is used to define the module.\n\n\tvar version = \"@VERSION@\";\n\tvar buildLevel = \"@BUILDLEVEL@\";\n\n\t/**\n\t * Unique message type identifiers, with associated\n\t * associated integer values.\n\t * @private\n\t */\n\tvar MESSAGE_TYPE = {\n\t\tCONNECT: 1,\n\t\tCONNACK: 2,\n\t\tPUBLISH: 3,\n\t\tPUBACK: 4,\n\t\tPUBREC: 5,\n\t\tPUBREL: 6,\n\t\tPUBCOMP: 7,\n\t\tSUBSCRIBE: 8,\n\t\tSUBACK: 9,\n\t\tUNSUBSCRIBE: 10,\n\t\tUNSUBACK: 11,\n\t\tPINGREQ: 12,\n\t\tPINGRESP: 13,\n\t\tDISCONNECT: 14\n\t};\n\n\t// Collection of utility methods used to simplify module code\n\t// and promote the DRY pattern.\n\n\t/**\n\t * Validate an object's parameter names to ensure they\n\t * match a list of expected variables name for this option\n\t * type. Used to ensure option object passed into the API don't\n\t * contain erroneous parameters.\n\t * @param {Object} obj - User options object\n\t * @param {Object} keys - valid keys and types that may exist in obj.\n\t * @throws {Error} Invalid option parameter found.\n\t * @private\n\t */\n\tvar validate = function(obj, keys) {\n\t\tfor (var key in obj) {\n\t\t\tif (obj.hasOwnProperty(key)) {\n\t\t\t\tif (keys.hasOwnProperty(key)) {\n\t\t\t\t\tif (typeof obj[key] !== keys[key])\n\t\t\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key]));\n\t\t\t\t} else {\n\t\t\t\t\tvar errorStr = \"Unknown property, \" + key + \". Valid properties are:\";\n\t\t\t\t\tfor (var key in keys)\n\t\t\t\t\t\tif (keys.hasOwnProperty(key))\n\t\t\t\t\t\t\terrorStr = errorStr+\" \"+key;\n\t\t\t\t\tthrow new Error(errorStr);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Return a new function which runs the user function bound\n\t * to a fixed scope.\n\t * @param {function} User function\n\t * @param {object} Function scope\n\t * @return {function} User function bound to another scope\n\t * @private\n\t */\n\tvar scope = function (f, scope) {\n\t\treturn function () {\n\t\t\treturn f.apply(scope, arguments);\n\t\t};\n\t};\n\n\t/**\n\t * Unique message type identifiers, with associated\n\t * associated integer values.\n\t * @private\n\t */\n\tvar ERROR = {\n\t\tOK: {code:0, text:\"AMQJSC0000I OK.\"},\n\t\tCONNECT_TIMEOUT: {code:1, text:\"AMQJSC0001E Connect timed out.\"},\n\t\tSUBSCRIBE_TIMEOUT: {code:2, text:\"AMQJS0002E Subscribe timed out.\"},\n\t\tUNSUBSCRIBE_TIMEOUT: {code:3, text:\"AMQJS0003E Unsubscribe timed out.\"},\n\t\tPING_TIMEOUT: {code:4, text:\"AMQJS0004E Ping timed out.\"},\n\t\tINTERNAL_ERROR: {code:5, text:\"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}\"},\n\t\tCONNACK_RETURNCODE: {code:6, text:\"AMQJS0006E Bad Connack return code:{0} {1}.\"},\n\t\tSOCKET_ERROR: {code:7, text:\"AMQJS0007E Socket error:{0}.\"},\n\t\tSOCKET_CLOSE: {code:8, text:\"AMQJS0008I Socket closed.\"},\n\t\tMALFORMED_UTF: {code:9, text:\"AMQJS0009E Malformed UTF data:{0} {1} {2}.\"},\n\t\tUNSUPPORTED: {code:10, text:\"AMQJS0010E {0} is not supported by this browser.\"},\n\t\tINVALID_STATE: {code:11, text:\"AMQJS0011E Invalid state {0}.\"},\n\t\tINVALID_TYPE: {code:12, text:\"AMQJS0012E Invalid type {0} for {1}.\"},\n\t\tINVALID_ARGUMENT: {code:13, text:\"AMQJS0013E Invalid argument {0} for {1}.\"},\n\t\tUNSUPPORTED_OPERATION: {code:14, text:\"AMQJS0014E Unsupported operation.\"},\n\t\tINVALID_STORED_DATA: {code:15, text:\"AMQJS0015E Invalid data in local storage key={0} value={1}.\"},\n\t\tINVALID_MQTT_MESSAGE_TYPE: {code:16, text:\"AMQJS0016E Invalid MQTT message type {0}.\"},\n\t\tMALFORMED_UNICODE: {code:17, text:\"AMQJS0017E Malformed Unicode string:{0} {1}.\"},\n\t};\n\n\t/** CONNACK RC Meaning. */\n\tvar CONNACK_RC = {\n\t\t0:\"Connection Accepted\",\n\t\t1:\"Connection Refused: unacceptable protocol version\",\n\t\t2:\"Connection Refused: identifier rejected\",\n\t\t3:\"Connection Refused: server unavailable\",\n\t\t4:\"Connection Refused: bad user name or password\",\n\t\t5:\"Connection Refused: not authorized\"\n\t};\n\n\t/**\n\t * Format an error message text.\n\t * @private\n\t * @param {error} ERROR.KEY value above.\n\t * @param {substitutions} [array] substituted into the text.\n\t * @return the text with the substitutions made.\n\t */\n\tvar format = function(error, substitutions) {\n\t\tvar text = error.text;\n\t\tif (substitutions) {\n\t\t\tvar field,start;\n\t\t\tfor (var i=0; i<substitutions.length; i++) {\n\t\t\t\tfield = \"{\"+i+\"}\";\n\t\t\t\tstart = text.indexOf(field);\n\t\t\t\tif(start > 0) {\n\t\t\t\t\tvar part1 = text.substring(0,start);\n\t\t\t\t\tvar part2 = text.substring(start+field.length);\n\t\t\t\t\ttext = part1+substitutions[i]+part2;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn text;\n\t};\n\n\t//MQTT protocol and version          6    M    Q    I    s    d    p    3\n\tvar MqttProtoIdentifierv3 = [0x00,0x06,0x4d,0x51,0x49,0x73,0x64,0x70,0x03];\n\t//MQTT proto/version for 311         4    M    Q    T    T    4\n\tvar MqttProtoIdentifierv4 = [0x00,0x04,0x4d,0x51,0x54,0x54,0x04];\n\n\t/**\n\t * Construct an MQTT wire protocol message.\n\t * @param type MQTT packet type.\n\t * @param options optional wire message attributes.\n\t *\n\t * Optional properties\n\t *\n\t * messageIdentifier: message ID in the range [0..65535]\n\t * payloadMessage:\tApplication Message - PUBLISH only\n\t * connectStrings:\tarray of 0 or more Strings to be put into the CONNECT payload\n\t * topics:\t\t\tarray of strings (SUBSCRIBE, UNSUBSCRIBE)\n\t * requestQoS:\t\tarray of QoS values [0..2]\n\t *\n\t * \"Flag\" properties\n\t * cleanSession:\ttrue if present / false if absent (CONNECT)\n\t * willMessage:  \ttrue if present / false if absent (CONNECT)\n\t * isRetained:\t\ttrue if present / false if absent (CONNECT)\n\t * userName:\t\ttrue if present / false if absent (CONNECT)\n\t * password:\t\ttrue if present / false if absent (CONNECT)\n\t * keepAliveInterval:\tinteger [0..65535]  (CONNECT)\n\t *\n\t * @private\n\t * @ignore\n\t */\n\tvar WireMessage = function (type, options) {\n\t\tthis.type = type;\n\t\tfor (var name in options) {\n\t\t\tif (options.hasOwnProperty(name)) {\n\t\t\t\tthis[name] = options[name];\n\t\t\t}\n\t\t}\n\t};\n\n\tWireMessage.prototype.encode = function() {\n\t\t// Compute the first byte of the fixed header\n\t\tvar first = ((this.type & 0x0f) << 4);\n\n\t\t/*\n\t\t * Now calculate the length of the variable header + payload by adding up the lengths\n\t\t * of all the component parts\n\t\t */\n\n\t\tvar remLength = 0;\n\t\tvar topicStrLength = new Array();\n\t\tvar destinationNameLength = 0;\n\n\t\t// if the message contains a messageIdentifier then we need two bytes for that\n\t\tif (this.messageIdentifier != undefined)\n\t\t\tremLength += 2;\n\n\t\tswitch(this.type) {\n\t\t\t// If this a Connect then we need to include 12 bytes for its header\n\t\t\tcase MESSAGE_TYPE.CONNECT:\n\t\t\t\tswitch(this.mqttVersion) {\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\tremLength += MqttProtoIdentifierv3.length + 3;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\tremLength += MqttProtoIdentifierv4.length + 3;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tremLength += UTF8Length(this.clientId) + 2;\n\t\t\t\tif (this.willMessage != undefined) {\n\t\t\t\t\tremLength += UTF8Length(this.willMessage.destinationName) + 2;\n\t\t\t\t\t// Will message is always a string, sent as UTF-8 characters with a preceding length.\n\t\t\t\t\tvar willMessagePayloadBytes = this.willMessage.payloadBytes;\n\t\t\t\t\tif (!(willMessagePayloadBytes instanceof Uint8Array))\n\t\t\t\t\t\twillMessagePayloadBytes = new Uint8Array(payloadBytes);\n\t\t\t\t\tremLength += willMessagePayloadBytes.byteLength +2;\n\t\t\t\t}\n\t\t\t\tif (this.userName != undefined)\n\t\t\t\t\tremLength += UTF8Length(this.userName) + 2;\n\t\t\t\tif (this.password != undefined)\n\t\t\t\t\tremLength += UTF8Length(this.password) + 2;\n\t\t\t\tbreak;\n\n\t\t\t// Subscribe, Unsubscribe can both contain topic strings\n\t\t\tcase MESSAGE_TYPE.SUBSCRIBE:\n\t\t\t\tfirst |= 0x02; // Qos = 1;\n\t\t\t\tfor ( var i = 0; i < this.topics.length; i++) {\n\t\t\t\t\ttopicStrLength[i] = UTF8Length(this.topics[i]);\n\t\t\t\t\tremLength += topicStrLength[i] + 2;\n\t\t\t\t}\n\t\t\t\tremLength += this.requestedQos.length; // 1 byte for each topic's Qos\n\t\t\t\t// QoS on Subscribe only\n\t\t\t\tbreak;\n\n\t\t\tcase MESSAGE_TYPE.UNSUBSCRIBE:\n\t\t\t\tfirst |= 0x02; // Qos = 1;\n\t\t\t\tfor ( var i = 0; i < this.topics.length; i++) {\n\t\t\t\t\ttopicStrLength[i] = UTF8Length(this.topics[i]);\n\t\t\t\t\tremLength += topicStrLength[i] + 2;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase MESSAGE_TYPE.PUBREL:\n\t\t\t\tfirst |= 0x02; // Qos = 1;\n\t\t\t\tbreak;\n\n\t\t\tcase MESSAGE_TYPE.PUBLISH:\n\t\t\t\tif (this.payloadMessage.duplicate) first |= 0x08;\n\t\t\t\tfirst  = first |= (this.payloadMessage.qos << 1);\n\t\t\t\tif (this.payloadMessage.retained) first |= 0x01;\n\t\t\t\tdestinationNameLength = UTF8Length(this.payloadMessage.destinationName);\n\t\t\t\tremLength += destinationNameLength + 2;\n\t\t\t\tvar payloadBytes = this.payloadMessage.payloadBytes;\n\t\t\t\tremLength += payloadBytes.byteLength;\n\t\t\t\tif (payloadBytes instanceof ArrayBuffer)\n\t\t\t\t\tpayloadBytes = new Uint8Array(payloadBytes);\n\t\t\t\telse if (!(payloadBytes instanceof Uint8Array))\n\t\t\t\t\tpayloadBytes = new Uint8Array(payloadBytes.buffer);\n\t\t\t\tbreak;\n\n\t\t\tcase MESSAGE_TYPE.DISCONNECT:\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\t;\n\t\t}\n\n\t\t// Now we can allocate a buffer for the message\n\n\t\tvar mbi = encodeMBI(remLength);  // Convert the length to MQTT MBI format\n\t\tvar pos = mbi.length + 1;        // Offset of start of variable header\n\t\tvar buffer = new ArrayBuffer(remLength + pos);\n\t\tvar byteStream = new Uint8Array(buffer);    // view it as a sequence of bytes\n\n\t\t//Write the fixed header into the buffer\n\t\tbyteStream[0] = first;\n\t\tbyteStream.set(mbi,1);\n\n\t\t// If this is a PUBLISH then the variable header starts with a topic\n\t\tif (this.type == MESSAGE_TYPE.PUBLISH)\n\t\t\tpos = writeString(this.payloadMessage.destinationName, destinationNameLength, byteStream, pos);\n\t\t// If this is a CONNECT then the variable header contains the protocol name/version, flags and keepalive time\n\n\t\telse if (this.type == MESSAGE_TYPE.CONNECT) {\n\t\t\tswitch (this.mqttVersion) {\n\t\t\t\tcase 3:\n\t\t\t\t\tbyteStream.set(MqttProtoIdentifierv3, pos);\n\t\t\t\t\tpos += MqttProtoIdentifierv3.length;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tbyteStream.set(MqttProtoIdentifierv4, pos);\n\t\t\t\t\tpos += MqttProtoIdentifierv4.length;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tvar connectFlags = 0;\n\t\t\tif (this.cleanSession)\n\t\t\t\tconnectFlags = 0x02;\n\t\t\tif (this.willMessage != undefined ) {\n\t\t\t\tconnectFlags |= 0x04;\n\t\t\t\tconnectFlags |= (this.willMessage.qos<<3);\n\t\t\t\tif (this.willMessage.retained) {\n\t\t\t\t\tconnectFlags |= 0x20;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.userName != undefined)\n\t\t\t\tconnectFlags |= 0x80;\n\t\t\tif (this.password != undefined)\n\t\t\t\tconnectFlags |= 0x40;\n\t\t\tbyteStream[pos++] = connectFlags;\n\t\t\tpos = writeUint16 (this.keepAliveInterval, byteStream, pos);\n\t\t}\n\n\t\t// Output the messageIdentifier - if there is one\n\t\tif (this.messageIdentifier != undefined)\n\t\t\tpos = writeUint16 (this.messageIdentifier, byteStream, pos);\n\n\t\tswitch(this.type) {\n\t\t\tcase MESSAGE_TYPE.CONNECT:\n\t\t\t\tpos = writeString(this.clientId, UTF8Length(this.clientId), byteStream, pos);\n\t\t\t\tif (this.willMessage != undefined) {\n\t\t\t\t\tpos = writeString(this.willMessage.destinationName, UTF8Length(this.willMessage.destinationName), byteStream, pos);\n\t\t\t\t\tpos = writeUint16(willMessagePayloadBytes.byteLength, byteStream, pos);\n\t\t\t\t\tbyteStream.set(willMessagePayloadBytes, pos);\n\t\t\t\t\tpos += willMessagePayloadBytes.byteLength;\n\n\t\t\t\t}\n\t\t\t\tif (this.userName != undefined)\n\t\t\t\t\tpos = writeString(this.userName, UTF8Length(this.userName), byteStream, pos);\n\t\t\t\tif (this.password != undefined)\n\t\t\t\t\tpos = writeString(this.password, UTF8Length(this.password), byteStream, pos);\n\t\t\t\tbreak;\n\n\t\t\tcase MESSAGE_TYPE.PUBLISH:\n\t\t\t\t// PUBLISH has a text or binary payload, if text do not add a 2 byte length field, just the UTF characters.\n\t\t\t\tbyteStream.set(payloadBytes, pos);\n\n\t\t\t\tbreak;\n\n//    \t    case MESSAGE_TYPE.PUBREC:\n//    \t    case MESSAGE_TYPE.PUBREL:\n//    \t    case MESSAGE_TYPE.PUBCOMP:\n//    \t    \tbreak;\n\n\t\t\tcase MESSAGE_TYPE.SUBSCRIBE:\n\t\t\t\t// SUBSCRIBE has a list of topic strings and request QoS\n\t\t\t\tfor (var i=0; i<this.topics.length; i++) {\n\t\t\t\t\tpos = writeString(this.topics[i], topicStrLength[i], byteStream, pos);\n\t\t\t\t\tbyteStream[pos++] = this.requestedQos[i];\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase MESSAGE_TYPE.UNSUBSCRIBE:\n\t\t\t\t// UNSUBSCRIBE has a list of topic strings\n\t\t\t\tfor (var i=0; i<this.topics.length; i++)\n\t\t\t\t\tpos = writeString(this.topics[i], topicStrLength[i], byteStream, pos);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t// Do nothing.\n\t\t}\n\n\t\treturn buffer;\n\t}\n\n\tfunction decodeMessage(input,pos) {\n\t\tvar startingPos = pos;\n\t\tvar first = input[pos];\n\t\tvar type = first >> 4;\n\t\tvar messageInfo = first &= 0x0f;\n\t\tpos += 1;\n\n\n\t\t// Decode the remaining length (MBI format)\n\n\t\tvar digit;\n\t\tvar remLength = 0;\n\t\tvar multiplier = 1;\n\t\tdo {\n\t\t\tif (pos == input.length) {\n\t\t\t\treturn [null,startingPos];\n\t\t\t}\n\t\t\tdigit = input[pos++];\n\t\t\tremLength += ((digit & 0x7F) * multiplier);\n\t\t\tmultiplier *= 128;\n\t\t} while ((digit & 0x80) != 0);\n\n\t\tvar endPos = pos+remLength;\n\t\tif (endPos > input.length) {\n\t\t\treturn [null,startingPos];\n\t\t}\n\n\t\tvar wireMessage = new WireMessage(type);\n\t\tswitch(type) {\n\t\t\tcase MESSAGE_TYPE.CONNACK:\n\t\t\t\tvar connectAcknowledgeFlags = input[pos++];\n\t\t\t\tif (connectAcknowledgeFlags & 0x01)\n\t\t\t\t\twireMessage.sessionPresent = true;\n\t\t\t\twireMessage.returnCode = input[pos++];\n\t\t\t\tbreak;\n\n\t\t\tcase MESSAGE_TYPE.PUBLISH:\n\t\t\t\tvar qos = (messageInfo >> 1) & 0x03;\n\n\t\t\t\tvar len = readUint16(input, pos);\n\t\t\t\tpos += 2;\n\t\t\t\tvar topicName = parseUTF8(input, pos, len);\n\t\t\t\tpos += len;\n\t\t\t\t// If QoS 1 or 2 there will be a messageIdentifier\n\t\t\t\tif (qos > 0) {\n\t\t\t\t\twireMessage.messageIdentifier = readUint16(input, pos);\n\t\t\t\t\tpos += 2;\n\t\t\t\t}\n\n\t\t\t\tvar message = new Paho.MQTT.Message(input.subarray(pos, endPos));\n\t\t\t\tif ((messageInfo & 0x01) == 0x01)\n\t\t\t\t\tmessage.retained = true;\n\t\t\t\tif ((messageInfo & 0x08) == 0x08)\n\t\t\t\t\tmessage.duplicate =  true;\n\t\t\t\tmessage.qos = qos;\n\t\t\t\tmessage.destinationName = topicName;\n\t\t\t\twireMessage.payloadMessage = message;\n\t\t\t\tbreak;\n\n\t\t\tcase  MESSAGE_TYPE.PUBACK:\n\t\t\tcase  MESSAGE_TYPE.PUBREC:\n\t\t\tcase  MESSAGE_TYPE.PUBREL:\n\t\t\tcase  MESSAGE_TYPE.PUBCOMP:\n\t\t\tcase  MESSAGE_TYPE.UNSUBACK:\n\t\t\t\twireMessage.messageIdentifier = readUint16(input, pos);\n\t\t\t\tbreak;\n\n\t\t\tcase  MESSAGE_TYPE.SUBACK:\n\t\t\t\twireMessage.messageIdentifier = readUint16(input, pos);\n\t\t\t\tpos += 2;\n\t\t\t\twireMessage.returnCode = input.subarray(pos, endPos);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\t;\n\t\t}\n\n\t\treturn [wireMessage,endPos];\n\t}\n\n\tfunction writeUint16(input, buffer, offset) {\n\t\tbuffer[offset++] = input >> 8;      //MSB\n\t\tbuffer[offset++] = input % 256;     //LSB\n\t\treturn offset;\n\t}\n\n\tfunction writeString(input, utf8Length, buffer, offset) {\n\t\toffset = writeUint16(utf8Length, buffer, offset);\n\t\tstringToUTF8(input, buffer, offset);\n\t\treturn offset + utf8Length;\n\t}\n\n\tfunction readUint16(buffer, offset) {\n\t\treturn 256*buffer[offset] + buffer[offset+1];\n\t}\n\n\t/**\n\t * Encodes an MQTT Multi-Byte Integer\n\t * @private\n\t */\n\tfunction encodeMBI(number) {\n\t\tvar output = new Array(1);\n\t\tvar numBytes = 0;\n\n\t\tdo {\n\t\t\tvar digit = number % 128;\n\t\t\tnumber = number >> 7;\n\t\t\tif (number > 0) {\n\t\t\t\tdigit |= 0x80;\n\t\t\t}\n\t\t\toutput[numBytes++] = digit;\n\t\t} while ( (number > 0) && (numBytes<4) );\n\n\t\treturn output;\n\t}\n\n\t/**\n\t * Takes a String and calculates its length in bytes when encoded in UTF8.\n\t * @private\n\t */\n\tfunction UTF8Length(input) {\n\t\tvar output = 0;\n\t\tfor (var i = 0; i<input.length; i++)\n\t\t{\n\t\t\tvar charCode = input.charCodeAt(i);\n\t\t\tif (charCode > 0x7FF)\n\t\t\t{\n\t\t\t\t// Surrogate pair means its a 4 byte character\n\t\t\t\tif (0xD800 <= charCode && charCode <= 0xDBFF)\n\t\t\t\t{\n\t\t\t\t\ti++;\n\t\t\t\t\toutput++;\n\t\t\t\t}\n\t\t\t\toutput +=3;\n\t\t\t}\n\t\t\telse if (charCode > 0x7F)\n\t\t\t\toutput +=2;\n\t\t\telse\n\t\t\t\toutput++;\n\t\t}\n\t\treturn output;\n\t}\n\n\t/**\n\t * Takes a String and writes it into an array as UTF8 encoded bytes.\n\t * @private\n\t */\n\tfunction stringToUTF8(input, output, start) {\n\t\tvar pos = start;\n\t\tfor (var i = 0; i<input.length; i++) {\n\t\t\tvar charCode = input.charCodeAt(i);\n\n\t\t\t// Check for a surrogate pair.\n\t\t\tif (0xD800 <= charCode && charCode <= 0xDBFF) {\n\t\t\t\tvar lowCharCode = input.charCodeAt(++i);\n\t\t\t\tif (isNaN(lowCharCode)) {\n\t\t\t\t\tthrow new Error(format(ERROR.MALFORMED_UNICODE, [charCode, lowCharCode]));\n\t\t\t\t}\n\t\t\t\tcharCode = ((charCode - 0xD800)<<10) + (lowCharCode - 0xDC00) + 0x10000;\n\n\t\t\t}\n\n\t\t\tif (charCode <= 0x7F) {\n\t\t\t\toutput[pos++] = charCode;\n\t\t\t} else if (charCode <= 0x7FF) {\n\t\t\t\toutput[pos++] = charCode>>6  & 0x1F | 0xC0;\n\t\t\t\toutput[pos++] = charCode     & 0x3F | 0x80;\n\t\t\t} else if (charCode <= 0xFFFF) {\n\t\t\t\toutput[pos++] = charCode>>12 & 0x0F | 0xE0;\n\t\t\t\toutput[pos++] = charCode>>6  & 0x3F | 0x80;\n\t\t\t\toutput[pos++] = charCode     & 0x3F | 0x80;\n\t\t\t} else {\n\t\t\t\toutput[pos++] = charCode>>18 & 0x07 | 0xF0;\n\t\t\t\toutput[pos++] = charCode>>12 & 0x3F | 0x80;\n\t\t\t\toutput[pos++] = charCode>>6  & 0x3F | 0x80;\n\t\t\t\toutput[pos++] = charCode     & 0x3F | 0x80;\n\t\t\t};\n\t\t}\n\t\treturn output;\n\t}\n\n\tfunction parseUTF8(input, offset, length) {\n\t\tvar output = \"\";\n\t\tvar utf16;\n\t\tvar pos = offset;\n\n\t\twhile (pos < offset+length)\n\t\t{\n\t\t\tvar byte1 = input[pos++];\n\t\t\tif (byte1 < 128)\n\t\t\t\tutf16 = byte1;\n\t\t\telse\n\t\t\t{\n\t\t\t\tvar byte2 = input[pos++]-128;\n\t\t\t\tif (byte2 < 0)\n\t\t\t\t\tthrow new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16),\"\"]));\n\t\t\t\tif (byte1 < 0xE0)             // 2 byte character\n\t\t\t\t\tutf16 = 64*(byte1-0xC0) + byte2;\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tvar byte3 = input[pos++]-128;\n\t\t\t\t\tif (byte3 < 0)\n\t\t\t\t\t\tthrow new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16)]));\n\t\t\t\t\tif (byte1 < 0xF0)        // 3 byte character\n\t\t\t\t\t\tutf16 = 4096*(byte1-0xE0) + 64*byte2 + byte3;\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tvar byte4 = input[pos++]-128;\n\t\t\t\t\t\tif (byte4 < 0)\n\t\t\t\t\t\t\tthrow new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)]));\n\t\t\t\t\t\tif (byte1 < 0xF8)        // 4 byte character\n\t\t\t\t\t\t\tutf16 = 262144*(byte1-0xF0) + 4096*byte2 + 64*byte3 + byte4;\n\t\t\t\t\t\telse                     // longer encodings are not supported\n\t\t\t\t\t\t\tthrow new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)]));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (utf16 > 0xFFFF)   // 4 byte character - express as a surrogate pair\n\t\t\t{\n\t\t\t\tutf16 -= 0x10000;\n\t\t\t\toutput += String.fromCharCode(0xD800 + (utf16 >> 10)); // lead character\n\t\t\t\tutf16 = 0xDC00 + (utf16 & 0x3FF);  // trail character\n\t\t\t}\n\t\t\toutput += String.fromCharCode(utf16);\n\t\t}\n\t\treturn output;\n\t}\n\n\t/**\n\t * Repeat keepalive requests, monitor responses.\n\t * @ignore\n\t */\n\tvar Pinger = function(client, window, keepAliveInterval) {\n\t\tthis._client = client;\n\t\tthis._window = window;\n\t\tthis._keepAliveInterval = keepAliveInterval*1000;\n\t\tthis.isReset = false;\n\n\t\tvar pingReq = new WireMessage(MESSAGE_TYPE.PINGREQ).encode();\n\n\t\tvar doTimeout = function (pinger) {\n\t\t\treturn function () {\n\t\t\t\treturn doPing.apply(pinger);\n\t\t\t};\n\t\t};\n\n\t\t/** @ignore */\n\t\tvar doPing = function() {\n\t\t\tif (!this.isReset) {\n\t\t\t\tthis._client._trace(\"Pinger.doPing\", \"Timed out\");\n\t\t\t\tthis._client._disconnected( ERROR.PING_TIMEOUT.code , format(ERROR.PING_TIMEOUT));\n\t\t\t} else {\n\t\t\t\tthis.isReset = false;\n\t\t\t\tthis._client._trace(\"Pinger.doPing\", \"send PINGREQ\");\n\t\t\t\tthis._client.socket.send(pingReq);\n\t\t\t\tthis.timeout = this._window.setTimeout(doTimeout(this), this._keepAliveInterval);\n\t\t\t}\n\t\t}\n\n\t\tthis.reset = function() {\n\t\t\tthis.isReset = true;\n\t\t\tthis._window.clearTimeout(this.timeout);\n\t\t\tif (this._keepAliveInterval > 0)\n\t\t\t\tthis.timeout = setTimeout(doTimeout(this), this._keepAliveInterval);\n\t\t}\n\n\t\tthis.cancel = function() {\n\t\t\tthis._window.clearTimeout(this.timeout);\n\t\t}\n\t};\n\n\t/**\n\t * Monitor request completion.\n\t * @ignore\n\t */\n\tvar Timeout = function(client, window, timeoutSeconds, action, args) {\n\t\tthis._window = window;\n\t\tif (!timeoutSeconds)\n\t\t\ttimeoutSeconds = 30;\n\n\t\tvar doTimeout = function (action, client, args) {\n\t\t\treturn function () {\n\t\t\t\treturn action.apply(client, args);\n\t\t\t};\n\t\t};\n\t\tthis.timeout = setTimeout(doTimeout(action, client, args), timeoutSeconds * 1000);\n\n\t\tthis.cancel = function() {\n\t\t\tthis._window.clearTimeout(this.timeout);\n\t\t}\n\t};\n\n\t/*\n\t * Internal implementation of the Websockets MQTT V3.1 client.\n\t *\n\t * @name Paho.MQTT.ClientImpl @constructor\n\t * @param {String} host the DNS nameof the webSocket host.\n\t * @param {Number} port the port number for that host.\n\t * @param {String} clientId the MQ client identifier.\n\t */\n\tvar ClientImpl = function (uri, host, port, path, clientId) {\n\t\t// Check dependencies are satisfied in this browser.\n\t\tif (!(\"WebSocket\" in global && global[\"WebSocket\"] !== null)) {\n\t\t\tthrow new Error(format(ERROR.UNSUPPORTED, [\"WebSocket\"]));\n\t\t}\n\t\tif (!(\"localStorage\" in global && global[\"localStorage\"] !== null)) {\n\t\t\tthrow new Error(format(ERROR.UNSUPPORTED, [\"localStorage\"]));\n\t\t}\n\t\tif (!(\"ArrayBuffer\" in global && global[\"ArrayBuffer\"] !== null)) {\n\t\t\tthrow new Error(format(ERROR.UNSUPPORTED, [\"ArrayBuffer\"]));\n\t\t}\n\t\tthis._trace(\"Paho.MQTT.Client\", uri, host, port, path, clientId);\n\n\t\tthis.host = host;\n\t\tthis.port = port;\n\t\tthis.path = path;\n\t\tthis.uri = uri;\n\t\tthis.clientId = clientId;\n\n\t\t// Local storagekeys are qualified with the following string.\n\t\t// The conditional inclusion of path in the key is for backward\n\t\t// compatibility to when the path was not configurable and assumed to\n\t\t// be /mqtt\n\t\tthis._localKey=host+\":\"+port+(path!=\"/mqtt\"?\":\"+path:\"\")+\":\"+clientId+\":\";\n\n\t\t// Create private instance-only message queue\n\t\t// Internal queue of messages to be sent, in sending order.\n\t\tthis._msg_queue = [];\n\n\t\t// Messages we have sent and are expecting a response for, indexed by their respective message ids.\n\t\tthis._sentMessages = {};\n\n\t\t// Messages we have received and acknowleged and are expecting a confirm message for\n\t\t// indexed by their respective message ids.\n\t\tthis._receivedMessages = {};\n\n\t\t// Internal list of callbacks to be executed when messages\n\t\t// have been successfully sent over web socket, e.g. disconnect\n\t\t// when it doesn't have to wait for ACK, just message is dispatched.\n\t\tthis._notify_msg_sent = {};\n\n\t\t// Unique identifier for SEND messages, incrementing\n\t\t// counter as messages are sent.\n\t\tthis._message_identifier = 1;\n\n\t\t// Used to determine the transmission sequence of stored sent messages.\n\t\tthis._sequence = 0;\n\n\n\t\t// Load the local state, if any, from the saved version, only restore state relevant to this client.\n\t\tfor (var key in localStorage)\n\t\t\tif (   key.indexOf(\"Sent:\"+this._localKey) == 0\n\t\t\t\t|| key.indexOf(\"Received:\"+this._localKey) == 0)\n\t\t\t\tthis.restore(key);\n\t};\n\n\t// Messaging Client public instance members.\n\tClientImpl.prototype.host;\n\tClientImpl.prototype.port;\n\tClientImpl.prototype.path;\n\tClientImpl.prototype.uri;\n\tClientImpl.prototype.clientId;\n\n\t// Messaging Client private instance members.\n\tClientImpl.prototype.socket;\n\t/* true once we have received an acknowledgement to a CONNECT packet. */\n\tClientImpl.prototype.connected = false;\n\t/* The largest message identifier allowed, may not be larger than 2**16 but\n\t * if set smaller reduces the maximum number of outbound messages allowed.\n\t */\n\tClientImpl.prototype.maxMessageIdentifier = 65536;\n\tClientImpl.prototype.connectOptions;\n\tClientImpl.prototype.hostIndex;\n\tClientImpl.prototype.onConnectionLost;\n\tClientImpl.prototype.onMessageDelivered;\n\tClientImpl.prototype.onMessageArrived;\n\tClientImpl.prototype.traceFunction;\n\tClientImpl.prototype._msg_queue = null;\n\tClientImpl.prototype._connectTimeout;\n\t/* The sendPinger monitors how long we allow before we send data to prove to the server that we are alive. */\n\tClientImpl.prototype.sendPinger = null;\n\t/* The receivePinger monitors how long we allow before we require evidence that the server is alive. */\n\tClientImpl.prototype.receivePinger = null;\n\n\tClientImpl.prototype.receiveBuffer = null;\n\n\tClientImpl.prototype._traceBuffer = null;\n\tClientImpl.prototype._MAX_TRACE_ENTRIES = 100;\n\n\tClientImpl.prototype.connect = function (connectOptions) {\n\t\tvar connectOptionsMasked = this._traceMask(connectOptions, \"password\");\n\t\tthis._trace(\"Client.connect\", connectOptionsMasked, this.socket, this.connected);\n\n\t\tif (this.connected)\n\t\t\tthrow new Error(format(ERROR.INVALID_STATE, [\"already connected\"]));\n\t\tif (this.socket)\n\t\t\tthrow new Error(format(ERROR.INVALID_STATE, [\"already connected\"]));\n\n\t\tthis.connectOptions = connectOptions;\n\n\t\tif (connectOptions.uris) {\n\t\t\tthis.hostIndex = 0;\n\t\t\tthis._doConnect(connectOptions.uris[0]);\n\t\t} else {\n\t\t\tthis._doConnect(this.uri);\n\t\t}\n\n\t};\n\n\tClientImpl.prototype.subscribe = function (filter, subscribeOptions) {\n\t\tthis._trace(\"Client.subscribe\", filter, subscribeOptions);\n\n\t\tif (!this.connected)\n\t\t\tthrow new Error(format(ERROR.INVALID_STATE, [\"not connected\"]));\n\n\t\tvar wireMessage = new WireMessage(MESSAGE_TYPE.SUBSCRIBE);\n\t\twireMessage.topics=[filter];\n\t\tif (subscribeOptions.qos != undefined)\n\t\t\twireMessage.requestedQos = [subscribeOptions.qos];\n\t\telse\n\t\t\twireMessage.requestedQos = [0];\n\n\t\tif (subscribeOptions.onSuccess) {\n\t\t\twireMessage.onSuccess = function(grantedQos) {subscribeOptions.onSuccess({invocationContext:subscribeOptions.invocationContext,grantedQos:grantedQos});};\n\t\t}\n\n\t\tif (subscribeOptions.onFailure) {\n\t\t\twireMessage.onFailure = function(errorCode) {subscribeOptions.onFailure({invocationContext:subscribeOptions.invocationContext,errorCode:errorCode});};\n\t\t}\n\n\t\tif (subscribeOptions.timeout) {\n\t\t\twireMessage.timeOut = new Timeout(this, window, subscribeOptions.timeout, subscribeOptions.onFailure\n\t\t\t\t, [{invocationContext:subscribeOptions.invocationContext,\n\t\t\t\t\terrorCode:ERROR.SUBSCRIBE_TIMEOUT.code,\n\t\t\t\t\terrorMessage:format(ERROR.SUBSCRIBE_TIMEOUT)}]);\n\t\t}\n\n\t\t// All subscriptions return a SUBACK.\n\t\tthis._requires_ack(wireMessage);\n\t\tthis._schedule_message(wireMessage);\n\t};\n\n\t/** @ignore */\n\tClientImpl.prototype.unsubscribe = function(filter, unsubscribeOptions) {\n\t\tthis._trace(\"Client.unsubscribe\", filter, unsubscribeOptions);\n\n\t\tif (!this.connected)\n\t\t\tthrow new Error(format(ERROR.INVALID_STATE, [\"not connected\"]));\n\n\t\tvar wireMessage = new WireMessage(MESSAGE_TYPE.UNSUBSCRIBE);\n\t\twireMessage.topics = [filter];\n\n\t\tif (unsubscribeOptions.onSuccess) {\n\t\t\twireMessage.callback = function() {unsubscribeOptions.onSuccess({invocationContext:unsubscribeOptions.invocationContext});};\n\t\t}\n\t\tif (unsubscribeOptions.timeout) {\n\t\t\twireMessage.timeOut = new Timeout(this, window, unsubscribeOptions.timeout, unsubscribeOptions.onFailure\n\t\t\t\t, [{invocationContext:unsubscribeOptions.invocationContext,\n\t\t\t\t\terrorCode:ERROR.UNSUBSCRIBE_TIMEOUT.code,\n\t\t\t\t\terrorMessage:format(ERROR.UNSUBSCRIBE_TIMEOUT)}]);\n\t\t}\n\n\t\t// All unsubscribes return a SUBACK.\n\t\tthis._requires_ack(wireMessage);\n\t\tthis._schedule_message(wireMessage);\n\t};\n\n\tClientImpl.prototype.send = function (message) {\n\t\tthis._trace(\"Client.send\", message);\n\n\t\tif (!this.connected)\n\t\t\tthrow new Error(format(ERROR.INVALID_STATE, [\"not connected\"]));\n\n\t\twireMessage = new WireMessage(MESSAGE_TYPE.PUBLISH);\n\t\twireMessage.payloadMessage = message;\n\n\t\tif (message.qos > 0)\n\t\t\tthis._requires_ack(wireMessage);\n\t\telse if (this.onMessageDelivered)\n\t\t\tthis._notify_msg_sent[wireMessage] = this.onMessageDelivered(wireMessage.payloadMessage);\n\t\tthis._schedule_message(wireMessage);\n\t};\n\n\tClientImpl.prototype.disconnect = function () {\n\t\tthis._trace(\"Client.disconnect\");\n\n\t\tif (!this.socket)\n\t\t\tthrow new Error(format(ERROR.INVALID_STATE, [\"not connecting or connected\"]));\n\n\t\twireMessage = new WireMessage(MESSAGE_TYPE.DISCONNECT);\n\n\t\t// Run the disconnected call back as soon as the message has been sent,\n\t\t// in case of a failure later on in the disconnect processing.\n\t\t// as a consequence, the _disconected call back may be run several times.\n\t\tthis._notify_msg_sent[wireMessage] = scope(this._disconnected, this);\n\n\t\tthis._schedule_message(wireMessage);\n\t};\n\n\tClientImpl.prototype.getTraceLog = function () {\n\t\tif ( this._traceBuffer !== null ) {\n\t\t\tthis._trace(\"Client.getTraceLog\", new Date());\n\t\t\tthis._trace(\"Client.getTraceLog in flight messages\", this._sentMessages.length);\n\t\t\tfor (var key in this._sentMessages)\n\t\t\t\tthis._trace(\"_sentMessages \",key, this._sentMessages[key]);\n\t\t\tfor (var key in this._receivedMessages)\n\t\t\t\tthis._trace(\"_receivedMessages \",key, this._receivedMessages[key]);\n\n\t\t\treturn this._traceBuffer;\n\t\t}\n\t};\n\n\tClientImpl.prototype.startTrace = function () {\n\t\tif ( this._traceBuffer === null ) {\n\t\t\tthis._traceBuffer = [];\n\t\t}\n\t\tthis._trace(\"Client.startTrace\", new Date(), version);\n\t};\n\n\tClientImpl.prototype.stopTrace = function () {\n\t\tdelete this._traceBuffer;\n\t};\n\n\tClientImpl.prototype._doConnect = function (wsurl) {\n\t\t// When the socket is open, this client will send the CONNECT WireMessage using the saved parameters.\n\t\tif (this.connectOptions.useSSL) {\n\t\t\tvar uriParts = wsurl.split(\":\");\n\t\t\turiParts[0] = \"wss\";\n\t\t\twsurl = uriParts.join(\":\");\n\t\t}\n\t\tthis.connected = false;\n\t\tif (this.connectOptions.mqttVersion < 4) {\n\t\t\tthis.socket = new WebSocket(wsurl, [\"mqttv3.1\"]);\n\t\t} else {\n\t\t\tthis.socket = new WebSocket(wsurl, [\"mqtt\"]);\n\t\t}\n\t\tthis.socket.binaryType = 'arraybuffer';\n\n\t\tthis.socket.onopen = scope(this._on_socket_open, this);\n\t\tthis.socket.onmessage = scope(this._on_socket_message, this);\n\t\tthis.socket.onerror = scope(this._on_socket_error, this);\n\t\tthis.socket.onclose = scope(this._on_socket_close, this);\n\n\t\tthis.sendPinger = new Pinger(this, window, this.connectOptions.keepAliveInterval);\n\t\tthis.receivePinger = new Pinger(this, window, this.connectOptions.keepAliveInterval);\n\n\t\tthis._connectTimeout = new Timeout(this, window, this.connectOptions.timeout, this._disconnected,  [ERROR.CONNECT_TIMEOUT.code, format(ERROR.CONNECT_TIMEOUT)]);\n\t};\n\n\n\t// Schedule a new message to be sent over the WebSockets\n\t// connection. CONNECT messages cause WebSocket connection\n\t// to be started. All other messages are queued internally\n\t// until this has happened. When WS connection starts, process\n\t// all outstanding messages.\n\tClientImpl.prototype._schedule_message = function (message) {\n\t\tthis._msg_queue.push(message);\n\t\t// Process outstanding messages in the queue if we have an  open socket, and have received CONNACK.\n\t\tif (this.connected) {\n\t\t\tthis._process_queue();\n\t\t}\n\t};\n\n\tClientImpl.prototype.store = function(prefix, wireMessage) {\n\t\tvar storedMessage = {type:wireMessage.type, messageIdentifier:wireMessage.messageIdentifier, version:1};\n\n\t\tswitch(wireMessage.type) {\n\t\t\tcase MESSAGE_TYPE.PUBLISH:\n\t\t\t\tif(wireMessage.pubRecReceived)\n\t\t\t\t\tstoredMessage.pubRecReceived = true;\n\n\t\t\t\t// Convert the payload to a hex string.\n\t\t\t\tstoredMessage.payloadMessage = {};\n\t\t\t\tvar hex = \"\";\n\t\t\t\tvar messageBytes = wireMessage.payloadMessage.payloadBytes;\n\t\t\t\tfor (var i=0; i<messageBytes.length; i++) {\n\t\t\t\t\tif (messageBytes[i] <= 0xF)\n\t\t\t\t\t\thex = hex+\"0\"+messageBytes[i].toString(16);\n\t\t\t\t\telse\n\t\t\t\t\t\thex = hex+messageBytes[i].toString(16);\n\t\t\t\t}\n\t\t\t\tstoredMessage.payloadMessage.payloadHex = hex;\n\n\t\t\t\tstoredMessage.payloadMessage.qos = wireMessage.payloadMessage.qos;\n\t\t\t\tstoredMessage.payloadMessage.destinationName = wireMessage.payloadMessage.destinationName;\n\t\t\t\tif (wireMessage.payloadMessage.duplicate)\n\t\t\t\t\tstoredMessage.payloadMessage.duplicate = true;\n\t\t\t\tif (wireMessage.payloadMessage.retained)\n\t\t\t\t\tstoredMessage.payloadMessage.retained = true;\n\n\t\t\t\t// Add a sequence number to sent messages.\n\t\t\t\tif ( prefix.indexOf(\"Sent:\") == 0 ) {\n\t\t\t\t\tif ( wireMessage.sequence === undefined )\n\t\t\t\t\t\twireMessage.sequence = ++this._sequence;\n\t\t\t\t\tstoredMessage.sequence = wireMessage.sequence;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tthrow Error(format(ERROR.INVALID_STORED_DATA, [key, storedMessage]));\n\t\t}\n\t\tlocalStorage.setItem(prefix+this._localKey+wireMessage.messageIdentifier, JSON.stringify(storedMessage));\n\t};\n\n\tClientImpl.prototype.restore = function(key) {\n\t\tvar value = localStorage.getItem(key);\n\t\tvar storedMessage = JSON.parse(value);\n\n\t\tvar wireMessage = new WireMessage(storedMessage.type, storedMessage);\n\n\t\tswitch(storedMessage.type) {\n\t\t\tcase MESSAGE_TYPE.PUBLISH:\n\t\t\t\t// Replace the payload message with a Message object.\n\t\t\t\tvar hex = storedMessage.payloadMessage.payloadHex;\n\t\t\t\tvar buffer = new ArrayBuffer((hex.length)/2);\n\t\t\t\tvar byteStream = new Uint8Array(buffer);\n\t\t\t\tvar i = 0;\n\t\t\t\twhile (hex.length >= 2) {\n\t\t\t\t\tvar x = parseInt(hex.substring(0, 2), 16);\n\t\t\t\t\thex = hex.substring(2, hex.length);\n\t\t\t\t\tbyteStream[i++] = x;\n\t\t\t\t}\n\t\t\t\tvar payloadMessage = new Paho.MQTT.Message(byteStream);\n\n\t\t\t\tpayloadMessage.qos = storedMessage.payloadMessage.qos;\n\t\t\t\tpayloadMessage.destinationName = storedMessage.payloadMessage.destinationName;\n\t\t\t\tif (storedMessage.payloadMessage.duplicate)\n\t\t\t\t\tpayloadMessage.duplicate = true;\n\t\t\t\tif (storedMessage.payloadMessage.retained)\n\t\t\t\t\tpayloadMessage.retained = true;\n\t\t\t\twireMessage.payloadMessage = payloadMessage;\n\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tthrow Error(format(ERROR.INVALID_STORED_DATA, [key, value]));\n\t\t}\n\n\t\tif (key.indexOf(\"Sent:\"+this._localKey) == 0) {\n\t\t\twireMessage.payloadMessage.duplicate = true;\n\t\t\tthis._sentMessages[wireMessage.messageIdentifier] = wireMessage;\n\t\t} else if (key.indexOf(\"Received:\"+this._localKey) == 0) {\n\t\t\tthis._receivedMessages[wireMessage.messageIdentifier] = wireMessage;\n\t\t}\n\t};\n\n\tClientImpl.prototype._process_queue = function () {\n\t\tvar message = null;\n\t\t// Process messages in order they were added\n\t\tvar fifo = this._msg_queue.reverse();\n\n\t\t// Send all queued messages down socket connection\n\t\twhile ((message = fifo.pop())) {\n\t\t\tthis._socket_send(message);\n\t\t\t// Notify listeners that message was successfully sent\n\t\t\tif (this._notify_msg_sent[message]) {\n\t\t\t\tthis._notify_msg_sent[message]();\n\t\t\t\tdelete this._notify_msg_sent[message];\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Expect an ACK response for this message. Add message to the set of in progress\n\t * messages and set an unused identifier in this message.\n\t * @ignore\n\t */\n\tClientImpl.prototype._requires_ack = function (wireMessage) {\n\t\tvar messageCount = Object.keys(this._sentMessages).length;\n\t\tif (messageCount > this.maxMessageIdentifier)\n\t\t\tthrow Error (\"Too many messages:\"+messageCount);\n\n\t\twhile(this._sentMessages[this._message_identifier] !== undefined) {\n\t\t\tthis._message_identifier++;\n\t\t}\n\t\twireMessage.messageIdentifier = this._message_identifier;\n\t\tthis._sentMessages[wireMessage.messageIdentifier] = wireMessage;\n\t\tif (wireMessage.type === MESSAGE_TYPE.PUBLISH) {\n\t\t\tthis.store(\"Sent:\", wireMessage);\n\t\t}\n\t\tif (this._message_identifier === this.maxMessageIdentifier) {\n\t\t\tthis._message_identifier = 1;\n\t\t}\n\t};\n\n\t/**\n\t * Called when the underlying websocket has been opened.\n\t * @ignore\n\t */\n\tClientImpl.prototype._on_socket_open = function () {\n\t\t// Create the CONNECT message object.\n\t\tvar wireMessage = new WireMessage(MESSAGE_TYPE.CONNECT, this.connectOptions);\n\t\twireMessage.clientId = this.clientId;\n\t\tthis._socket_send(wireMessage);\n\t};\n\n\t/**\n\t * Called when the underlying websocket has received a complete packet.\n\t * @ignore\n\t */\n\tClientImpl.prototype._on_socket_message = function (event) {\n\t\tthis._trace(\"Client._on_socket_message\", event.data);\n\t\t// Reset the receive ping timer, we now have evidence the server is alive.\n\t\tthis.receivePinger.reset();\n\t\tvar messages = this._deframeMessages(event.data);\n\t\tfor (var i = 0; i < messages.length; i+=1) {\n\t\t\tthis._handleMessage(messages[i]);\n\t\t}\n\t}\n\n\tClientImpl.prototype._deframeMessages = function(data) {\n\t\tvar byteArray = new Uint8Array(data);\n\t\tif (this.receiveBuffer) {\n\t\t\tvar newData = new Uint8Array(this.receiveBuffer.length+byteArray.length);\n\t\t\tnewData.set(this.receiveBuffer);\n\t\t\tnewData.set(byteArray,this.receiveBuffer.length);\n\t\t\tbyteArray = newData;\n\t\t\tdelete this.receiveBuffer;\n\t\t}\n\t\ttry {\n\t\t\tvar offset = 0;\n\t\t\tvar messages = [];\n\t\t\twhile(offset < byteArray.length) {\n\t\t\t\tvar result = decodeMessage(byteArray,offset);\n\t\t\t\tvar wireMessage = result[0];\n\t\t\t\toffset = result[1];\n\t\t\t\tif (wireMessage !== null) {\n\t\t\t\t\tmessages.push(wireMessage);\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (offset < byteArray.length) {\n\t\t\t\tthis.receiveBuffer = byteArray.subarray(offset);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()]));\n\t\t\treturn;\n\t\t}\n\t\treturn messages;\n\t}\n\n\tClientImpl.prototype._handleMessage = function(wireMessage) {\n\n\t\tthis._trace(\"Client._handleMessage\", wireMessage);\n\n\t\ttry {\n\t\t\tswitch(wireMessage.type) {\n\t\t\t\tcase MESSAGE_TYPE.CONNACK:\n\t\t\t\t\tthis._connectTimeout.cancel();\n\n\t\t\t\t\t// If we have started using clean session then clear up the local state.\n\t\t\t\t\tif (this.connectOptions.cleanSession) {\n\t\t\t\t\t\tfor (var key in this._sentMessages) {\n\t\t\t\t\t\t\tvar sentMessage = this._sentMessages[key];\n\t\t\t\t\t\t\tlocalStorage.removeItem(\"Sent:\"+this._localKey+sentMessage.messageIdentifier);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._sentMessages = {};\n\n\t\t\t\t\t\tfor (var key in this._receivedMessages) {\n\t\t\t\t\t\t\tvar receivedMessage = this._receivedMessages[key];\n\t\t\t\t\t\t\tlocalStorage.removeItem(\"Received:\"+this._localKey+receivedMessage.messageIdentifier);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._receivedMessages = {};\n\t\t\t\t\t}\n\t\t\t\t\t// Client connected and ready for business.\n\t\t\t\t\tif (wireMessage.returnCode === 0) {\n\t\t\t\t\t\tthis.connected = true;\n\t\t\t\t\t\t// Jump to the end of the list of uris and stop looking for a good host.\n\t\t\t\t\t\tif (this.connectOptions.uris)\n\t\t\t\t\t\t\tthis.hostIndex = this.connectOptions.uris.length;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis._disconnected(ERROR.CONNACK_RETURNCODE.code , format(ERROR.CONNACK_RETURNCODE, [wireMessage.returnCode, CONNACK_RC[wireMessage.returnCode]]));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Resend messages.\n\t\t\t\t\tvar sequencedMessages = new Array();\n\t\t\t\t\tfor (var msgId in this._sentMessages) {\n\t\t\t\t\t\tif (this._sentMessages.hasOwnProperty(msgId))\n\t\t\t\t\t\t\tsequencedMessages.push(this._sentMessages[msgId]);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Sort sentMessages into the original sent order.\n\t\t\t\t\tvar sequencedMessages = sequencedMessages.sort(function(a,b) {return a.sequence - b.sequence;} );\n\t\t\t\t\tfor (var i=0, len=sequencedMessages.length; i<len; i++) {\n\t\t\t\t\t\tvar sentMessage = sequencedMessages[i];\n\t\t\t\t\t\tif (sentMessage.type == MESSAGE_TYPE.PUBLISH && sentMessage.pubRecReceived) {\n\t\t\t\t\t\t\tvar pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier:sentMessage.messageIdentifier});\n\t\t\t\t\t\t\tthis._schedule_message(pubRelMessage);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis._schedule_message(sentMessage);\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Execute the connectOptions.onSuccess callback if there is one.\n\t\t\t\t\tif (this.connectOptions.onSuccess) {\n\t\t\t\t\t\tthis.connectOptions.onSuccess({invocationContext:this.connectOptions.invocationContext});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Process all queued messages now that the connection is established.\n\t\t\t\t\tthis._process_queue();\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase MESSAGE_TYPE.PUBLISH:\n\t\t\t\t\tthis._receivePublish(wireMessage);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase MESSAGE_TYPE.PUBACK:\n\t\t\t\t\tvar sentMessage = this._sentMessages[wireMessage.messageIdentifier];\n\t\t\t\t\t// If this is a re flow of a PUBACK after we have restarted receivedMessage will not exist.\n\t\t\t\t\tif (sentMessage) {\n\t\t\t\t\t\tdelete this._sentMessages[wireMessage.messageIdentifier];\n\t\t\t\t\t\tlocalStorage.removeItem(\"Sent:\"+this._localKey+wireMessage.messageIdentifier);\n\t\t\t\t\t\tif (this.onMessageDelivered)\n\t\t\t\t\t\t\tthis.onMessageDelivered(sentMessage.payloadMessage);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase MESSAGE_TYPE.PUBREC:\n\t\t\t\t\tvar sentMessage = this._sentMessages[wireMessage.messageIdentifier];\n\t\t\t\t\t// If this is a re flow of a PUBREC after we have restarted receivedMessage will not exist.\n\t\t\t\t\tif (sentMessage) {\n\t\t\t\t\t\tsentMessage.pubRecReceived = true;\n\t\t\t\t\t\tvar pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier:wireMessage.messageIdentifier});\n\t\t\t\t\t\tthis.store(\"Sent:\", sentMessage);\n\t\t\t\t\t\tthis._schedule_message(pubRelMessage);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase MESSAGE_TYPE.PUBREL:\n\t\t\t\t\tvar receivedMessage = this._receivedMessages[wireMessage.messageIdentifier];\n\t\t\t\t\tlocalStorage.removeItem(\"Received:\"+this._localKey+wireMessage.messageIdentifier);\n\t\t\t\t\t// If this is a re flow of a PUBREL after we have restarted receivedMessage will not exist.\n\t\t\t\t\tif (receivedMessage) {\n\t\t\t\t\t\tthis._receiveMessage(receivedMessage);\n\t\t\t\t\t\tdelete this._receivedMessages[wireMessage.messageIdentifier];\n\t\t\t\t\t}\n\t\t\t\t\t// Always flow PubComp, we may have previously flowed PubComp but the server lost it and restarted.\n\t\t\t\t\tvar pubCompMessage = new WireMessage(MESSAGE_TYPE.PUBCOMP, {messageIdentifier:wireMessage.messageIdentifier});\n\t\t\t\t\tthis._schedule_message(pubCompMessage);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase MESSAGE_TYPE.PUBCOMP:\n\t\t\t\t\tvar sentMessage = this._sentMessages[wireMessage.messageIdentifier];\n\t\t\t\t\tdelete this._sentMessages[wireMessage.messageIdentifier];\n\t\t\t\t\tlocalStorage.removeItem(\"Sent:\"+this._localKey+wireMessage.messageIdentifier);\n\t\t\t\t\tif (this.onMessageDelivered)\n\t\t\t\t\t\tthis.onMessageDelivered(sentMessage.payloadMessage);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase MESSAGE_TYPE.SUBACK:\n\t\t\t\t\tvar sentMessage = this._sentMessages[wireMessage.messageIdentifier];\n\t\t\t\t\tif (sentMessage) {\n\t\t\t\t\t\tif(sentMessage.timeOut)\n\t\t\t\t\t\t\tsentMessage.timeOut.cancel();\n\t\t\t\t\t\t// This will need to be fixed when we add multiple topic support\n\t\t\t\t\t\tif (wireMessage.returnCode[0] === 0x80) {\n\t\t\t\t\t\t\tif (sentMessage.onFailure) {\n\t\t\t\t\t\t\t\tsentMessage.onFailure(wireMessage.returnCode);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (sentMessage.onSuccess) {\n\t\t\t\t\t\t\tsentMessage.onSuccess(wireMessage.returnCode);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdelete this._sentMessages[wireMessage.messageIdentifier];\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase MESSAGE_TYPE.UNSUBACK:\n\t\t\t\t\tvar sentMessage = this._sentMessages[wireMessage.messageIdentifier];\n\t\t\t\t\tif (sentMessage) {\n\t\t\t\t\t\tif (sentMessage.timeOut)\n\t\t\t\t\t\t\tsentMessage.timeOut.cancel();\n\t\t\t\t\t\tif (sentMessage.callback) {\n\t\t\t\t\t\t\tsentMessage.callback();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdelete this._sentMessages[wireMessage.messageIdentifier];\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase MESSAGE_TYPE.PINGRESP:\n\t\t\t\t\t/* The sendPinger or receivePinger may have sent a ping, the receivePinger has already been reset. */\n\t\t\t\t\tthis.sendPinger.reset();\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase MESSAGE_TYPE.DISCONNECT:\n\t\t\t\t\t// Clients do not expect to receive disconnect packets.\n\t\t\t\t\tthis._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code , format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type]));\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tthis._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code , format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type]));\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthis._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()]));\n\t\t\treturn;\n\t\t}\n\t};\n\n\t/** @ignore */\n\tClientImpl.prototype._on_socket_error = function (error) {\n\t\tthis._disconnected(ERROR.SOCKET_ERROR.code , format(ERROR.SOCKET_ERROR, [error.data]));\n\t};\n\n\t/** @ignore */\n\tClientImpl.prototype._on_socket_close = function () {\n\t\tthis._disconnected(ERROR.SOCKET_CLOSE.code , format(ERROR.SOCKET_CLOSE));\n\t};\n\n\t/** @ignore */\n\tClientImpl.prototype._socket_send = function (wireMessage) {\n\n\t\tif (wireMessage.type == 1) {\n\t\t\tvar wireMessageMasked = this._traceMask(wireMessage, \"password\");\n\t\t\tthis._trace(\"Client._socket_send\", wireMessageMasked);\n\t\t}\n\t\telse this._trace(\"Client._socket_send\", wireMessage);\n\n\t\tthis.socket.send(wireMessage.encode());\n\t\t/* We have proved to the server we are alive. */\n\t\tthis.sendPinger.reset();\n\t};\n\n\t/** @ignore */\n\tClientImpl.prototype._receivePublish = function (wireMessage) {\n\t\tswitch(wireMessage.payloadMessage.qos) {\n\t\t\tcase \"undefined\":\n\t\t\tcase 0:\n\t\t\t\tthis._receiveMessage(wireMessage);\n\t\t\t\tbreak;\n\n\t\t\tcase 1:\n\t\t\t\tvar pubAckMessage = new WireMessage(MESSAGE_TYPE.PUBACK, {messageIdentifier:wireMessage.messageIdentifier});\n\t\t\t\tthis._schedule_message(pubAckMessage);\n\t\t\t\tthis._receiveMessage(wireMessage);\n\t\t\t\tbreak;\n\n\t\t\tcase 2:\n\t\t\t\tthis._receivedMessages[wireMessage.messageIdentifier] = wireMessage;\n\t\t\t\tthis.store(\"Received:\", wireMessage);\n\t\t\t\tvar pubRecMessage = new WireMessage(MESSAGE_TYPE.PUBREC, {messageIdentifier:wireMessage.messageIdentifier});\n\t\t\t\tthis._schedule_message(pubRecMessage);\n\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tthrow Error(\"Invaild qos=\"+wireMmessage.payloadMessage.qos);\n\t\t};\n\t};\n\n\t/** @ignore */\n\tClientImpl.prototype._receiveMessage = function (wireMessage) {\n\t\tif (this.onMessageArrived) {\n\t\t\tthis.onMessageArrived(wireMessage.payloadMessage);\n\t\t}\n\t};\n\n\t/**\n\t * Client has disconnected either at its own request or because the server\n\t * or transfer disconnected it. Remove all non-durable state.\n\t * @param {errorCode} [number] the error number.\n\t * @param {errorText} [string] the error text.\n\t * @ignore\n\t */\n\tClientImpl.prototype._disconnected = function (errorCode, errorText) {\n\t\tthis._trace(\"Client._disconnected\", errorCode, errorText);\n\n\t\tthis.sendPinger.cancel();\n\t\tthis.receivePinger.cancel();\n\t\tif (this._connectTimeout)\n\t\t\tthis._connectTimeout.cancel();\n\t\t// Clear message buffers.\n\t\tthis._msg_queue = [];\n\t\tthis._notify_msg_sent = {};\n\n\t\tif (this.socket) {\n\t\t\t// Cancel all socket callbacks so that they cannot be driven again by this socket.\n\t\t\tthis.socket.onopen = null;\n\t\t\tthis.socket.onmessage = null;\n\t\t\tthis.socket.onerror = null;\n\t\t\tthis.socket.onclose = null;\n\t\t\tif (this.socket.readyState === 1)\n\t\t\t\tthis.socket.close();\n\t\t\tdelete this.socket;\n\t\t}\n\n\t\tif (this.connectOptions.uris && this.hostIndex < this.connectOptions.uris.length-1) {\n\t\t\t// Try the next host.\n\t\t\tthis.hostIndex++;\n\t\t\tthis._doConnect(this.connectOptions.uris[this.hostIndex]);\n\n\t\t} else {\n\n\t\t\tif (errorCode === undefined) {\n\t\t\t\terrorCode = ERROR.OK.code;\n\t\t\t\terrorText = format(ERROR.OK);\n\t\t\t}\n\n\t\t\t// Run any application callbacks last as they may attempt to reconnect and hence create a new socket.\n\t\t\tif (this.connected) {\n\t\t\t\tthis.connected = false;\n\t\t\t\t// Execute the connectionLostCallback if there is one, and we were connected.\n\t\t\t\tif (this.onConnectionLost)\n\t\t\t\t\tthis.onConnectionLost({errorCode:errorCode, errorMessage:errorText});\n\t\t\t} else {\n\t\t\t\t// Otherwise we never had a connection, so indicate that the connect has failed.\n\t\t\t\tif (this.connectOptions.mqttVersion === 4 && this.connectOptions.mqttVersionExplicit === false) {\n\t\t\t\t\tthis._trace(\"Failed to connect V4, dropping back to V3\")\n\t\t\t\t\tthis.connectOptions.mqttVersion = 3;\n\t\t\t\t\tif (this.connectOptions.uris) {\n\t\t\t\t\t\tthis.hostIndex = 0;\n\t\t\t\t\t\tthis._doConnect(this.connectOptions.uris[0]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis._doConnect(this.uri);\n\t\t\t\t\t}\n\t\t\t\t} else if(this.connectOptions.onFailure) {\n\t\t\t\t\tthis.connectOptions.onFailure({invocationContext:this.connectOptions.invocationContext, errorCode:errorCode, errorMessage:errorText});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\t/** @ignore */\n\tClientImpl.prototype._trace = function () {\n\t\t// Pass trace message back to client's callback function\n\t\tif (this.traceFunction) {\n\t\t\tfor (var i in arguments)\n\t\t\t{\n\t\t\t\tif (typeof arguments[i] !== \"undefined\")\n\t\t\t\t\targuments[i] = JSON.stringify(arguments[i]);\n\t\t\t}\n\t\t\tvar record = Array.prototype.slice.call(arguments).join(\"\");\n\t\t\tthis.traceFunction ({severity: \"Debug\", message: record\t});\n\t\t}\n\n\t\t//buffer style trace\n\t\tif ( this._traceBuffer !== null ) {\n\t\t\tfor (var i = 0, max = arguments.length; i < max; i++) {\n\t\t\t\tif ( this._traceBuffer.length == this._MAX_TRACE_ENTRIES ) {\n\t\t\t\t\tthis._traceBuffer.shift();\n\t\t\t\t}\n\t\t\t\tif (i === 0) this._traceBuffer.push(arguments[i]);\n\t\t\t\telse if (typeof arguments[i] === \"undefined\" ) this._traceBuffer.push(arguments[i]);\n\t\t\t\telse this._traceBuffer.push(\"  \"+JSON.stringify(arguments[i]));\n\t\t\t};\n\t\t};\n\t};\n\n\t/** @ignore */\n\tClientImpl.prototype._traceMask = function (traceObject, masked) {\n\t\tvar traceObjectMasked = {};\n\t\tfor (var attr in traceObject) {\n\t\t\tif (traceObject.hasOwnProperty(attr)) {\n\t\t\t\tif (attr == masked)\n\t\t\t\t\ttraceObjectMasked[attr] = \"******\";\n\t\t\t\telse\n\t\t\t\t\ttraceObjectMasked[attr] = traceObject[attr];\n\t\t\t}\n\t\t}\n\t\treturn traceObjectMasked;\n\t};\n\n\t// ------------------------------------------------------------------------\n\t// Public Programming interface.\n\t// ------------------------------------------------------------------------\n\n\t/**\n\t * The JavaScript application communicates to the server using a {@link Paho.MQTT.Client} object.\n\t * <p>\n\t * Most applications will create just one Client object and then call its connect() method,\n\t * however applications can create more than one Client object if they wish.\n\t * In this case the combination of host, port and clientId attributes must be different for each Client object.\n\t * <p>\n\t * The send, subscribe and unsubscribe methods are implemented as asynchronous JavaScript methods\n\t * (even though the underlying protocol exchange might be synchronous in nature).\n\t * This means they signal their completion by calling back to the application,\n\t * via Success or Failure callback functions provided by the application on the method in question.\n\t * Such callbacks are called at most once per method invocation and do not persist beyond the lifetime\n\t * of the script that made the invocation.\n\t * <p>\n\t * In contrast there are some callback functions, most notably <i>onMessageArrived</i>,\n\t * that are defined on the {@link Paho.MQTT.Client} object.\n\t * These may get called multiple times, and aren't directly related to specific method invocations made by the client.\n\t *\n\t * @name Paho.MQTT.Client\n\t *\n\t * @constructor\n\t *\n\t * @param {string} host - the address of the messaging server, as a fully qualified WebSocket URI, as a DNS name or dotted decimal IP address.\n\t * @param {number} port - the port number to connect to - only required if host is not a URI\n\t * @param {string} path - the path on the host to connect to - only used if host is not a URI. Default: '/mqtt'.\n\t * @param {string} clientId - the Messaging client identifier, between 1 and 23 characters in length.\n\t *\n\t * @property {string} host - <i>read only</i> the server's DNS hostname or dotted decimal IP address.\n\t * @property {number} port - <i>read only</i> the server's port.\n\t * @property {string} path - <i>read only</i> the server's path.\n\t * @property {string} clientId - <i>read only</i> used when connecting to the server.\n\t * @property {function} onConnectionLost - called when a connection has been lost.\n\t *                            after a connect() method has succeeded.\n\t *                            Establish the call back used when a connection has been lost. The connection may be\n\t *                            lost because the client initiates a disconnect or because the server or transfer\n\t *                            cause the client to be disconnected. The disconnect call back may be called without\n\t *                            the connectionComplete call back being invoked if, for example the client fails to\n\t *                            connect.\n\t *                            A single response object parameter is passed to the onConnectionLost callback containing the following fields:\n\t *                            <ol>\n\t *                            <li>errorCode\n\t *                            <li>errorMessage\n\t *                            </ol>\n\t * @property {function} onMessageDelivered called when a message has been delivered.\n\t *                            All processing that this Client will ever do has been completed. So, for example,\n\t *                            in the case of a Qos=2 message sent by this client, the PubComp flow has been received from the server\n\t *                            and the message has been removed from persistent storage before this callback is invoked.\n\t *                            Parameters passed to the onMessageDelivered callback are:\n\t *                            <ol>\n\t *                            <li>{@link Paho.MQTT.Message} that was delivered.\n\t *                            </ol>\n\t * @property {function} onMessageArrived called when a message has arrived in this Paho.MQTT.client.\n\t *                            Parameters passed to the onMessageArrived callback are:\n\t *                            <ol>\n\t *                            <li>{@link Paho.MQTT.Message} that has arrived.\n\t *                            </ol>\n\t */\n\tvar Client = function (host, port, path, clientId) {\n\n\t\tvar uri;\n\n\t\tif (typeof host !== \"string\")\n\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof host, \"host\"]));\n\n\t\tif (arguments.length == 2) {\n\t\t\t// host: must be full ws:// uri\n\t\t\t// port: clientId\n\t\t\tclientId = port;\n\t\t\turi = host;\n\t\t\tvar match = uri.match(/^(wss?):\\/\\/((\\[(.+)\\])|([^\\/]+?))(:(\\d+))?(\\/.*)$/);\n\t\t\tif (match) {\n\t\t\t\thost = match[4]||match[2];\n\t\t\t\tport = parseInt(match[7]);\n\t\t\t\tpath = match[8];\n\t\t\t} else {\n\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT,[host,\"host\"]));\n\t\t\t}\n\t\t} else {\n\t\t\tif (arguments.length == 3) {\n\t\t\t\tclientId = path;\n\t\t\t\tpath = \"/mqtt\";\n\t\t\t}\n\t\t\tif (typeof port !== \"number\" || port < 0)\n\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof port, \"port\"]));\n\t\t\tif (typeof path !== \"string\")\n\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof path, \"path\"]));\n\n\t\t\tvar ipv6AddSBracket = (host.indexOf(\":\") != -1 && host.slice(0,1) != \"[\" && host.slice(-1) != \"]\");\n\t\t\turi = \"ws://\"+(ipv6AddSBracket?\"[\"+host+\"]\":host)+\":\"+port+path;\n\t\t}\n\n\t\tvar clientIdLength = 0;\n\t\tfor (var i = 0; i<clientId.length; i++) {\n\t\t\tvar charCode = clientId.charCodeAt(i);\n\t\t\tif (0xD800 <= charCode && charCode <= 0xDBFF)  {\n\t\t\t\ti++; // Surrogate pair.\n\t\t\t}\n\t\t\tclientIdLength++;\n\t\t}\n\t\tif (typeof clientId !== \"string\" || clientIdLength > 65535)\n\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [clientId, \"clientId\"]));\n\n\t\tvar client = new ClientImpl(uri, host, port, path, clientId);\n\t\tthis._getHost =  function() { return host; };\n\t\tthis._setHost = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };\n\n\t\tthis._getPort = function() { return port; };\n\t\tthis._setPort = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };\n\n\t\tthis._getPath = function() { return path; };\n\t\tthis._setPath = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };\n\n\t\tthis._getURI = function() { return uri; };\n\t\tthis._setURI = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };\n\n\t\tthis._getClientId = function() { return client.clientId; };\n\t\tthis._setClientId = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };\n\n\t\tthis._getOnConnectionLost = function() { return client.onConnectionLost; };\n\t\tthis._setOnConnectionLost = function(newOnConnectionLost) {\n\t\t\tif (typeof newOnConnectionLost === \"function\")\n\t\t\t\tclient.onConnectionLost = newOnConnectionLost;\n\t\t\telse\n\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnectionLost, \"onConnectionLost\"]));\n\t\t};\n\n\t\tthis._getOnMessageDelivered = function() { return client.onMessageDelivered; };\n\t\tthis._setOnMessageDelivered = function(newOnMessageDelivered) {\n\t\t\tif (typeof newOnMessageDelivered === \"function\")\n\t\t\t\tclient.onMessageDelivered = newOnMessageDelivered;\n\t\t\telse\n\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageDelivered, \"onMessageDelivered\"]));\n\t\t};\n\n\t\tthis._getOnMessageArrived = function() { return client.onMessageArrived; };\n\t\tthis._setOnMessageArrived = function(newOnMessageArrived) {\n\t\t\tif (typeof newOnMessageArrived === \"function\")\n\t\t\t\tclient.onMessageArrived = newOnMessageArrived;\n\t\t\telse\n\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageArrived, \"onMessageArrived\"]));\n\t\t};\n\n\t\tthis._getTrace = function() { return client.traceFunction; };\n\t\tthis._setTrace = function(trace) {\n\t\t\tif(typeof trace === \"function\"){\n\t\t\t\tclient.traceFunction = trace;\n\t\t\t}else{\n\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof trace, \"onTrace\"]));\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Connect this Messaging client to its server.\n\t\t *\n\t\t * @name Paho.MQTT.Client#connect\n\t\t * @function\n\t\t * @param {Object} connectOptions - attributes used with the connection.\n\t\t * @param {number} connectOptions.timeout - If the connect has not succeeded within this\n\t\t *                    number of seconds, it is deemed to have failed.\n\t\t *                    The default is 30 seconds.\n\t\t * @param {string} connectOptions.userName - Authentication username for this connection.\n\t\t * @param {string} connectOptions.password - Authentication password for this connection.\n\t\t * @param {Paho.MQTT.Message} connectOptions.willMessage - sent by the server when the client\n\t\t *                    disconnects abnormally.\n\t\t * @param {Number} connectOptions.keepAliveInterval - the server disconnects this client if\n\t\t *                    there is no activity for this number of seconds.\n\t\t *                    The default value of 60 seconds is assumed if not set.\n\t\t * @param {boolean} connectOptions.cleanSession - if true(default) the client and server\n\t\t *                    persistent state is deleted on successful connect.\n\t\t * @param {boolean} connectOptions.useSSL - if present and true, use an SSL Websocket connection.\n\t\t * @param {object} connectOptions.invocationContext - passed to the onSuccess callback or onFailure callback.\n\t\t * @param {function} connectOptions.onSuccess - called when the connect acknowledgement\n\t\t *                    has been received from the server.\n\t\t * A single response object parameter is passed to the onSuccess callback containing the following fields:\n\t\t * <ol>\n\t\t * <li>invocationContext as passed in to the onSuccess method in the connectOptions.\n\t\t * </ol>\n\t\t * @config {function} [onFailure] called when the connect request has failed or timed out.\n\t\t * A single response object parameter is passed to the onFailure callback containing the following fields:\n\t\t * <ol>\n\t\t * <li>invocationContext as passed in to the onFailure method in the connectOptions.\n\t\t * <li>errorCode a number indicating the nature of the error.\n\t\t * <li>errorMessage text describing the error.\n\t\t * </ol>\n\t\t * @config {Array} [hosts] If present this contains either a set of hostnames or fully qualified\n\t\t * WebSocket URIs (ws://example.com:1883/mqtt), that are tried in order in place\n\t\t * of the host and port paramater on the construtor. The hosts are tried one at at time in order until\n\t\t * one of then succeeds.\n\t\t * @config {Array} [ports] If present the set of ports matching the hosts. If hosts contains URIs, this property\n\t\t * is not used.\n\t\t * @throws {InvalidState} if the client is not in disconnected state. The client must have received connectionLost\n\t\t * or disconnected before calling connect for a second or subsequent time.\n\t\t */\n\t\tthis.connect = function (connectOptions) {\n\t\t\tconnectOptions = connectOptions || {} ;\n\t\t\tvalidate(connectOptions,  {timeout:\"number\",\n\t\t\t\tuserName:\"string\",\n\t\t\t\tpassword:\"string\",\n\t\t\t\twillMessage:\"object\",\n\t\t\t\tkeepAliveInterval:\"number\",\n\t\t\t\tcleanSession:\"boolean\",\n\t\t\t\tuseSSL:\"boolean\",\n\t\t\t\tinvocationContext:\"object\",\n\t\t\t\tonSuccess:\"function\",\n\t\t\t\tonFailure:\"function\",\n\t\t\t\thosts:\"object\",\n\t\t\t\tports:\"object\",\n\t\t\t\tmqttVersion:\"number\"});\n\n\t\t\t// If no keep alive interval is set, assume 60 seconds.\n\t\t\tif (connectOptions.keepAliveInterval === undefined)\n\t\t\t\tconnectOptions.keepAliveInterval = 60;\n\n\t\t\tif (connectOptions.mqttVersion > 4 || connectOptions.mqttVersion < 3) {\n\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.mqttVersion, \"connectOptions.mqttVersion\"]));\n\t\t\t}\n\n\t\t\tif (connectOptions.mqttVersion === undefined) {\n\t\t\t\tconnectOptions.mqttVersionExplicit = false;\n\t\t\t\tconnectOptions.mqttVersion = 4;\n\t\t\t} else {\n\t\t\t\tconnectOptions.mqttVersionExplicit = true;\n\t\t\t}\n\n\t\t\t//Check that if password is set, so is username\n\t\t\tif (connectOptions.password === undefined && connectOptions.userName !== undefined)\n\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.password, \"connectOptions.password\"]))\n\n\t\t\tif (connectOptions.willMessage) {\n\t\t\t\tif (!(connectOptions.willMessage instanceof Message))\n\t\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [connectOptions.willMessage, \"connectOptions.willMessage\"]));\n\t\t\t\t// The will message must have a payload that can be represented as a string.\n\t\t\t\t// Cause the willMessage to throw an exception if this is not the case.\n\t\t\t\tconnectOptions.willMessage.stringPayload;\n\n\t\t\t\tif (typeof connectOptions.willMessage.destinationName === \"undefined\")\n\t\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.willMessage.destinationName, \"connectOptions.willMessage.destinationName\"]));\n\t\t\t}\n\t\t\tif (typeof connectOptions.cleanSession === \"undefined\")\n\t\t\t\tconnectOptions.cleanSession = true;\n\t\t\tif (connectOptions.hosts) {\n\n\t\t\t\tif (!(connectOptions.hosts instanceof Array) )\n\t\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, \"connectOptions.hosts\"]));\n\t\t\t\tif (connectOptions.hosts.length <1 )\n\t\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, \"connectOptions.hosts\"]));\n\n\t\t\t\tvar usingURIs = false;\n\t\t\t\tfor (var i = 0; i<connectOptions.hosts.length; i++) {\n\t\t\t\t\tif (typeof connectOptions.hosts[i] !== \"string\")\n\t\t\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.hosts[i], \"connectOptions.hosts[\"+i+\"]\"]));\n\t\t\t\t\tif (/^(wss?):\\/\\/((\\[(.+)\\])|([^\\/]+?))(:(\\d+))?(\\/.*)$/.test(connectOptions.hosts[i])) {\n\t\t\t\t\t\tif (i == 0) {\n\t\t\t\t\t\t\tusingURIs = true;\n\t\t\t\t\t\t} else if (!usingURIs) {\n\t\t\t\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], \"connectOptions.hosts[\"+i+\"]\"]));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (usingURIs) {\n\t\t\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], \"connectOptions.hosts[\"+i+\"]\"]));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!usingURIs) {\n\t\t\t\t\tif (!connectOptions.ports)\n\t\t\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, \"connectOptions.ports\"]));\n\t\t\t\t\tif (!(connectOptions.ports instanceof Array) )\n\t\t\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, \"connectOptions.ports\"]));\n\t\t\t\t\tif (connectOptions.hosts.length != connectOptions.ports.length)\n\t\t\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, \"connectOptions.ports\"]));\n\n\t\t\t\t\tconnectOptions.uris = [];\n\n\t\t\t\t\tfor (var i = 0; i<connectOptions.hosts.length; i++) {\n\t\t\t\t\t\tif (typeof connectOptions.ports[i] !== \"number\" || connectOptions.ports[i] < 0)\n\t\t\t\t\t\t\tthrow new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.ports[i], \"connectOptions.ports[\"+i+\"]\"]));\n\t\t\t\t\t\tvar host = connectOptions.hosts[i];\n\t\t\t\t\t\tvar port = connectOptions.ports[i];\n\n\t\t\t\t\t\tvar ipv6 = (host.indexOf(\":\") != -1);\n\t\t\t\t\t\turi = \"ws://\"+(ipv6?\"[\"+host+\"]\":host)+\":\"+port+path;\n\t\t\t\t\t\tconnectOptions.uris.push(uri);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconnectOptions.uris = connectOptions.hosts;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tclient.connect(connectOptions);\n\t\t};\n\n\t\t/**\n\t\t * Subscribe for messages, request receipt of a copy of messages sent to the destinations described by the filter.\n\t\t *\n\t\t * @name Paho.MQTT.Client#subscribe\n\t\t * @function\n\t\t * @param {string} filter describing the destinations to receive messages from.\n\t\t * <br>\n\t\t * @param {object} subscribeOptions - used to control the subscription\n\t\t *\n\t\t * @param {number} subscribeOptions.qos - the maiximum qos of any publications sent\n\t\t *                                  as a result of making this subscription.\n\t\t * @param {object} subscribeOptions.invocationContext - passed to the onSuccess callback\n\t\t *                                  or onFailure callback.\n\t\t * @param {function} subscribeOptions.onSuccess - called when the subscribe acknowledgement\n\t\t *                                  has been received from the server.\n\t\t *                                  A single response object parameter is passed to the onSuccess callback containing the following fields:\n\t\t *                                  <ol>\n\t\t *                                  <li>invocationContext if set in the subscribeOptions.\n\t\t *                                  </ol>\n\t\t * @param {function} subscribeOptions.onFailure - called when the subscribe request has failed or timed out.\n\t\t *                                  A single response object parameter is passed to the onFailure callback containing the following fields:\n\t\t *                                  <ol>\n\t\t *                                  <li>invocationContext - if set in the subscribeOptions.\n\t\t *                                  <li>errorCode - a number indicating the nature of the error.\n\t\t *                                  <li>errorMessage - text describing the error.\n\t\t *                                  </ol>\n\t\t * @param {number} subscribeOptions.timeout - which, if present, determines the number of\n\t\t *                                  seconds after which the onFailure calback is called.\n\t\t *                                  The presence of a timeout does not prevent the onSuccess\n\t\t *                                  callback from being called when the subscribe completes.\n\t\t * @throws {InvalidState} if the client is not in connected state.\n\t\t */\n\t\tthis.subscribe = function (filter, subscribeOptions) {\n\t\t\tif (typeof filter !== \"string\")\n\t\t\t\tthrow new Error(\"Invalid argument:\"+filter);\n\t\t\tsubscribeOptions = subscribeOptions || {} ;\n\t\t\tvalidate(subscribeOptions,  {qos:\"number\",\n\t\t\t\tinvocationContext:\"object\",\n\t\t\t\tonSuccess:\"function\",\n\t\t\t\tonFailure:\"function\",\n\t\t\t\ttimeout:\"number\"\n\t\t\t});\n\t\t\tif (subscribeOptions.timeout && !subscribeOptions.onFailure)\n\t\t\t\tthrow new Error(\"subscribeOptions.timeout specified with no onFailure callback.\");\n\t\t\tif (typeof subscribeOptions.qos !== \"undefined\"\n\t\t\t\t&& !(subscribeOptions.qos === 0 || subscribeOptions.qos === 1 || subscribeOptions.qos === 2 ))\n\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [subscribeOptions.qos, \"subscribeOptions.qos\"]));\n\t\t\tclient.subscribe(filter, subscribeOptions);\n\t\t};\n\n\t\t/**\n\t\t * Unsubscribe for messages, stop receiving messages sent to destinations described by the filter.\n\t\t *\n\t\t * @name Paho.MQTT.Client#unsubscribe\n\t\t * @function\n\t\t * @param {string} filter - describing the destinations to receive messages from.\n\t\t * @param {object} unsubscribeOptions - used to control the subscription\n\t\t * @param {object} unsubscribeOptions.invocationContext - passed to the onSuccess callback\n\t\t or onFailure callback.\n\t\t * @param {function} unsubscribeOptions.onSuccess - called when the unsubscribe acknowledgement has been received from the server.\n\t\t *                                    A single response object parameter is passed to the\n\t\t *                                    onSuccess callback containing the following fields:\n\t\t *                                    <ol>\n\t\t *                                    <li>invocationContext - if set in the unsubscribeOptions.\n\t\t *                                    </ol>\n\t\t * @param {function} unsubscribeOptions.onFailure called when the unsubscribe request has failed or timed out.\n\t\t *                                    A single response object parameter is passed to the onFailure callback containing the following fields:\n\t\t *                                    <ol>\n\t\t *                                    <li>invocationContext - if set in the unsubscribeOptions.\n\t\t *                                    <li>errorCode - a number indicating the nature of the error.\n\t\t *                                    <li>errorMessage - text describing the error.\n\t\t *                                    </ol>\n\t\t * @param {number} unsubscribeOptions.timeout - which, if present, determines the number of seconds\n\t\t *                                    after which the onFailure callback is called. The presence of\n\t\t *                                    a timeout does not prevent the onSuccess callback from being\n\t\t *                                    called when the unsubscribe completes\n\t\t * @throws {InvalidState} if the client is not in connected state.\n\t\t */\n\t\tthis.unsubscribe = function (filter, unsubscribeOptions) {\n\t\t\tif (typeof filter !== \"string\")\n\t\t\t\tthrow new Error(\"Invalid argument:\"+filter);\n\t\t\tunsubscribeOptions = unsubscribeOptions || {} ;\n\t\t\tvalidate(unsubscribeOptions,  {invocationContext:\"object\",\n\t\t\t\tonSuccess:\"function\",\n\t\t\t\tonFailure:\"function\",\n\t\t\t\ttimeout:\"number\"\n\t\t\t});\n\t\t\tif (unsubscribeOptions.timeout && !unsubscribeOptions.onFailure)\n\t\t\t\tthrow new Error(\"unsubscribeOptions.timeout specified with no onFailure callback.\");\n\t\t\tclient.unsubscribe(filter, unsubscribeOptions);\n\t\t};\n\n\t\t/**\n\t\t * Send a message to the consumers of the destination in the Message.\n\t\t *\n\t\t * @name Paho.MQTT.Client#send\n\t\t * @function\n\t\t * @param {string|Paho.MQTT.Message} topic - <b>mandatory</b> The name of the destination to which the message is to be sent.\n\t\t * \t\t\t\t\t   - If it is the only parameter, used as Paho.MQTT.Message object.\n\t\t * @param {String|ArrayBuffer} payload - The message data to be sent.\n\t\t * @param {number} qos The Quality of Service used to deliver the message.\n\t\t * \t\t<dl>\n\t\t * \t\t\t<dt>0 Best effort (default).\n\t\t *     \t\t\t<dt>1 At least once.\n\t\t *     \t\t\t<dt>2 Exactly once.\n\t\t * \t\t</dl>\n\t\t * @param {Boolean} retained If true, the message is to be retained by the server and delivered\n\t\t *                     to both current and future subscriptions.\n\t\t *                     If false the server only delivers the message to current subscribers, this is the default for new Messages.\n\t\t *                     A received message has the retained boolean set to true if the message was published\n\t\t *                     with the retained boolean set to true\n\t\t *                     and the subscrption was made after the message has been published.\n\t\t * @throws {InvalidState} if the client is not connected.\n\t\t */\n\t\tthis.send = function (topic,payload,qos,retained) {\n\t\t\tvar message ;\n\n\t\t\tif(arguments.length == 0){\n\t\t\t\tthrow new Error(\"Invalid argument.\"+\"length\");\n\n\t\t\t}else if(arguments.length == 1) {\n\n\t\t\t\tif (!(topic instanceof Message) && (typeof topic !== \"string\"))\n\t\t\t\t\tthrow new Error(\"Invalid argument:\"+ typeof topic);\n\n\t\t\t\tmessage = topic;\n\t\t\t\tif (typeof message.destinationName === \"undefined\")\n\t\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,\"Message.destinationName\"]));\n\t\t\t\tclient.send(message);\n\n\t\t\t}else {\n\t\t\t\t//parameter checking in Message object\n\t\t\t\tmessage = new Message(payload);\n\t\t\t\tmessage.destinationName = topic;\n\t\t\t\tif(arguments.length >= 3)\n\t\t\t\t\tmessage.qos = qos;\n\t\t\t\tif(arguments.length >= 4)\n\t\t\t\t\tmessage.retained = retained;\n\t\t\t\tclient.send(message);\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Normal disconnect of this Messaging client from its server.\n\t\t *\n\t\t * @name Paho.MQTT.Client#disconnect\n\t\t * @function\n\t\t * @throws {InvalidState} if the client is already disconnected.\n\t\t */\n\t\tthis.disconnect = function () {\n\t\t\tclient.disconnect();\n\t\t};\n\n\t\t/**\n\t\t * Get the contents of the trace log.\n\t\t *\n\t\t * @name Paho.MQTT.Client#getTraceLog\n\t\t * @function\n\t\t * @return {Object[]} tracebuffer containing the time ordered trace records.\n\t\t */\n\t\tthis.getTraceLog = function () {\n\t\t\treturn client.getTraceLog();\n\t\t}\n\n\t\t/**\n\t\t * Start tracing.\n\t\t *\n\t\t * @name Paho.MQTT.Client#startTrace\n\t\t * @function\n\t\t */\n\t\tthis.startTrace = function () {\n\t\t\tclient.startTrace();\n\t\t};\n\n\t\t/**\n\t\t * Stop tracing.\n\t\t *\n\t\t * @name Paho.MQTT.Client#stopTrace\n\t\t * @function\n\t\t */\n\t\tthis.stopTrace = function () {\n\t\t\tclient.stopTrace();\n\t\t};\n\n\t\tthis.isConnected = function() {\n\t\t\treturn client.connected;\n\t\t};\n\t};\n\n\tClient.prototype = {\n\t\tget host() { return this._getHost(); },\n\t\tset host(newHost) { this._setHost(newHost); },\n\n\t\tget port() { return this._getPort(); },\n\t\tset port(newPort) { this._setPort(newPort); },\n\n\t\tget path() { return this._getPath(); },\n\t\tset path(newPath) { this._setPath(newPath); },\n\n\t\tget clientId() { return this._getClientId(); },\n\t\tset clientId(newClientId) { this._setClientId(newClientId); },\n\n\t\tget onConnectionLost() { return this._getOnConnectionLost(); },\n\t\tset onConnectionLost(newOnConnectionLost) { this._setOnConnectionLost(newOnConnectionLost); },\n\n\t\tget onMessageDelivered() { return this._getOnMessageDelivered(); },\n\t\tset onMessageDelivered(newOnMessageDelivered) { this._setOnMessageDelivered(newOnMessageDelivered); },\n\n\t\tget onMessageArrived() { return this._getOnMessageArrived(); },\n\t\tset onMessageArrived(newOnMessageArrived) { this._setOnMessageArrived(newOnMessageArrived); },\n\n\t\tget trace() { return this._getTrace(); },\n\t\tset trace(newTraceFunction) { this._setTrace(newTraceFunction); }\n\n\t};\n\n\t/**\n\t * An application message, sent or received.\n\t * <p>\n\t * All attributes may be null, which implies the default values.\n\t *\n\t * @name Paho.MQTT.Message\n\t * @constructor\n\t * @param {String|ArrayBuffer} payload The message data to be sent.\n\t * <p>\n\t * @property {string} payloadString <i>read only</i> The payload as a string if the payload consists of valid UTF-8 characters.\n\t * @property {ArrayBuffer} payloadBytes <i>read only</i> The payload as an ArrayBuffer.\n\t * <p>\n\t * @property {string} destinationName <b>mandatory</b> The name of the destination to which the message is to be sent\n\t *                    (for messages about to be sent) or the name of the destination from which the message has been received.\n\t *                    (for messages received by the onMessage function).\n\t * <p>\n\t * @property {number} qos The Quality of Service used to deliver the message.\n\t * <dl>\n\t *     <dt>0 Best effort (default).\n\t *     <dt>1 At least once.\n\t *     <dt>2 Exactly once.\n\t * </dl>\n\t * <p>\n\t * @property {Boolean} retained If true, the message is to be retained by the server and delivered\n\t *                     to both current and future subscriptions.\n\t *                     If false the server only delivers the message to current subscribers, this is the default for new Messages.\n\t *                     A received message has the retained boolean set to true if the message was published\n\t *                     with the retained boolean set to true\n\t *                     and the subscrption was made after the message has been published.\n\t * <p>\n\t * @property {Boolean} duplicate <i>read only</i> If true, this message might be a duplicate of one which has already been received.\n\t *                     This is only set on messages received from the server.\n\t *\n\t */\n\tvar Message = function (newPayload) {\n\t\tvar payload;\n\t\tif (   typeof newPayload === \"string\"\n\t\t\t|| newPayload instanceof ArrayBuffer\n\t\t\t|| newPayload instanceof Int8Array\n\t\t\t|| newPayload instanceof Uint8Array\n\t\t\t|| newPayload instanceof Int16Array\n\t\t\t|| newPayload instanceof Uint16Array\n\t\t\t|| newPayload instanceof Int32Array\n\t\t\t|| newPayload instanceof Uint32Array\n\t\t\t|| newPayload instanceof Float32Array\n\t\t\t|| newPayload instanceof Float64Array\n\t\t) {\n\t\t\tpayload = newPayload;\n\t\t} else {\n\t\t\tthrow (format(ERROR.INVALID_ARGUMENT, [newPayload, \"newPayload\"]));\n\t\t}\n\n\t\tthis._getPayloadString = function () {\n\t\t\tif (typeof payload === \"string\")\n\t\t\t\treturn payload;\n\t\t\telse\n\t\t\t\treturn parseUTF8(payload, 0, payload.length);\n\t\t};\n\n\t\tthis._getPayloadBytes = function() {\n\t\t\tif (typeof payload === \"string\") {\n\t\t\t\tvar buffer = new ArrayBuffer(UTF8Length(payload));\n\t\t\t\tvar byteStream = new Uint8Array(buffer);\n\t\t\t\tstringToUTF8(payload, byteStream, 0);\n\n\t\t\t\treturn byteStream;\n\t\t\t} else {\n\t\t\t\treturn payload;\n\t\t\t};\n\t\t};\n\n\t\tvar destinationName = undefined;\n\t\tthis._getDestinationName = function() { return destinationName; };\n\t\tthis._setDestinationName = function(newDestinationName) {\n\t\t\tif (typeof newDestinationName === \"string\")\n\t\t\t\tdestinationName = newDestinationName;\n\t\t\telse\n\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [newDestinationName, \"newDestinationName\"]));\n\t\t};\n\n\t\tvar qos = 0;\n\t\tthis._getQos = function() { return qos; };\n\t\tthis._setQos = function(newQos) {\n\t\t\tif (newQos === 0 || newQos === 1 || newQos === 2 )\n\t\t\t\tqos = newQos;\n\t\t\telse\n\t\t\t\tthrow new Error(\"Invalid argument:\"+newQos);\n\t\t};\n\n\t\tvar retained = false;\n\t\tthis._getRetained = function() { return retained; };\n\t\tthis._setRetained = function(newRetained) {\n\t\t\tif (typeof newRetained === \"boolean\")\n\t\t\t\tretained = newRetained;\n\t\t\telse\n\t\t\t\tthrow new Error(format(ERROR.INVALID_ARGUMENT, [newRetained, \"newRetained\"]));\n\t\t};\n\n\t\tvar duplicate = false;\n\t\tthis._getDuplicate = function() { return duplicate; };\n\t\tthis._setDuplicate = function(newDuplicate) { duplicate = newDuplicate; };\n\t};\n\n\tMessage.prototype = {\n\t\tget payloadString() { return this._getPayloadString(); },\n\t\tget payloadBytes() { return this._getPayloadBytes(); },\n\n\t\tget destinationName() { return this._getDestinationName(); },\n\t\tset destinationName(newDestinationName) { this._setDestinationName(newDestinationName); },\n\n\t\tget qos() { return this._getQos(); },\n\t\tset qos(newQos) { this._setQos(newQos); },\n\n\t\tget retained() { return this._getRetained(); },\n\t\tset retained(newRetained) { this._setRetained(newRetained); },\n\n\t\tget duplicate() { return this._getDuplicate(); },\n\t\tset duplicate(newDuplicate) { this._setDuplicate(newDuplicate); }\n\t};\n\n\t// Module contents.\n\treturn {\n\t\tClient: Client,\n\t\tMessage: Message\n\t};\n})(window);\n"
  },
  {
    "path": "jmqtt-example/src/main/java/org/jmqtt/websocket/webSocket.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>webSocket test</title>\n</head>\n<body>\n<h1>Test WebSocket function</h1>\n<div>\n    <span>server ip:</span>\n    <input name=\"serverIp\" type=\"text\" value=\"127.0.0.1\">\n</div>\n<div>\n    <span>webSocket port:</span>\n    <input name=\"port\" type=\"text\" value=\"8883\">\n</div>\n<div>\n    <span>clientId:</span>\n    <input name=\"clientId\" type=\"text\" value=\"client_test_id\">\n</div>\n<button class=\"connect\">connect</button>\n<br>\n<br>\n<div>\n    <span>Pub Topic:</span>\n    <input name=\"topic\" type=\"text\" value=\"T_WEBSOCKET_TEST\">\n</div>\n<div>\n    <span>Message:</span>\n    <input name=\"message\" type=\"text\" value=\"this is a message from webSocket client\">\n</div>\n<button class=\"pub\">pubMessage</button>\n<br>\n<br>\n<div>\n    <span>Sub Topic:</span>\n    <input name=\"subTopic\" type=\"text\" value=\"T_WEBSOCKET_TEST\">\n</div>\n<button class=\"sub\">Subscribe</button>\n<button class=\"unSub\">UnSubscribe</button>\n<div class=\"recMessage\"></div>\n\n</body>\n<script src=\"http://libs.baidu.com/jquery/2.1.4/jquery.min.js\"></script>\n<script src=\"paho-mqtt-min.js\" type=\"text/javascript\"></script>\n<script>\n    $(function () {\n        var client;\n        $(\".connect\").click(function () {\n            var ip = $(\"input[name='serverIp']\").val();\n            var port = $(\"input[name='port']\").val();\n            var clientId = $(\"input[name='clientId']\").val();\n            client = connect(ip, port, clientId);\n        })\n        $(\".pub\").click(function () {\n            var topic = $(\"input[name='topic']\").val();\n            var message = $(\"input[name='message']\").val();\n            pubMessage(client, topic, message);\n        })\n        $(\".sub\").click(function () {\n            var subTopic = $(\"input[name='subTopic']\").val();\n            subscribe(client, subTopic);\n        })\n        $(\".unSub\").click(function () {\n            var subTopic = $(\"input[name='subTopic']\").val();\n            unSubscribe(client, subTopic);\n        })\n\n\n    })\n\n    function connect(ip, port, clientId) {\n        console.log(\"ip:\" + ip);\n        console.log(\"port:\" + port);\n        console.log(\"clientId:\" + clientId);\n        var client = new Paho.MQTT.Client(ip, Number(port), \"/mqtt\", clientId);\n        client.onConnectionLost = onConnectionLost;\n        client.onMessageArrived = onMessageArrived;\n        client.onMessageDelivered = onMessageDelivered;\n        client.connect({\n            onSuccess: onConnect\n        });\n        return client;\n    }\n\n    function pubMessage(client, topic, message) {\n        var message = new Paho.MQTT.Message(message);\n        message.destinationName = topic;\n        client.send(message);\n    }\n\n    function subscribe(client, subTopic) {\n        client.subscribe(subTopic);\n        alert(\"sub topic success\");\n    }\n\n    function unSubscribe(client, subTopic) {\n        client.unsubscribe(subTopic);\n        alert(\"un sub topic success\");\n    }\n\n    function onMessageArrived(message) {\n        console.log(\"receive message:\" + message.payloadString);\n        var txt = \"<p>\" + message.payloadString + \"</p>\";\n        $(\".recMessage\").append(txt);\n    }\n\n    function onMessageDelivered(message) {\n        console.log(\"pub message\" + message.payloadString)\n        alert(\"pub message success,message: \" + message.payloadString);\n    }\n\n    // called when the client connects\n    function onConnect() {\n        alert(\"connect success.\");\n    }\n\n    // called when the client loses its connection\n    function onConnectionLost(responseObject) {\n        if (responseObject.errorCode !== 0) {\n            console.log(\"onConnectionLost:\" + responseObject.errorMessage);\n        }\n    }\n</script>\n</html>\n"
  },
  {
    "path": "jmqtt-manager",
    "content": "\n"
  },
  {
    "path": "jmqtt-mqtt/README.md",
    "content": "Support mqtt gateway\n"
  },
  {
    "path": "jmqtt-mqtt/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n    <parent>\n        <artifactId>jmqtt</artifactId>\n        <groupId>org.jmqtt</groupId>\n        <version>3.0.0</version>\n    </parent>\n\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>jmqtt-mqtt</artifactId>\n    <packaging>jar</packaging>\n\n    <name>jmqtt-mqtt</name>\n\n\n    <dependencies>\n\n        <dependency>\n            <groupId>org.jmqtt</groupId>\n            <artifactId>jmqtt-bus</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-all</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n        </dependency>\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/ConnectManager.java",
    "content": "package org.jmqtt.mqtt;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * 客户端连接管理器\n */\npublic class ConnectManager {\n\n    private  Map<String /* clientId */, MQTTConnection /* ClientSession */> clientCache = new ConcurrentHashMap<>();\n\n    private static final  ConnectManager INSTANCE  =  new ConnectManager();\n\n    private ConnectManager(){}\n\n    public static  ConnectManager getInstance(){\n        return INSTANCE;\n    }\n\n    public Collection<MQTTConnection> getAllConnections(){\n        return this.clientCache.values();\n    }\n\n    public MQTTConnection getClient(String clientId){\n        return this.clientCache.get(clientId);\n    }\n\n    public MQTTConnection putClient(String clientId,MQTTConnection clientSession){\n        return this.clientCache.put(clientId,clientSession);\n    }\n\n    public boolean containClient(String clientId){\n        return this.clientCache.containsKey(clientId);\n    }\n\n    public MQTTConnection removeClient(String clientId){\n        if (clientId != null) {\n            return this.clientCache.remove(clientId);\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTConnection.java",
    "content": "\npackage org.jmqtt.mqtt;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.*;\nimport io.netty.handler.timeout.IdleStateHandler;\nimport org.jmqtt.bus.*;\nimport org.jmqtt.bus.enums.*;\nimport org.jmqtt.bus.model.ClusterEvent;\nimport org.jmqtt.bus.model.DeviceMessage;\nimport org.jmqtt.bus.model.DeviceSession;\nimport org.jmqtt.bus.model.DeviceSubscription;\nimport org.jmqtt.mqtt.model.MqttTopic;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\nimport org.jmqtt.mqtt.retain.RetainMessageHandler;\nimport org.jmqtt.mqtt.session.MqttSession;\nimport org.jmqtt.mqtt.utils.MqttMessageUtil;\nimport org.jmqtt.mqtt.utils.MqttMsgHeader;\nimport org.jmqtt.support.config.BrokerConfig;\nimport org.jmqtt.support.helper.Pair;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.jmqtt.support.remoting.RemotingHelper;\nimport org.slf4j.Logger;\n\nimport java.util.*;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.RejectedExecutionException;\n\nimport static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;\nimport static io.netty.channel.ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE;\n\n/**\n * handle mqtt connection and process mqtt protocol\n */\npublic class MQTTConnection {\n\n    private static final Logger log = JmqttLogger.mqttLog;\n\n    private Channel                                               channel;\n    private Map<Integer, Pair<RequestProcessor, ExecutorService>> processorTable;\n    private BrokerConfig                                          brokerConfig;\n    private MqttSession                                           bindedSession;\n    private Authenticator                                         authenticator;\n\n    private DeviceSessionManager      deviceSessionManager;\n    private DeviceSubscriptionManager deviceSubscriptionManager;\n    private DeviceMessageManager      deviceMessageManager;\n    private RetainMessageHandler      retainMessageHandler;\n    private ClusterEventManager clusterEventManager;\n    private String clientId;\n\n    public MQTTConnection(Channel channel, Map<Integer, Pair<RequestProcessor, ExecutorService>> processorTable,\n                          BrokerConfig brokerConfig, BusController busController,RetainMessageHandler retainMessageHandler) {\n        this.channel = channel;\n        this.processorTable = processorTable;\n        this.brokerConfig = brokerConfig;\n        this.authenticator = busController.getAuthenticator();\n        this.deviceSessionManager = busController.getDeviceSessionManager();\n        this.deviceMessageManager = busController.getDeviceMessageManager();\n        this.deviceSubscriptionManager = busController.getDeviceSubscriptionManager();\n        this.retainMessageHandler = retainMessageHandler;\n        this.clusterEventManager = busController.getClusterEventManager();\n    }\n\n\n\n    public void processProtocol(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        int protocolType = mqttMessage.fixedHeader().messageType().value();\n        Runnable runnable = () -> processorTable.get(protocolType).getObject1().processRequest(ctx, mqttMessage);\n        try {\n            processorTable.get(protocolType).getObject2().submit(runnable);\n        } catch (RejectedExecutionException ex) {\n            LogUtil.warn(log, \"Reject mqtt request,cause={}\", ex.getMessage());\n        }\n    }\n\n    public void processPubComp(MqttMessage mqttMessage) {\n        String clientId = getClientId();\n        int packetId = MqttMessageUtil.getMessageId(mqttMessage);\n        boolean flag = bindedSession.releaseQos2SecFlow(packetId);\n        LogUtil.debug(log,\"[PubComp] -> Receive PubCom and remove the flow message,clientId={},msgId={}\",clientId,packetId);\n        if(!flag){\n            LogUtil.warn(log,\"[PubComp] -> The message is not in Flow cache,clientId={},msgId={}\",clientId,packetId);\n        }\n    }\n\n    public void processPubRec(MqttMessage mqttMessage){\n        String clientId = getClientId();\n        int packetId = MqttMessageUtil.getMessageId(mqttMessage);\n        DeviceMessage stayAckMsg = bindedSession.releaseOutboundFlowMessage(packetId);\n        if (stayAckMsg == null) {\n            LogUtil.warn(log,\"[PUBREC] Stay release message is not exist,packetId:{},clientId:{}\",packetId,getClientId());\n        } else {\n            this.deviceMessageManager.ackMessage(getClientId(),stayAckMsg.getId());\n        }\n\n        bindedSession.receivePubRec(packetId);\n        LogUtil.debug(log,\"[PubRec] -> Receive PubRec message,clientId={},msgId={}\",clientId,packetId);\n        MqttMessage pubRelMessage = MqttMessageUtil.getPubRelMessage(packetId);\n        this.channel.writeAndFlush(pubRelMessage);\n    }\n\n    public void processPubAck(MqttMessage mqttMessage){\n        int packetId = MqttMessageUtil.getMessageId(mqttMessage);\n        LogUtil.info(log, \"[PubAck] -> Receive PubAck message,clientId={},msgId={}\", getClientId(),packetId);\n        DeviceMessage deviceMessage = bindedSession.releaseOutboundFlowMessage(packetId);\n        if (deviceMessage == null) {\n            LogUtil.warn(log,\"[PUBACK] Stay release message is not exist,packetId:{},clientId:{}\",packetId,getClientId());\n            return;\n        }\n        this.deviceMessageManager.ackMessage(getClientId(),deviceMessage.getId());\n    }\n\n    public void processUnSubscribe(MqttUnsubscribeMessage mqttUnsubscribeMessage){\n        String clientId = getClientId();\n        MqttUnsubscribePayload unsubscribePayload = mqttUnsubscribeMessage.payload();\n        List<String> topics = unsubscribePayload.topics();\n        topics.forEach( topic -> {\n            this.bindedSession.removeSubscription(topic);\n            deviceSubscriptionManager.unSubscribe(clientId,topic);\n        });\n        MqttUnsubAckMessage unsubAckMessage = MqttMessageUtil.getUnSubAckMessage(MqttMessageUtil.getMessageId(mqttUnsubscribeMessage));\n        this.channel.writeAndFlush(unsubAckMessage);\n    }\n\n    public void processSubscribe(MqttSubscribeMessage subscribeMessage) {\n\n        int packetId = subscribeMessage.variableHeader().messageId();\n        List<MqttTopic> validTopicList = validTopics(subscribeMessage.payload().topicSubscriptions());\n        if (validTopicList == null || validTopicList.size() == 0) {\n            LogUtil.warn(log, \"[Subscribe] -> Valid all subscribe topic failure,clientId:{},packetId:{}\", getClientId(),packetId);\n            return;\n        }\n\n        // subscribe\n        List<Integer> ackQos = new ArrayList<>(validTopicList.size());\n        for (MqttTopic topic : validTopicList) {\n            DeviceSubscription deviceSubscription = new DeviceSubscription();\n            deviceSubscription.setTopic(topic.getTopicName());\n            deviceSubscription.setClientId(getClientId());\n            deviceSubscription.setSubscribeTime(new Date());\n            Map<String,Object> properties = new HashMap<>();\n            properties.put(MqttMsgHeader.QOS,topic.getQos());\n            deviceSubscription.setProperties(properties);\n            this.bindedSession.addSubscription(deviceSubscription);\n            boolean succ = this.deviceSubscriptionManager.subscribe(deviceSubscription);\n            if (succ) {\n                ackQos.add(topic.getQos());\n            } else {\n                LogUtil.error(log,\"[SUBSCRIBE] subscribe error.\");\n            }\n        }\n\n        MqttMessage subAckMessage = MqttMessageUtil.getSubAckMessage(packetId, ackQos);\n        this.channel.writeAndFlush(subAckMessage).addListener(new ChannelFutureListener() {\n            @Override\n            public void operationComplete(ChannelFuture future) throws Exception {\n                if (future.isSuccess()) {\n                    // dispatcher retain message\n                    List<DeviceMessage> retainMessages = retainMessageHandler.getAllRetatinMessage();\n                    if (retainMessages != null) {\n                        retainMessages.forEach(message -> {\n                            message.setSource(MessageSourceEnum.DEVICE);\n                            // match\n                            for (MqttTopic topic : validTopicList) {\n                                if (deviceSubscriptionManager.isMatch(message.getTopic(), topic.getTopicName())) {\n                                    // 1. store stay message\n                                    Long msgId = deviceMessageManager.storeMessage(message);\n                                    deviceMessageManager.addClientInBoxMsg(getClientId(),msgId, MessageAckEnum.UN_ACK);\n\n                                    // 2. ack message\n                                    bindedSession.sendMessage(message);\n                                }\n                            }\n                        });\n                    }\n\n                } else {\n                    LogUtil.error(log, \"[SUBSCRIBE] suback response error.\");\n                }\n            }\n        });\n    }\n\n    public void publishMessage(MqttPublishMessage mqttPublishMessage){\n        this.channel.writeAndFlush(mqttPublishMessage);\n    }\n\n    /**\n     * 返回校验合法的topic\n     */\n    private List<MqttTopic> validTopics(List<MqttTopicSubscription> topics) {\n        List<MqttTopic> topicList = new ArrayList<>();\n        for (MqttTopicSubscription subscription : topics) {\n            if (!authenticator.subscribeVerify(getClientId(), subscription.topicName())) {\n                LogUtil.warn(log, \"[SubPermission] this clientId:{} have no permission to subscribe this topic:{}\", getClientId(),\n                        subscription.topicName());\n                continue;\n            }\n            MqttTopic topic = new MqttTopic(subscription.topicName(), subscription.qualityOfService().value());\n            topicList.add(topic);\n        }\n        return topicList;\n    }\n\n    public void processPublishMessage(MqttPublishMessage mqttPublishMessage) {\n        MqttQoS qos = mqttPublishMessage.fixedHeader().qosLevel();\n        switch (qos) {\n            case AT_MOST_ONCE:\n                dispatcherMessage(mqttPublishMessage);\n                break;\n            case AT_LEAST_ONCE:\n                processQos1(mqttPublishMessage);\n                break;\n            case EXACTLY_ONCE:\n                processQos2(mqttPublishMessage);\n                break;\n            default:\n                LogUtil.warn(log, \"[PubMessage] -> Wrong mqtt message,clientId={}\", getClientId());\n                break;\n        }\n    }\n\n    private void processQos2(MqttPublishMessage mqttPublishMessage) {\n        int originMessageId = mqttPublishMessage.variableHeader().packetId();\n        LogUtil.debug(log, \"[PubMessage] -> Process qos2 message,clientId={}\", getClientId());\n        DeviceMessage deviceMessage = MqttMsgHeader.buildDeviceMessage(mqttPublishMessage);\n        bindedSession.receivedPublishQos2(originMessageId, deviceMessage);\n        MqttMessage pubRecMessage = MqttMessageUtil.getPubRecMessage(originMessageId);\n        this.channel.writeAndFlush(pubRecMessage);\n    }\n\n    private void processQos1(MqttPublishMessage mqttPublishMessage) {\n        int originMessageId = mqttPublishMessage.variableHeader().packetId();\n        dispatcherMessage(mqttPublishMessage);\n        LogUtil.info(log, \"[PubMessage] -> Process qos1 message,clientId={}\", getClientId());\n        MqttPubAckMessage pubAckMessage = MqttMessageUtil.getPubAckMessage(originMessageId);\n        this.channel.writeAndFlush(pubAckMessage);\n    }\n\n    public void processPubRelMessage(MqttMessage mqttMessage) {\n        int packetId = MqttMessageUtil.getMessageId(mqttMessage);\n        DeviceMessage deviceMessage = bindedSession.receivedPubRelQos2(packetId);\n        if (deviceMessage == null) {\n            LogUtil.error(log, \"[PUBREL] receivedPubRelQos2 cached message is not exist,message lost !!!!!,packetId:{},clientId:{}\",\n                    packetId, getClientId());\n        } else {\n            dispatcherMessage(deviceMessage);\n        }\n        MqttMessage pubComMessage = MqttMessageUtil.getPubComMessage(packetId);\n        this.channel.writeAndFlush(pubComMessage);\n    }\n\n    private void dispatcherMessage(DeviceMessage deviceMessage) {\n        // 1. retain消息逻辑\n        boolean retain = deviceMessage.getProperty(MqttMsgHeader.RETAIN);\n        int qos = deviceMessage.getProperty(MqttMsgHeader.QOS);\n        if (retain) {\n            //qos == 0 or payload is none,then clear previous retain message\n            if (qos == 0 || deviceMessage.getContent() == null || deviceMessage.getContent().length == 0) {\n                this.retainMessageHandler.clearRetainMessage(deviceMessage.getTopic());\n            } else {\n                this.retainMessageHandler.storeRetainMessage(deviceMessage);\n            }\n        }\n        // 2. 向集群中分发消息：第一阶段\n        this.deviceMessageManager.dispatcher(deviceMessage);\n    }\n\n    private void dispatcherMessage(MqttPublishMessage mqttPublishMessage) {\n        boolean retain = mqttPublishMessage.fixedHeader().isRetain();\n        int qos = mqttPublishMessage.fixedHeader().qosLevel().value();\n        byte[] payload = MqttMessageUtil.readBytesFromByteBuf(mqttPublishMessage.payload());\n        String topic = mqttPublishMessage.variableHeader().topicName();\n\n        DeviceMessage deviceMessage = MqttMsgHeader.buildDeviceMessage(retain, qos, topic, payload);\n        deviceMessage.setSource(MessageSourceEnum.DEVICE);\n        deviceMessage.setFromClientId(clientId);\n        dispatcherMessage(deviceMessage);\n    }\n\n    public void handleConnectionLost() {\n        String clientID = MqttNettyUtils.clientID(channel);\n        if (clientID == null || clientID.isEmpty()) {\n            return;\n        }\n        if (bindedSession.hasWill()) {\n            dispatcherMessage(bindedSession.getWill());\n        }\n        if (bindedSession.isCleanSession()) {\n            clearSession();\n        }\n        offline();\n        LogUtil.info(log, \"[CONNECT INACTIVE] connect lost\");\n    }\n\n    public boolean createOrReopenSession(MqttConnectMessage mqttConnectMessage) {\n\n        int mqttVersion = mqttConnectMessage.variableHeader().version();\n        String clientId = mqttConnectMessage.payload().clientIdentifier();\n        this.clientId = clientId;\n        boolean cleanSession = mqttConnectMessage.variableHeader().isCleanSession();\n\n        this.bindedSession = new MqttSession();\n        this.bindedSession.setClientId(clientId);\n        this.bindedSession.setCleanSession(cleanSession);\n        this.bindedSession.setMqttVersion(mqttVersion);\n        this.bindedSession.setClientIp(RemotingHelper.getRemoteAddr(this.channel));\n        this.bindedSession.setServerIp(RemotingHelper.getLocalAddr());\n        this.bindedSession.setMqttConnection(this);\n\n        boolean sessionPresent = false;\n        boolean notifyClearOtherSession = true;\n\n\n        // 1. 从集群/本服务器中查询是否存在该clientId的设备消息\n        DeviceSession deviceSession = deviceSessionManager.getSession(clientId);\n\n        if (deviceSession != null && deviceSession.getOnline() == DeviceOnlineStateEnum.ONLINE) {\n            MQTTConnection previousClient = ConnectManager.getInstance().getClient(clientId);\n            if (previousClient != null) {\n                //clear old session in this node\n                previousClient.abortConnection(MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED);\n                ConnectManager.getInstance().removeClient(clientId);\n\n                notifyClearOtherSession = false;\n            }\n        }\n        if (deviceSession == null) {\n            sessionPresent = false;\n            notifyClearOtherSession = false;\n        } else {\n            if (cleanSession) {\n                sessionPresent = false;\n                clearSession();\n            } else {\n                sessionPresent = true;\n                reloadSubscriptions();\n            }\n        }\n\n        // 2. 清理连接到其它节点的连接\n        if (notifyClearOtherSession) {\n            ClusterEvent clusterEvent = new ClusterEvent();\n            clusterEvent.setClusterEventCode(ClusterEventCodeEnum.MQTT_CLEAR_SESSION);\n            clusterEvent.setContent(getClientId());\n            clusterEvent.setGmtCreate(new Date());\n            clusterEvent.setNodeIp(bindedSession.getServerIp());\n            clusterEventManager.sendEvent(clusterEvent);\n        }\n        // 3. 处理will 消息\n        boolean willFlag = mqttConnectMessage.variableHeader().isWillFlag();\n        if (willFlag) {\n            boolean willRetain = mqttConnectMessage.variableHeader().isWillRetain();\n            int willQos = mqttConnectMessage.variableHeader().willQos();\n            String willTopic = mqttConnectMessage.payload().willTopic();\n            byte[] willPayload = mqttConnectMessage.payload().willMessageInBytes();\n            DeviceMessage deviceMessage = MqttMsgHeader.buildDeviceMessage(willRetain, willQos, willTopic, willPayload);\n            deviceMessage.setSource(MessageSourceEnum.DEVICE);\n            deviceMessage.setFromClientId(clientId);\n            bindedSession.setWill(deviceMessage);\n        }\n        return sessionPresent;\n    }\n\n    public void storeSession() {\n        ConnectManager.getInstance().putClient(getClientId(),this);\n        DeviceSession deviceSession = new DeviceSession();\n        deviceSession.setTransportProtocol(TransportProtocolEnum.MQTT);\n        deviceSession.setServerIp(bindedSession.getServerIp());\n        deviceSession.setClientIp(bindedSession.getClientIp());\n        deviceSession.setOnlineTime(new Date());\n        deviceSession.setClientId(getClientId());\n        deviceSession.setOnline(DeviceOnlineStateEnum.ONLINE);\n        Map<String,Object> properties = new HashMap<>();\n        properties.put(MqttMsgHeader.CLEAN_SESSION,bindedSession.isCleanSession());\n        deviceSession.setProperties(properties);\n        deviceSessionManager.storeSession(deviceSession);\n    }\n\n    public void reSendMessage2Client() {\n        int limit = 100; // per 100\n        boolean hasUnAckMessages = true;\n        while (hasUnAckMessages) {\n            List<DeviceMessage> deviceInboxMessageList = this.deviceMessageManager.queryUnAckMessages(getClientId(),limit);\n            if (deviceInboxMessageList == null) {\n                return;\n            }\n            if (deviceInboxMessageList.size() < 100) {\n                hasUnAckMessages = false;\n            }\n\n            // resendMessage\n            deviceInboxMessageList.forEach(deviceMessage -> {\n                bindedSession.sendMessage(deviceMessage);\n            });\n        }\n\n    }\n\n    public boolean keepAlive(int heatbeatSec) {\n        int keepAlive = (int) (heatbeatSec * 1.5f);\n        if (this.channel.pipeline().names().contains(\"idleStateHandler\")) {\n            this.channel.pipeline().remove(\"idleStateHandler\");\n        }\n        this.channel.pipeline().addFirst(\"idleStateHandler\", new IdleStateHandler(keepAlive, 0, 0));\n        return true;\n    }\n\n    public boolean login(String clientId, String username, byte[] password) {\n        return this.authenticator.login(clientId, username, password);\n    }\n\n    public boolean onBlackList(String remoteAddr, String clientId) {\n        return this.authenticator.onBlackList(clientId, remoteAddr);\n    }\n\n    public boolean clientIdVerify(String clientId) {\n        return this.authenticator.clientIdVerify(clientId);\n    }\n\n    public void abortConnection(MqttConnectReturnCode returnCode) {\n        MqttConnAckMessage badProto = MqttMessageBuilders.connAck()\n                .returnCode(returnCode)\n                .sessionPresent(false).build();\n        this.channel.writeAndFlush(badProto).addListener(FIRE_EXCEPTION_ON_FAILURE);\n        this.channel.close().addListener(CLOSE_ON_FAILURE);\n    }\n\n    public void processDisconnect() {\n        // 1. 清理will消息\n        bindedSession.clearWill();\n        // 2. 下线\n        offline();\n    }\n\n    private void clearSession() {\n        deviceSubscriptionManager.deleteAllSubscription(getClientId());\n        deviceMessageManager.clearUnAckMessage(getClientId());\n    }\n\n    private void reloadSubscriptions() {\n        Set<DeviceSubscription> deviceSubscriptions = deviceSubscriptionManager.getAllSubscription(getClientId());\n        if (deviceSubscriptions != null) {\n            deviceSubscriptions.forEach(item -> {\n                deviceSubscriptionManager.onlySubscribe2Tree(item);\n            });\n        }\n    }\n\n    private void offline() {\n        this.deviceSessionManager.offline(getClientId());\n        ConnectManager.getInstance().removeClient(getClientId());\n    }\n\n    public String getClientId() {\n        return this.clientId;\n    }\n\n    public Channel getChannel() {\n        return channel;\n    }\n\n    public Map<Integer, Pair<RequestProcessor, ExecutorService>> getProcessorTable() {\n        return processorTable;\n    }\n\n    public BrokerConfig getBrokerConfig() {\n        return brokerConfig;\n    }\n\n    public MqttSession getBindedSession() {\n        return bindedSession;\n    }\n\n    public Authenticator getAuthenticator() {\n        return authenticator;\n    }\n\n    public DeviceSessionManager getDeviceSessionManager() {\n        return deviceSessionManager;\n    }\n\n    public DeviceSubscriptionManager getDeviceSubscriptionManager() {\n        return deviceSubscriptionManager;\n    }\n\n    public DeviceMessageManager getDeviceMessageManager() {\n        return deviceMessageManager;\n    }\n\n    public RetainMessageHandler getRetainMessageHandler() {\n        return retainMessageHandler;\n    }\n\n    public ClusterEventManager getClusterEventManager() {\n        return clusterEventManager;\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTConnectionFactory.java",
    "content": "\npackage org.jmqtt.mqtt;\n\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.mqtt.MqttMessageType;\nimport org.jmqtt.bus.BusController;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\nimport org.jmqtt.mqtt.protocol.impl.*;\nimport org.jmqtt.mqtt.retain.RetainMessageHandler;\nimport org.jmqtt.mqtt.retain.impl.RetainMessageHandlerImpl;\nimport org.jmqtt.support.config.BrokerConfig;\nimport org.jmqtt.support.helper.Pair;\nimport org.jmqtt.support.helper.RejectHandler;\nimport org.jmqtt.support.helper.ThreadFactoryImpl;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * mqtt connection factory\n */\npublic class MQTTConnectionFactory {\n\n    private BrokerConfig brokerConfig;\n\n    /** mqtt protocol processor */\n    private ExecutorService connectExecutor;\n    private  ExecutorService pubExecutor;\n    private  ExecutorService subExecutor;\n    private  ExecutorService pingExecutor;\n    private     LinkedBlockingQueue<Runnable>                     connectQueue;\n    private     LinkedBlockingQueue<Runnable>                     pubQueue;\n    private     LinkedBlockingQueue<Runnable>                     subQueue;\n    private     LinkedBlockingQueue<Runnable>                     pingQueue;\n    private Map<Integer, Pair<RequestProcessor, ExecutorService>> processorTable;\n\n    private RetainMessageHandler retainMessageHandler;\n\n    /** bus dependency */\n    private BusController busController;\n\n\n\n    public MQTTConnectionFactory(BrokerConfig brokerConfig, BusController busController) {\n\n        this.brokerConfig = brokerConfig;\n        this.busController = busController;\n        this.retainMessageHandler = new RetainMessageHandlerImpl();\n\n        this.connectQueue = new LinkedBlockingQueue<>(100000);\n        this.pubQueue = new LinkedBlockingQueue<>(100000);\n        this.subQueue = new LinkedBlockingQueue<>(100000);\n        this.pingQueue = new LinkedBlockingQueue<>(10000);\n\n        int coreThreadNum = Runtime.getRuntime().availableProcessors();\n        this.connectExecutor = new ThreadPoolExecutor(coreThreadNum * 2,\n                coreThreadNum * 2,\n                60000,\n                TimeUnit.MILLISECONDS,\n                connectQueue,\n                new ThreadFactoryImpl(\"ConnectThread\"),\n                new RejectHandler(\"connect\", 100000));\n        this.pubExecutor = new ThreadPoolExecutor(coreThreadNum * 2,\n                coreThreadNum * 2,\n                60000,\n                TimeUnit.MILLISECONDS,\n                pubQueue,\n                new ThreadFactoryImpl(\"PubThread\"),\n                new RejectHandler(\"pub\", 100000));\n        this.subExecutor = new ThreadPoolExecutor(coreThreadNum * 2,\n                coreThreadNum * 2,\n                60000,\n                TimeUnit.MILLISECONDS,\n                subQueue,\n                new ThreadFactoryImpl(\"SubThread\"),\n                new RejectHandler(\"sub\", 100000));\n        this.pingExecutor = new ThreadPoolExecutor(coreThreadNum,\n                coreThreadNum,\n                60000,\n                TimeUnit.MILLISECONDS,\n                pingQueue,\n                new ThreadFactoryImpl(\"PingThread\"),\n                new RejectHandler(\"heartbeat\", 100000));\n\n        RequestProcessor connectProcessor = new ConnectProcessor();\n        RequestProcessor disconnectProcessor = new DisconnectProcessor();\n        RequestProcessor pingProcessor = new PingProcessor();\n        RequestProcessor publishProcessor = new PublishProcessor();\n        RequestProcessor pubRelProcessor = new PubRelProcessor();\n        RequestProcessor subscribeProcessor = new SubscribeProcessor();\n        RequestProcessor unSubscribeProcessor = new UnSubscribeProcessor();\n        RequestProcessor pubRecProcessor = new PubRecProcessor();\n        RequestProcessor pubAckProcessor = new PubAckProcessor();\n        RequestProcessor pubCompProcessor = new PubCompProcessor();\n\n        processorTable = new HashMap<>();\n        registerProcessor(MqttMessageType.CONNECT.value(), connectProcessor, connectExecutor);\n        registerProcessor(MqttMessageType.DISCONNECT.value(), disconnectProcessor,\n                connectExecutor);\n        registerProcessor(MqttMessageType.PINGREQ.value(), pingProcessor, pingExecutor);\n        registerProcessor(MqttMessageType.PUBLISH.value(), publishProcessor, pubExecutor);\n        registerProcessor(MqttMessageType.PUBACK.value(), pubAckProcessor, pubExecutor);\n        registerProcessor(MqttMessageType.PUBREL.value(), pubRelProcessor, pubExecutor);\n        registerProcessor(MqttMessageType.SUBSCRIBE.value(), subscribeProcessor, subExecutor);\n        registerProcessor(MqttMessageType.UNSUBSCRIBE.value(), unSubscribeProcessor, subExecutor);\n        registerProcessor(MqttMessageType.PUBREC.value(), pubRecProcessor, subExecutor);\n        registerProcessor(MqttMessageType.PUBCOMP.value(), pubCompProcessor, subExecutor);\n    }\n\n    public void registerProcessor(int mqttMessageType, RequestProcessor requestProcessor, ExecutorService executorService){\n        processorTable.put(mqttMessageType,new Pair<>(requestProcessor,executorService));\n    }\n\n    public MQTTConnection create(Channel channel){\n        MQTTConnection mqttConnection = new MQTTConnection(channel,processorTable,brokerConfig,busController,retainMessageHandler);\n        return mqttConnection;\n    }\n\n\n    public void shutdown(){\n        this.connectExecutor.shutdown();\n        this.pubExecutor.shutdown();\n        this.subExecutor.shutdown();\n        this.pingExecutor.shutdown();\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTServer.java",
    "content": "\npackage org.jmqtt.mqtt;\n\nimport io.netty.handler.codec.mqtt.MqttConnectReturnCode;\nimport org.jmqtt.bus.BusController;\nimport org.jmqtt.mqtt.event.MqttEventListener;\nimport org.jmqtt.mqtt.netty.MqttRemotingServer;\nimport org.jmqtt.support.config.BrokerConfig;\nimport org.jmqtt.support.config.NettyConfig;\nimport org.jmqtt.support.helper.MixAll;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport java.util.Collection;\n\n/**\n * mqtt服务\n */\npublic class MQTTServer {\n\n    private static final Logger log = JmqttLogger.mqttLog;\n\n    private BusController busController;\n    private BrokerConfig brokerConfig;\n    private NettyConfig nettyConfig;\n    private MqttRemotingServer mqttRemotingServer;\n    private MqttEventListener mqttEventListener;\n\n    public MQTTServer(BusController busController, BrokerConfig brokerConfig, NettyConfig nettyConfig){\n        this.busController = busController;\n        this.brokerConfig = brokerConfig;\n        this.nettyConfig = nettyConfig;\n        this.mqttRemotingServer = new MqttRemotingServer(brokerConfig,nettyConfig,busController);\n        this.mqttEventListener = new MqttEventListener(busController.getDeviceMessageManager());\n    }\n\n    public void start(){\n        this.busController.getClusterEventManager().registerEventListener(mqttEventListener);\n        this.mqttRemotingServer.start();\n        LogUtil.info(log,\"MQTT server start success.\");\n    }\n\n\n    public void shutdown(){\n        this.mqttRemotingServer.shutdown();\n        Collection<MQTTConnection> mqttConnections = ConnectManager.getInstance().getAllConnections();\n        if (!MixAll.isEmpty(mqttConnections)) {\n            for (MQTTConnection mqttConnection : mqttConnections) {\n                mqttConnection.abortConnection(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/codec/ByteBuf2WebSocketEncoder.java",
    "content": "package org.jmqtt.mqtt.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToMessageEncoder;\nimport io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;\n\nimport java.util.List;\n\npublic class ByteBuf2WebSocketEncoder extends MessageToMessageEncoder<ByteBuf> {\n    @Override\n    protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {\n        BinaryWebSocketFrame fram = new BinaryWebSocketFrame();\n        fram.content().writeBytes(byteBuf);\n        list.add(fram);\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/codec/WebSocket2ByteBufDecoder.java",
    "content": "package org.jmqtt.mqtt.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.MessageToMessageDecoder;\nimport io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;\n\nimport java.util.List;\n\npublic class WebSocket2ByteBufDecoder extends MessageToMessageDecoder<BinaryWebSocketFrame> {\n\n    @Override\n    protected void decode(ChannelHandlerContext channelHandlerContext, BinaryWebSocketFrame binaryWebSocketFrame, List<Object> list) throws Exception {\n        ByteBuf byteBuf = binaryWebSocketFrame.content();\n        byteBuf.retain();\n        list.add(byteBuf);\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/event/MqttEventListener.java",
    "content": "package org.jmqtt.mqtt.event;\n\nimport com.alibaba.fastjson.JSONObject;\nimport com.google.common.eventbus.Subscribe;\nimport io.netty.handler.codec.mqtt.MqttConnectReturnCode;\nimport org.jmqtt.bus.DeviceMessageManager;\nimport org.jmqtt.bus.enums.ClusterEventCodeEnum;\nimport org.jmqtt.bus.enums.MessageAckEnum;\nimport org.jmqtt.bus.event.GatewayListener;\nimport org.jmqtt.bus.model.ClusterEvent;\nimport org.jmqtt.bus.model.DeviceMessage;\nimport org.jmqtt.bus.model.DeviceSubscription;\nimport org.jmqtt.bus.subscription.model.Subscription;\nimport org.jmqtt.mqtt.ConnectManager;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.utils.MqttMessageUtil;\nimport org.jmqtt.mqtt.utils.MqttMsgHeader;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.jmqtt.support.remoting.RemotingHelper;\nimport org.slf4j.Logger;\n\nimport java.util.Map;\n\npublic class MqttEventListener implements GatewayListener {\n\n    private static final Logger log = JmqttLogger.mqttLog;\n\n    private static final String        currentIp  = RemotingHelper.getLocalAddr();\n\n    private DeviceMessageManager deviceMessageManager;\n\n    public MqttEventListener(DeviceMessageManager deviceMessageManager) {\n        this.deviceMessageManager = deviceMessageManager;\n    }\n\n    /**\n     * 分发到网关的消息，bus上检查设备是在线的，若不在线，说明是断开了\n     * mqtt网关处理两类事件：\n     * 1. 集群消息：分发给设备\n     * 2. 清理session：设备连接到其它服务器了，需要清理当前服务器上该设备的连接session（clientId 保持唯一）\n     * @param clusterEvent\n     */\n    @Override\n    @Subscribe\n    public void consume(ClusterEvent clusterEvent) {\n        ClusterEventCodeEnum eventCode = clusterEvent.getClusterEventCode();\n        switch (eventCode){\n            case MQTT_CLEAR_SESSION:\n                clearCurrentNodeSession(clusterEvent);\n                break;\n            case DISPATCHER_CLIENT_MESSAGE:\n                sendMessage2Client(clusterEvent);\n                break;\n            default:\n                LogUtil.warn(log,\"Mqtt gateway not support this event:{}\",eventCode);\n                break;\n        }\n    }\n\n\n    private void sendMessage2Client(ClusterEvent clusterEvent) {\n        DeviceMessage deviceMessage = JSONObject.parseObject(clusterEvent.getContent(),DeviceMessage.class);\n        Subscription subscription = clusterEvent.getSubscription();\n        if (deviceMessage == null ||subscription == null) {\n            LogUtil.error(log,\"[EVENT] Send event error, message or subscription is null,message:{},subscription:{}\",deviceMessage,subscription);\n            return;\n        }\n        MQTTConnection mqttConnection = ConnectManager.getInstance().getClient(subscription.getClientId());\n\n        Integer subQos = subscription.getProperty(MqttMsgHeader.QOS);\n        Integer msgQos = deviceMessage.getProperty(MqttMsgHeader.QOS);\n        if (subQos == null) {\n            subQos = 1;\n        }\n        if (msgQos == null) {\n            msgQos = 1;\n        }\n        int qos = MqttMessageUtil.getMinQos(subQos,msgQos);\n\n        // add to client inbox\n        if (qos == 0) {\n            this.deviceMessageManager.addClientInBoxMsg(subscription.getClientId(),deviceMessage.getId(), MessageAckEnum.ACK);\n        } else {\n            this.deviceMessageManager.addClientInBoxMsg(subscription.getClientId(),deviceMessage.getId(),MessageAckEnum.UN_ACK);\n        }\n\n        // offline can be optimize ,can carry to bus\n        if (mqttConnection == null) {\n            return;\n        }\n\n        mqttConnection.getBindedSession().sendMessage(deviceMessage);\n    }\n\n    private void clearCurrentNodeSession(ClusterEvent clusterEvent){\n        if (currentIp.equals(clusterEvent.getNodeIp())) {\n            return;\n        }\n\n        String clientId = clusterEvent.getContent();\n        MQTTConnection mqttConnection = ConnectManager.getInstance().getClient(clientId);\n        if (mqttConnection == null) {\n            LogUtil.info(log,\"MQTT connection is not exist.{}\",clientId);\n            return;\n        }\n        // 1. clear subscription tree\n        Map<String,DeviceSubscription> subscriptionSet = mqttConnection.getBindedSession().getSubscriptions();\n        if (subscriptionSet != null && subscriptionSet.size() > 0) {\n            for (String topic:subscriptionSet.keySet()) {\n                mqttConnection.getDeviceSubscriptionManager().onlyUnUnSubscribeFromTree(clientId,topic);\n                LogUtil.debug(log,\"[CLUSTER SESSION] clear other node's session.un subscribe clientId:{},topic:{}\",clientId,topic);\n            }\n        }\n\n        // 2. close channel\n        mqttConnection.abortConnection(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);\n\n        // 3. remove connection in cache\n        ConnectManager.getInstance().removeClient(clientId);\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/model/MqttTopic.java",
    "content": "package org.jmqtt.mqtt.model;\n\npublic class MqttTopic {\n\n    private String topicName;\n    private int    qos;\n\n    public MqttTopic(String topicName, int qos) {\n        this.topicName = topicName;\n        this.qos = qos;\n    }\n\n    public String getTopicName() {\n        return topicName;\n    }\n\n    public void setTopicName(String topicName) {\n        this.topicName = topicName;\n    }\n\n    public int getQos() {\n        return qos;\n    }\n\n    public void setQos(int qos) {\n        this.qos = qos;\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/MqttNettyUtils.java",
    "content": "package org.jmqtt.mqtt.netty;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport io.netty.util.Attribute;\nimport io.netty.util.AttributeKey;\nimport org.jmqtt.mqtt.MQTTConnection;\n\nimport java.io.IOException;\n\npublic class MqttNettyUtils {\n\n    public static final String ATTR_USERNAME = \"username\";\n    private static final String ATTR_CLIENTID = \"ClientID\";\n    private static final String CLEAN_SESSION = \"removeTemporaryQoS2\";\n    private static final String KEEP_ALIVE = \"keepAlive\";\n    private static final String               CONNECTION     = \"connection\";\n    private static final AttributeKey<Object> ATTR_KEY_CONNECTION = AttributeKey.valueOf(CONNECTION);\n    private static final AttributeKey<Object> ATTR_KEY_KEEPALIVE = AttributeKey.valueOf(KEEP_ALIVE);\n    private static final AttributeKey<Object> ATTR_KEY_CLEANSESSION = AttributeKey.valueOf(CLEAN_SESSION);\n    private static final AttributeKey<Object> ATTR_KEY_CLIENTID = AttributeKey.valueOf(ATTR_CLIENTID);\n    private static final AttributeKey<Object> ATTR_KEY_USERNAME = AttributeKey.valueOf(ATTR_USERNAME);\n\n    public static Object getAttribute(ChannelHandlerContext ctx, AttributeKey<Object> key) {\n        Attribute<Object> attr = ctx.channel().attr(key);\n        return attr.get();\n    }\n\n    public static void keepAlive(Channel channel, int keepAlive) {\n        channel.attr(ATTR_KEY_KEEPALIVE).set(keepAlive);\n    }\n\n    public static void cleanSession(Channel channel, boolean cleanSession) {\n        channel.attr(ATTR_KEY_CLEANSESSION).set(cleanSession);\n    }\n\n    public static boolean cleanSession(Channel channel) {\n        return (Boolean) channel.attr(ATTR_KEY_CLEANSESSION).get();\n    }\n\n    public static void clientID(Channel channel, String clientID) {\n        channel.attr(ATTR_KEY_CLIENTID).set(clientID);\n    }\n\n    public static String clientID(Channel channel) {\n        return (String) channel.attr(ATTR_KEY_CLIENTID).get();\n    }\n\n\n    public static void mqttConnection(Channel channel, MQTTConnection mqttConnection) {\n        channel.attr(ATTR_KEY_CONNECTION).set(mqttConnection);\n    }\n\n    public static MQTTConnection mqttConnection(Channel channel) {\n        return (MQTTConnection) channel.attr(ATTR_KEY_CONNECTION).get();\n    }\n\n    public static void userName(Channel channel, String username) {\n        channel.attr(ATTR_KEY_USERNAME).set(username);\n    }\n\n    public static String userName(Channel channel) {\n        return (String) channel.attr(ATTR_KEY_USERNAME).get();\n    }\n\n    public static MqttMessage validateMessage(Object message) throws IOException, ClassCastException {\n        MqttMessage msg = (MqttMessage) message;\n        if (msg.decoderResult() != null && msg.decoderResult().isFailure()) {\n            throw new IOException(\"invalid massage\", msg.decoderResult().cause());\n        }\n        if (msg.fixedHeader() == null) {\n            throw new IOException(\"Unknown packet, no fixedHeader present, no cause provided\");\n        }\n        return msg;\n    }\n\n    private MqttNettyUtils() {\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/MqttRemotingServer.java",
    "content": "package org.jmqtt.mqtt.netty;\n\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.buffer.PooledByteBufAllocator;\nimport io.netty.channel.*;\nimport io.netty.channel.epoll.EpollEventLoopGroup;\nimport io.netty.channel.epoll.EpollServerSocketChannel;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.handler.codec.http.HttpContentCompressor;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;\nimport io.netty.handler.codec.mqtt.MqttDecoder;\nimport io.netty.handler.codec.mqtt.MqttEncoder;\nimport io.netty.handler.timeout.IdleStateHandler;\nimport org.jmqtt.bus.BusController;\nimport org.jmqtt.mqtt.MQTTConnectionFactory;\nimport org.jmqtt.mqtt.codec.ByteBuf2WebSocketEncoder;\nimport org.jmqtt.mqtt.codec.WebSocket2ByteBufDecoder;\nimport org.jmqtt.support.config.BrokerConfig;\nimport org.jmqtt.support.config.NettyConfig;\nimport org.jmqtt.support.helper.MixAll;\nimport org.jmqtt.support.helper.ThreadFactoryImpl;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.jmqtt.support.remoting.RemotingService;\nimport org.slf4j.Logger;\n\n\n\npublic class MqttRemotingServer implements RemotingService {\n\n    int coreThreadNum = Runtime.getRuntime().availableProcessors();\n    private static final Logger                         log = JmqttLogger.remotingLog;\n    private              NettyConfig                    nettyConfig;\n    private              EventLoopGroup                 selectorGroup;\n    private              EventLoopGroup                 ioGroup;\n    private              Class<? extends ServerChannel> clazz;\n    private              BrokerConfig                   brokerConfig;\n    private              MQTTConnectionFactory          mqttConnectionFactory;\n\n\n    public MqttRemotingServer(BrokerConfig brokerConfig, NettyConfig nettyConfig, BusController busController) {\n        this.nettyConfig = nettyConfig;\n        this.brokerConfig = brokerConfig;\n        this.mqttConnectionFactory = new MQTTConnectionFactory(brokerConfig,busController);\n\n        if (!nettyConfig.getUseEpoll()) {\n            selectorGroup = new NioEventLoopGroup(coreThreadNum,\n                    new ThreadFactoryImpl(\"SelectorEventGroup\"));\n            ioGroup = new NioEventLoopGroup(coreThreadNum * 2,\n                    new ThreadFactoryImpl(\"IOEventGroup\"));\n            clazz = NioServerSocketChannel.class;\n        } else {\n            selectorGroup = new EpollEventLoopGroup(coreThreadNum,\n                    new ThreadFactoryImpl(\"SelectorEventGroup\"));\n            ioGroup = new EpollEventLoopGroup(coreThreadNum * 2,\n                    new ThreadFactoryImpl(\"IOEventGroup\"));\n            clazz = EpollServerSocketChannel.class;\n        }\n    }\n\n\n    @Override\n    public void start() {\n        // start TCP server\n        if (nettyConfig.getStartTcp()) {\n            startTcpServer(false, nettyConfig.getTcpPort());\n        }\n\n        if (nettyConfig.getStartSslTcp()) {\n            startTcpServer(true, nettyConfig.getSslTcpPort());\n        }\n\n        // start Websocket server\n        if (nettyConfig.getStartWebsocket()) {\n            startWebsocketServer(false, nettyConfig.getWebsocketPort());\n        }\n\n        if (nettyConfig.getStartSslWebsocket()) {\n            startWebsocketServer(true, nettyConfig.getSslWebsocketPort());\n        }\n    }\n\n    private void startWebsocketServer(boolean useSsl, Integer port) {\n        ServerBootstrap bootstrap = new ServerBootstrap();\n        bootstrap.group(selectorGroup, ioGroup)\n                .channel(clazz)\n                .option(ChannelOption.SO_BACKLOG, nettyConfig.getTcpBackLog())\n                .childOption(ChannelOption.TCP_NODELAY, nettyConfig.getTcpNoDelay())\n                .childOption(ChannelOption.SO_SNDBUF, nettyConfig.getTcpSndBuf())\n                .option(ChannelOption.SO_RCVBUF, nettyConfig.getTcpRcvBuf())\n                .option(ChannelOption.SO_REUSEADDR, nettyConfig.getTcpReuseAddr())\n                .childOption(ChannelOption.SO_KEEPALIVE, nettyConfig.getTcpKeepAlive())\n                .childHandler(new ChannelInitializer<SocketChannel>() {\n                    @Override\n                    protected void initChannel(SocketChannel socketChannel) throws Exception {\n                        ChannelPipeline pipeline = socketChannel.pipeline();\n                        if (useSsl) {\n                            pipeline.addLast(\"ssl\", NettySslHandler.getSslHandler(\n                                    socketChannel,\n                                    nettyConfig.getUseClientCA(),\n                                    nettyConfig.getSslKeyStoreType(),\n                                    brokerConfig.getJmqttHome() + nettyConfig.getSslKeyFilePath(),\n                                    nettyConfig.getSslManagerPwd(),\n                                    nettyConfig.getSslStorePwd()\n                            ));\n                        }\n                        pipeline.addLast(\"idleStateHandler\", new IdleStateHandler(0, 0, 60))\n                                .addLast(\"httpCodec\", new HttpServerCodec())\n                                .addLast(\"aggregator\", new HttpObjectAggregator(65535))\n                                .addLast(\"compressor \", new HttpContentCompressor())\n                                .addLast(\"webSocketHandler\", new WebSocketServerProtocolHandler(\"/mqtt\", MixAll.MQTT_VERSION_SUPPORT, false,65536))\n                                .addLast(\"webSocket2ByteBufDecoder\", new WebSocket2ByteBufDecoder())\n                                .addLast(\"byteBuf2WebSocketEncoder\", new ByteBuf2WebSocketEncoder())\n                                .addLast(\"mqttDecoder\", new MqttDecoder(nettyConfig.getMaxMsgSize()))\n                                .addLast(\"mqttEncoder\", MqttEncoder.INSTANCE)\n                                .addLast(\"nettyMqttHandler\", new NettyMqttHandler(mqttConnectionFactory));\n                    }\n                });\n        if (nettyConfig.getPooledByteBufAllocatorEnable()) {\n            bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);\n        }\n        try {\n            ChannelFuture future = bootstrap.bind(port).sync();\n            LogUtil.info(log,\"Start webSocket server {}  success,port = {}\", useSsl ? \"with ssl\" : \"\", port);\n        } catch (InterruptedException ex) {\n            LogUtil.error(log,\"Start webSocket server {} failure.cause={}\", useSsl ? \"with ssl\" : \"\", ex);\n        }\n    }\n\n    private void startTcpServer(boolean useSsl, Integer port) {\n        ServerBootstrap bootstrap = new ServerBootstrap();\n        bootstrap.group(selectorGroup, ioGroup)\n                .channel(clazz)\n                .option(ChannelOption.SO_BACKLOG, nettyConfig.getTcpBackLog())\n                .childOption(ChannelOption.TCP_NODELAY, nettyConfig.getTcpNoDelay())\n                .childOption(ChannelOption.SO_SNDBUF, nettyConfig.getTcpSndBuf())\n                .option(ChannelOption.SO_RCVBUF, nettyConfig.getTcpRcvBuf())\n                .option(ChannelOption.SO_REUSEADDR, nettyConfig.getTcpReuseAddr())\n                .childOption(ChannelOption.SO_KEEPALIVE, nettyConfig.getTcpKeepAlive())\n                .childHandler(new ChannelInitializer<SocketChannel>() {\n                    @Override\n                    protected void initChannel(SocketChannel socketChannel) throws Exception {\n                        ChannelPipeline pipeline = socketChannel.pipeline();\n                        if (useSsl) {\n                            pipeline.addLast(\"ssl\", NettySslHandler.getSslHandler(\n                                    socketChannel,\n                                    nettyConfig.getUseClientCA(),\n                                    nettyConfig.getSslKeyStoreType(),\n                                    brokerConfig.getJmqttHome() + nettyConfig.getSslKeyFilePath(),\n                                    nettyConfig.getSslManagerPwd(),\n                                    nettyConfig.getSslStorePwd()\n                            ));\n                        }\n                        pipeline.addLast(\"idleStateHandler\", new IdleStateHandler(60, 0, 0))\n                                .addLast(\"mqttEncoder\", MqttEncoder.INSTANCE)\n                                .addLast(\"mqttDecoder\", new MqttDecoder(nettyConfig.getMaxMsgSize()))\n                                .addLast(\"nettyMqttHandler\", new NettyMqttHandler(mqttConnectionFactory));\n                    }\n                });\n        if (nettyConfig.getPooledByteBufAllocatorEnable()) {\n            bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);\n        }\n        try {\n            ChannelFuture future = bootstrap.bind(port).sync();\n            LogUtil.info(log,\"Start tcp server {} success,port = {}\", useSsl ? \"with ssl\" : \"\", port);\n        } catch (InterruptedException ex) {\n            LogUtil.error(log,\"Start tcp server {} failure.cause={}\", useSsl ? \"with ssl\" : \"\", ex);\n        }\n    }\n\n\n    @Override\n    public void shutdown() {\n\n        this.mqttConnectionFactory.shutdown();\n\n        if (selectorGroup != null) {\n            selectorGroup.shutdownGracefully();\n        }\n        if (ioGroup != null) {\n            ioGroup.shutdownGracefully();\n        }\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/NettyMqttHandler.java",
    "content": "\npackage org.jmqtt.mqtt.netty;\n\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.MQTTConnectionFactory;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;\n\npublic class NettyMqttHandler extends ChannelDuplexHandler {\n\n    private static final Logger  log = JmqttLogger.remotingLog;\n\n    private MQTTConnectionFactory connectionFactory;\n\n    NettyMqttHandler(MQTTConnectionFactory connectionFactory) {\n        this.connectionFactory = connectionFactory;\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {\n        MqttMessage msg = MqttNettyUtils.validateMessage(message);\n        final MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        try {\n            mqttConnection.processProtocol(ctx,msg);\n        } catch (Throwable ex) {\n            LogUtil.error(log,\"Error processing protocol message: {}\", msg.fixedHeader().messageType(), ex);\n            ctx.channel().close().addListener(new ChannelFutureListener() {\n                @Override\n                public void operationComplete(ChannelFuture future) {\n                    LogUtil.info(log,\"Closed client channel due to exception in processing\");\n                }\n            });\n        }\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) {\n        MQTTConnection connection = connectionFactory.create(ctx.channel());\n        MqttNettyUtils.mqttConnection(ctx.channel(), connection);\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) {\n        final MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        mqttConnection.handleConnectionLost();\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n        LogUtil.error(log,\"Unexpected exception while processing MQTT message. Closing Netty channel. CId={}\",\n                MqttNettyUtils.clientID(ctx.channel()), cause);\n        ctx.close().addListener(CLOSE_ON_FAILURE);\n    }\n\n    @Override\n    public void channelWritabilityChanged(ChannelHandlerContext ctx) {\n        ctx.fireChannelWritabilityChanged();\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {\n        //if (evt instanceof InflightResender.ResendNotAckedPublishes) {\n        //    final MQTTConnection mqttConnection = mqttConnection(ctx.channel());\n        //    mqttConnection.resendNotAckedPublishes();\n        //}\n        ctx.fireUserEventTriggered(evt);\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/NettySslHandler.java",
    "content": "package org.jmqtt.mqtt.netty;\n\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.handler.ssl.*;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLEngine;\nimport javax.net.ssl.TrustManagerFactory;\nimport java.io.FileInputStream;\nimport java.io.InputStream;\nimport java.security.KeyStore;\n\npublic class NettySslHandler {\n    private static final Logger log = JmqttLogger.remotingLog;\n\n    public static ChannelHandler getSslHandler(SocketChannel channel, boolean useClientCA, String sslKeyStoreType, String sslKeyFilePath, String sslManagerPwd, String sslStorePwd) {\n\n        SslContext sslContext = createSSLContext(useClientCA, sslKeyStoreType, sslKeyFilePath, sslManagerPwd, sslStorePwd);\n        SSLEngine sslEngine = sslContext.newEngine(\n                channel.alloc(),\n                channel.remoteAddress().getHostString(),\n                channel.remoteAddress().getPort());\n        // server mode\n        sslEngine.setUseClientMode(false);\n        if (useClientCA) {\n            sslEngine.setNeedClientAuth(true);\n        }\n        return new SslHandler(sslEngine);\n    }\n\n    private static SslContext createSSLContext(boolean useClientCA, String sslKeyStoreType, String sslKeyFilePath, String sslManagerPwd, String sslStorePwd) {\n        try {\n            InputStream ksInputStream = new FileInputStream(sslKeyFilePath);\n            KeyStore ks = KeyStore.getInstance(sslKeyStoreType);\n            ks.load(ksInputStream, sslStorePwd.toCharArray());\n\n\n            final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());\n            kmf.init(ks, sslManagerPwd.toCharArray());\n            SslContextBuilder contextBuilder = SslContextBuilder.forServer(kmf);\n\n            // whether need client CA(two-way authentication)\n            if (useClientCA) {\n                contextBuilder.clientAuth(ClientAuth.REQUIRE);\n                TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n                tmf.init(ks);\n                contextBuilder.trustManager(tmf);\n            }\n            return contextBuilder.sslProvider(SslProvider.valueOf(\"JDK\")).build();\n        } catch (Exception ex) {\n            LogUtil.error(log,\"Create ssl context failure.cause={}\", ex);\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/RequestProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\n\npublic interface RequestProcessor {\n\n    /**\n     * handle mqtt message processor\n     * ps：阅读mqtt协议代码从这里的实现开始\n     */\n    void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage);\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/ConnectProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttConnAckMessage;\nimport io.netty.handler.codec.mqtt.MqttConnectMessage;\nimport io.netty.handler.codec.mqtt.MqttConnectReturnCode;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\nimport org.jmqtt.mqtt.utils.MqttMessageUtil;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.jmqtt.support.remoting.RemotingHelper;\nimport org.slf4j.Logger;\n\n/**\n * mqtt 客户端连接逻辑处理 强制约束：jmqtt在未返回conAck之前，不接收其它任何mqtt协议报文（mqtt协议可以允许） TODO mqtt5 协议支持\n */\npublic class ConnectProcessor implements RequestProcessor {\n\n    private static final Logger log = JmqttLogger.mqttLog;\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx,MqttMessage mqttMessage) {\n        MqttConnectMessage connectMessage = (MqttConnectMessage) mqttMessage;\n        MqttConnectReturnCode returnCode = null;\n        int mqttVersion = connectMessage.variableHeader().version();\n        String clientId = connectMessage.payload().clientIdentifier();\n        String userName = connectMessage.payload().userName();\n        byte[] password = connectMessage.payload().passwordInBytes();\n        boolean cleanSession = connectMessage.variableHeader().isCleanSession();\n        MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        boolean sessionPresent = false;\n        try {\n\n            if (!versionValid(mqttVersion)) {\n                returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION;\n            } else if (!mqttConnection.clientIdVerify(clientId)) {\n                returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED;\n            } else if (mqttConnection.onBlackList(RemotingHelper.getRemoteAddr(ctx.channel()), clientId)) {\n                returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;\n            } else if (!mqttConnection.login(clientId, userName, password)) {\n                returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD;\n            } else {\n                // 1. 设置心跳\n                int heartbeatSec = connectMessage.variableHeader().keepAliveTimeSeconds();\n                if (!mqttConnection.keepAlive(heartbeatSec)) {\n                    LogUtil.warn(log,\"[CONNECT] -> set heartbeat failure,clientId:{},heartbeatSec:{}\", clientId, heartbeatSec);\n                    throw new Exception(\"set heartbeat failure\");\n                }\n                // 2. 处理连接\n                sessionPresent = mqttConnection.createOrReopenSession(connectMessage);\n\n                returnCode = MqttConnectReturnCode.CONNECTION_ACCEPTED;\n                MqttNettyUtils.clientID(ctx.channel(), clientId);\n            }\n\n            if (returnCode != MqttConnectReturnCode.CONNECTION_ACCEPTED) {\n                ctx.close();\n                LogUtil.warn(log,\"[CONNECT] -> {} connect failure,returnCode={}\", clientId, returnCode);\n            }\n            MqttConnAckMessage ackMessage = MqttMessageUtil.getConnectAckMessage(returnCode, sessionPresent);\n\n            final boolean hasOldSession = sessionPresent;\n            ctx.writeAndFlush(ackMessage).addListener(new ChannelFutureListener() {\n                @Override\n                public void operationComplete(ChannelFuture future) throws Exception {\n                    LogUtil.info(log,\"[CONNECT] -> {} connect to this mqtt server\", clientId);\n                    if (future.isSuccess()) {\n                        // store session\n                        mqttConnection.storeSession();\n                        // send unAckMessages\n                        if (!cleanSession && hasOldSession) {\n                            mqttConnection.reSendMessage2Client();\n                        }\n                    } else {\n                        LogUtil.error(log,\"[CONNECT] -> send connect ack error.\");\n                        mqttConnection.abortConnection(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);\n                    }\n                }\n            });\n            //LogUtil.warn(log,\"Connect Cost:{}\",(System.currentTimeMillis() - start));\n        } catch (Exception ex) {\n            LogUtil.warn(log,\"[CONNECT] -> Service Unavailable: cause={}\", ex);\n            returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE;\n            MqttConnAckMessage ackMessage = MqttMessageUtil.getConnectAckMessage(returnCode, sessionPresent);\n            ctx.writeAndFlush(ackMessage);\n            ctx.close();\n        }\n    }\n\n    private boolean versionValid(int mqttVersion) {\n        if (mqttVersion == 3 || mqttVersion == 4) {\n            return true;\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/DisconnectProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\n\n/**\n * 客户端主动发起断开连接：正常断连\n */\npublic class DisconnectProcessor implements RequestProcessor {\n\n    private static final Logger              log = JmqttLogger.mqttLog;\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        String clientId = MqttNettyUtils.clientID(ctx.channel());\n        if (mqttConnection == null) {\n            LogUtil.error(log,\"[DISCONNECT] -> {} hasn't connect before\",clientId );\n            return;\n        }\n        mqttConnection.processDisconnect();\n        ctx.close();\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PingProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\nimport org.jmqtt.mqtt.utils.MqttMessageUtil;\n\n/**\n * 心跳\n */\npublic class PingProcessor implements RequestProcessor {\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        MqttMessage pingRespMessage = MqttMessageUtil.getPingRespMessage();\n        ctx.writeAndFlush(pingRespMessage);\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubAckProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.slf4j.Logger;\n\n/**\n * 出栈消息接收到的客户端 pubAck报文：释放缓存的出栈消息\n */\npublic class PubAckProcessor implements RequestProcessor {\n\n    private static final Logger log = JmqttLogger.messageTraceLog;\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        mqttConnection.processPubAck(mqttMessage);\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubCompProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\n\n/**\n * 出栈消息接收到的qos2消息的pubComp报文：清除缓存的出栈消息\n */\npublic class PubCompProcessor implements RequestProcessor {\n\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        mqttConnection.processPubComp(mqttMessage);\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubRecProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\n\n/**\n * 出栈消息接收到qos2第一阶段的的pubRec报文: 丢弃消息，存储报文标志符\n * 发送pubRel报文\n */\npublic class PubRecProcessor implements RequestProcessor {\n\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        mqttConnection.processPubRec(mqttMessage);\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubRelProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.jmqtt.support.remoting.RemotingHelper;\nimport org.slf4j.Logger;\n\n/**\n * 客户端的pubRel报文：入栈消息qos2的第二阶段：\n * 1. 消息所有者转换为broker,开始分发消息\n * 2. 返回pubCom报文\n */\npublic class PubRelProcessor implements RequestProcessor {\n\n    private static final Logger log = JmqttLogger.messageTraceLog;\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        if(mqttConnection != null){\n            mqttConnection.processPubRelMessage(mqttMessage);\n        }else{\n            LogUtil.warn(log,\"[PubRelMessage] -> the client：{} disconnect to this server.\",mqttConnection.getClientId());\n            RemotingHelper.closeChannel(ctx.channel());\n        }\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PublishProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport io.netty.handler.codec.mqtt.MqttPublishMessage;\nimport io.netty.util.ReferenceCountUtil;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\n/**\n * 客户端publish消息到jmqtt broker TODO mqtt5实现,流控处理\n */\npublic class PublishProcessor implements RequestProcessor {\n\n    private static final Logger log = JmqttLogger.mqttLog;\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        try {\n            MqttPublishMessage publishMessage = (MqttPublishMessage) mqttMessage;\n            MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n            mqttConnection.processPublishMessage(publishMessage);\n        } catch (Throwable tr) {\n            LogUtil.warn(log, \"[PubMessage] -> Solve mqtt pub message exception:{}\", tr);\n        } finally {\n            ReferenceCountUtil.release(mqttMessage.payload());\n        }\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/SubscribeProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport io.netty.handler.codec.mqtt.MqttSubscribeMessage;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\n\n/**\n * 订阅报文逻辑处理\n * TODO mqtt5协议支持\n */\npublic class SubscribeProcessor implements RequestProcessor {\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        MqttSubscribeMessage subscribeMessage = (MqttSubscribeMessage) mqttMessage;\n        MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        mqttConnection.processSubscribe(subscribeMessage);\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/UnSubscribeProcessor.java",
    "content": "package org.jmqtt.mqtt.protocol.impl;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.mqtt.MqttMessage;\nimport io.netty.handler.codec.mqtt.MqttUnsubscribeMessage;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.netty.MqttNettyUtils;\nimport org.jmqtt.mqtt.protocol.RequestProcessor;\n\n/**\n * 取消订阅\n */\npublic class UnSubscribeProcessor implements RequestProcessor {\n\n    @Override\n    public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) {\n        MqttUnsubscribeMessage unsubscribeMessage = (MqttUnsubscribeMessage) mqttMessage;\n        MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel());\n        mqttConnection.processUnSubscribe(unsubscribeMessage);\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/retain/RetainMessageHandler.java",
    "content": "package org.jmqtt.mqtt.retain;\n\nimport org.jmqtt.bus.model.DeviceMessage;\n\nimport java.util.List;\n\npublic interface RetainMessageHandler {\n\n    List<DeviceMessage> getAllRetatinMessage();\n\n    void clearRetainMessage(String topic);\n\n    void storeRetainMessage(DeviceMessage deviceMessage);\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/retain/impl/RetainMessageHandlerImpl.java",
    "content": "package org.jmqtt.mqtt.retain.impl;\n\nimport org.apache.ibatis.session.SqlSession;\nimport org.jmqtt.bus.model.DeviceMessage;\nimport org.jmqtt.bus.store.DBCallback;\nimport org.jmqtt.bus.store.DBUtils;\nimport org.jmqtt.bus.store.daoobject.RetainMessageDO;\nimport org.jmqtt.mqtt.retain.RetainMessageHandler;\nimport org.jmqtt.support.helper.MixAll;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class RetainMessageHandlerImpl implements RetainMessageHandler {\n\n    private static final Logger log = JmqttLogger.mqttLog;\n\n    @Override\n    public List<DeviceMessage> getAllRetatinMessage() {\n\n        // If has too much retain message ,can optimize with paging query\n        List<RetainMessageDO> retainMessageDOList =  DBUtils.operate(new DBCallback() {\n            @Override\n            public List<RetainMessageDO> operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession,DBUtils.retainMessageMapperClass).getAllRetainMessage();\n            }\n        });\n        if (retainMessageDOList == null) {\n            return null;\n        }\n        List<DeviceMessage> deviceMessageList = new ArrayList<>();\n        retainMessageDOList.forEach(item -> {\n            DeviceMessage deviceMessage = new DeviceMessage();\n            deviceMessage.setId(item.getId());\n            deviceMessage.setProperties(MixAll.propToMap(item.getProperties()));\n            deviceMessage.setContent(item.getContent());\n            deviceMessage.setFromClientId(item.getFromClientId());\n            deviceMessage.setStoredTime(item.getStoredTime());\n            deviceMessage.setTopic(item.getTopic());\n            deviceMessageList.add(deviceMessage);\n        });\n        return deviceMessageList;\n    }\n\n    @Override\n    public void clearRetainMessage(String topic) {\n        DBUtils.operate(new DBCallback() {\n            @Override\n            public Integer operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession,DBUtils.retainMessageMapperClass).delRetainMessage(topic);\n            }\n        });\n    }\n\n    @Override\n    public void storeRetainMessage(DeviceMessage deviceMessage) {\n        RetainMessageDO retainMessageDO = convert(deviceMessage);\n        DBUtils.operate(new DBCallback() {\n            @Override\n            public Long operate(SqlSession sqlSession) {\n                return DBUtils.getMapper(sqlSession,DBUtils.retainMessageMapperClass).storeRetainMessage(retainMessageDO);\n            }\n        });\n        if (retainMessageDO.getId() == null) {\n            LogUtil.error(log,\"[RETAIN MESSAGE] store retain message index error.\");\n        }\n    }\n\n    private RetainMessageDO convert(DeviceMessage deviceMessage) {\n        RetainMessageDO retainMessageDO = new RetainMessageDO();\n        retainMessageDO.setTopic(deviceMessage.getTopic());\n        retainMessageDO.setFromClientId(deviceMessage.getFromClientId());\n        retainMessageDO.setContent(deviceMessage.getContent());\n        retainMessageDO.setStoredTime(deviceMessage.getStoredTime());\n        retainMessageDO.setProperties(MixAll.propToStr(deviceMessage.getProperties()));\n        return retainMessageDO;\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/session/MqttSession.java",
    "content": "\npackage org.jmqtt.mqtt.session;\n\nimport io.netty.handler.codec.mqtt.MqttPublishMessage;\nimport io.netty.handler.codec.mqtt.MqttQoS;\nimport org.jmqtt.bus.model.DeviceMessage;\nimport org.jmqtt.bus.model.DeviceSubscription;\nimport org.jmqtt.mqtt.MQTTConnection;\nimport org.jmqtt.mqtt.utils.MqttMessageUtil;\nimport org.jmqtt.mqtt.utils.MqttMsgHeader;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArraySet;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * mqtt session\n */\npublic class MqttSession {\n\n    private static final Logger log = JmqttLogger.mqttLog;\n\n    /**\n     * clientId uniqu in a cluseter\n     */\n    private           String                      clientId;\n    private           boolean                     cleanSession;\n    private transient AtomicInteger               lastPacketId              = new AtomicInteger(1);\n    private           int                         mqttVersion;\n    private           DeviceMessage               will;\n    private final     Map<Integer, DeviceMessage> qos2Receiving             = new ConcurrentHashMap<>();\n    private MQTTConnection                        mqttConnection;\n    private final Map<Integer,DeviceMessage>      outboundFlowMessages      = new ConcurrentHashMap<>();\n    private final Set<Integer>                    qos2OutboundFlowPacketIds = new CopyOnWriteArraySet<>();\n    private String                                clientIp;\n    private String                                serverIp;\n    private Map<String, DeviceSubscription>   subscriptions             = new HashMap<>();\n\n\n    public void addSubscription(DeviceSubscription deviceSubscription) {\n        this.subscriptions.put(deviceSubscription.getTopic(),deviceSubscription);\n    }\n\n    public void removeSubscription(String topic) {\n        this.subscriptions.remove(topic);\n    }\n\n    public void receivePubRec(int packetId){\n        this.qos2OutboundFlowPacketIds.add(packetId);\n    }\n\n    public boolean releaseQos2SecFlow(int packetId){\n        return this.qos2OutboundFlowPacketIds.remove(packetId);\n    }\n\n    public DeviceMessage releaseOutboundFlowMessage(int packetId){\n        DeviceMessage deviceMessage = outboundFlowMessages.remove(packetId);\n        if (deviceMessage == null) {\n            LogUtil.error(log,\"[PUBACK Or PUBREC] Release cache flow message,message is not success,packetId:{}\",packetId);\n        }\n        return deviceMessage;\n    }\n\n    public void sendMessage(DeviceMessage deviceMessage) {\n        Integer qos = deviceMessage.getProperty(MqttMsgHeader.QOS);\n        MqttQoS mqttQoS = qos == null ? MqttQoS.AT_LEAST_ONCE : MqttQoS.valueOf(qos);\n        switch (mqttQoS){\n            case AT_MOST_ONCE:\n                sendQos0Message(deviceMessage);\n                break;\n            case AT_LEAST_ONCE:\n                sendQos1Or2Message(deviceMessage);\n                break;\n            case EXACTLY_ONCE:\n                sendQos1Or2Message(deviceMessage);\n                break;\n            default:\n                LogUtil.error(log,\"[Outbound publish] unsupport qos.\",qos);\n                break;\n        }\n    }\n\n    private void sendQos0Message(DeviceMessage deviceMessage) {\n        MqttPublishMessage mqttPublishMessage = MqttMessageUtil.getPubMessage(deviceMessage,0);\n        mqttConnection.publishMessage(mqttPublishMessage);\n    }\n\n    private void sendQos1Or2Message(DeviceMessage deviceMessage) {\n        int packetId = nextPacketId();\n        // cache\n        outboundFlowMessages.put(packetId,deviceMessage);\n\n        MqttPublishMessage mqttPublishMessage = MqttMessageUtil.getPubMessage(deviceMessage,packetId);\n        mqttConnection.publishMessage(mqttPublishMessage);\n    }\n\n\n    public void receivedPublishQos2(int originPacketId,DeviceMessage deviceMessage) {\n        this.qos2Receiving.put(originPacketId,deviceMessage);\n    }\n\n    public DeviceMessage receivedPubRelQos2(int packgetId) {\n        return this.qos2Receiving.remove(packgetId);\n    }\n\n    public void clearWill(){\n        this.will = null;\n    }\n\n    public boolean hasWill(){\n        return will != null;\n    }\n\n    public int nextPacketId() {\n        return lastPacketId.updateAndGet(v -> v == 65535 ? 1 : v + 1);\n    }\n\n    /** setter getter  */\n    public String getClientId() {\n        return clientId;\n    }\n\n    public void setClientId(String clientId) {\n        this.clientId = clientId;\n    }\n\n    public boolean isCleanSession() {\n        return cleanSession;\n    }\n\n    public void setCleanSession(boolean cleanSession) {\n        this.cleanSession = cleanSession;\n    }\n\n    public int getMqttVersion() {\n        return mqttVersion;\n    }\n\n    public void setMqttVersion(int mqttVersion) {\n        this.mqttVersion = mqttVersion;\n    }\n\n    public DeviceMessage getWill() {\n        return will;\n    }\n\n    public void setWill(DeviceMessage will) {\n        this.will = will;\n    }\n\n\n    public MQTTConnection getMqttConnection() {\n        return mqttConnection;\n    }\n\n    public void setMqttConnection(MQTTConnection mqttConnection) {\n        this.mqttConnection = mqttConnection;\n    }\n\n    public String getClientIp() {\n        return clientIp;\n    }\n\n    public void setClientIp(String clientIp) {\n        this.clientIp = clientIp;\n    }\n\n    public String getServerIp() {\n        return serverIp;\n    }\n\n    public void setServerIp(String serverIp) {\n        this.serverIp = serverIp;\n    }\n\n    public Map<String, DeviceSubscription> getSubscriptions() {\n        return subscriptions;\n    }\n\n    public void setSubscriptions(Map<String, DeviceSubscription> subscriptions) {\n        this.subscriptions = subscriptions;\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/utils/MqttMessageUtil.java",
    "content": "package org.jmqtt.mqtt.utils;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler.codec.mqtt.*;\nimport org.jmqtt.bus.model.DeviceMessage;\n\nimport java.util.List;\n\n/**\n * transfer message from Message and MqttMessage\n */\npublic class MqttMessageUtil {\n\n    public static byte[] readBytesFromByteBuf(ByteBuf byteBuf){\n        byte[] bytes = new byte[byteBuf.readableBytes()];\n        byteBuf.readBytes(bytes);\n        return bytes;\n    }\n\n\n    public static MqttUnsubAckMessage getUnSubAckMessage(int messageId){\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBACK,false,MqttQoS.AT_MOST_ONCE,false,0);\n        MqttMessageIdVariableHeader idVariableHeader = MqttMessageIdVariableHeader.from(messageId);\n        return new MqttUnsubAckMessage(fixedHeader,idVariableHeader);\n    }\n\n    public static int getMessageId(MqttMessage mqttMessage){\n        MqttMessageIdVariableHeader idVariableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader();\n        return idVariableHeader.messageId();\n    }\n\n    public static int getMinQos(int qos1,int qos2){\n        if(qos1 < qos2){\n            return qos1;\n        }\n        return qos2;\n    }\n\n    public static MqttMessage getPubRelMessage(int messageId){\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREL,false,MqttQoS.AT_MOST_ONCE,false,0);\n        MqttMessageIdVariableHeader idVariableHeader = MqttMessageIdVariableHeader.from(messageId);\n        return new MqttMessage(fixedHeader,idVariableHeader);\n    }\n\n    public static MqttPublishMessage getPubMessage(DeviceMessage message,int packetId){\n        Integer qos = message.getProperty(MqttMsgHeader.QOS);\n        qos = qos == null ? 1 : qos;\n\n        Boolean dup = message.getProperty(MqttMsgHeader.DUP);\n        dup = dup == null ? false : dup;\n\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH,dup,MqttQoS.valueOf(qos),false,0);\n        MqttPublishVariableHeader publishVariableHeader = new MqttPublishVariableHeader(message.getTopic(),packetId);\n        ByteBuf heapBuf;\n        if(message.getContent() == null){\n            heapBuf = Unpooled.EMPTY_BUFFER;\n        }else{\n            heapBuf = Unpooled.wrappedBuffer(message.getContent());\n        }\n        return new MqttPublishMessage(fixedHeader,publishVariableHeader,heapBuf);\n    }\n\n    public static MqttMessage getSubAckMessage(int messageId, List<Integer> qos){\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBACK,false,MqttQoS.AT_MOST_ONCE,false,0);\n        MqttMessageIdVariableHeader idVariableHeader = MqttMessageIdVariableHeader.from(messageId);\n        MqttSubAckPayload subAckPayload = new MqttSubAckPayload(qos);\n        return new MqttSubAckMessage(fixedHeader,idVariableHeader,subAckPayload);\n    }\n\n    public static MqttMessage getPingRespMessage(){\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP,false,MqttQoS.AT_MOST_ONCE,false,0);\n        MqttMessage mqttMessage = new MqttMessage(fixedHeader);\n        return mqttMessage;\n    }\n\n    public static MqttMessage getPubComMessage(int messageId){\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP,false,MqttQoS.AT_MOST_ONCE,false,0);\n        MqttMessage mqttMessage = new MqttMessage(fixedHeader,MqttMessageIdVariableHeader.from(messageId));\n        return mqttMessage;\n    }\n\n    public static MqttMessage getPubRecMessage(int messageId){\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC,false,MqttQoS.AT_MOST_ONCE,false,0);\n        MqttMessage mqttMessage = new MqttMessage(fixedHeader,MqttMessageIdVariableHeader.from(messageId));\n        return mqttMessage;\n    }\n\n    public static MqttMessage getPubRecMessage(int messageId,boolean isDup){\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC,isDup,MqttQoS.AT_MOST_ONCE,false,0);\n        MqttMessage mqttMessage = new MqttMessage(fixedHeader,MqttMessageIdVariableHeader.from(messageId));\n        return mqttMessage;\n    }\n\n    public static MqttPubAckMessage getPubAckMessage(int messageId){\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK,false,MqttQoS.AT_MOST_ONCE,false,0);\n        MqttMessageIdVariableHeader idVariableHeader = MqttMessageIdVariableHeader.from(messageId);\n        return new MqttPubAckMessage(fixedHeader,idVariableHeader);\n    }\n\n    public static MqttConnAckMessage getConnectAckMessage(MqttConnectReturnCode returnCode,boolean sessionPresent){\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0);\n        MqttConnAckVariableHeader variableHeade = new MqttConnAckVariableHeader(returnCode,sessionPresent);\n        return new MqttConnAckMessage(fixedHeader,variableHeade);\n    }\n}\n"
  },
  {
    "path": "jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/utils/MqttMsgHeader.java",
    "content": "package org.jmqtt.mqtt.utils;\n\nimport io.netty.handler.codec.mqtt.MqttPublishMessage;\nimport org.jmqtt.bus.enums.MessageSourceEnum;\nimport org.jmqtt.bus.model.DeviceMessage;\n\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class MqttMsgHeader {\n\n    public static final String QOS  = \"qos\";\n\n    public static final String RETAIN = \"retain\";\n\n    public static final String DUP = \"dup\";\n\n    public static final String CLEAN_SESSION = \"cleansession\";\n\n    public static final DeviceMessage buildDeviceMessage(boolean retain,int qos,String topic,byte[] content){\n        DeviceMessage deviceMessage = new DeviceMessage();\n        deviceMessage.setId(null);\n        deviceMessage.setContent(content);\n        deviceMessage.setSource(MessageSourceEnum.DEVICE);\n        deviceMessage.setStoredTime(new Date());\n        deviceMessage.setTopic(topic);\n\n        Map<String,Object> properties = new HashMap<>();\n        properties.put(QOS,qos);\n        properties.put(RETAIN,retain);\n        deviceMessage.setProperties(properties);\n        return deviceMessage;\n    }\n\n    public static final DeviceMessage buildDeviceMessage(MqttPublishMessage mqttPublishMessage){\n        return buildDeviceMessage(mqttPublishMessage.fixedHeader().isRetain(),mqttPublishMessage.fixedHeader().qosLevel().value(),\n                mqttPublishMessage.variableHeader().topicName(),MqttMessageUtil.readBytesFromByteBuf(mqttPublishMessage.content()));\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-support/README.md",
    "content": "通用支持，支持消息总线模块Bus及各协议网关处理\n"
  },
  {
    "path": "jmqtt-support/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" 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    <parent>\n        <artifactId>jmqtt</artifactId>\n        <groupId>org.jmqtt</groupId>\n        <version>3.0.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>jmqtt-support</artifactId>\n\n    <name>jmqtt-support</name>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-all</artifactId>\n        </dependency>\n\n    </dependencies>\n</project>\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/config/BrokerConfig.java",
    "content": "package org.jmqtt.support.config;\n\nimport java.io.File;\n\npublic class BrokerConfig {\n\n    // 配置conf文件的所在位置，logback，properties文件等所在位置\n    private String jmqttHome = System.getenv(\"JMQTT_HOME\") != null ? System.getenv(\"JMQTT_HOME\") : System.getProperty(\"user.dir\") +\n            File.separator + \"jmqtt-broker\" + File.separator + \"src\" + File.separator + \"main\" + File.separator + \"resources\" + File.separator + \"conf\";\n    private String logLevel = \"INFO\";\n\n    private String  version         = \"3.0.2\";\n    private boolean anonymousEnable = true;\n\n    private int pollThreadNum = Runtime.getRuntime().availableProcessors() * 2;\n\n    // 采用拉消息方式时，一次最多拉的消息数目\n    private int maxPollEventNum  = 10;\n    private int pollWaitInterval = 10;//ms\n\n    /* db相关配置 */\n    private String driver   = \"com.mysql.jdbc.Driver\";\n    private String url\n                            = \"jdbc:mysql://localhost:3306/jmqtt?characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false\"\n            + \"&maxReconnects=10&useSSL=false\";\n    private String username = \"root\";\n    private String password = \"CallmeZ2013\";\n\n    public int getPollThreadNum() {\n        return pollThreadNum;\n    }\n\n    public void setPollThreadNum(int pollThreadNum) {\n        this.pollThreadNum = pollThreadNum;\n    }\n\n    public String getJmqttHome() {\n        return jmqttHome;\n    }\n\n    public void setJmqttHome(String jmqttHome) {\n        this.jmqttHome = jmqttHome;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public void setVersion(String version) {\n        this.version = version;\n    }\n\n    public boolean getAnonymousEnable() {\n        return anonymousEnable;\n    }\n\n    public void setAnonymousEnable(boolean anonymousEnable) {\n        this.anonymousEnable = anonymousEnable;\n    }\n\n    public String getDriver() {\n        return driver;\n    }\n\n    public void setDriver(String driver) {\n        this.driver = driver;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public String getUsername() {\n        return username;\n    }\n\n    public void setUsername(String username) {\n        this.username = username;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    public boolean isAnonymousEnable() {\n        return anonymousEnable;\n    }\n\n    public int getMaxPollEventNum() {\n        return maxPollEventNum;\n    }\n\n    public void setMaxPollEventNum(int maxPollEventNum) {\n        this.maxPollEventNum = maxPollEventNum;\n    }\n\n    public int getPollWaitInterval() {\n        return pollWaitInterval;\n    }\n\n    public void setPollWaitInterval(int pollWaitInterval) {\n        this.pollWaitInterval = pollWaitInterval;\n    }\n\n    public String getLogLevel() {\n        return logLevel;\n    }\n\n    public void setLogLevel(String logLevel) {\n        this.logLevel = logLevel;\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/config/NettyConfig.java",
    "content": "package org.jmqtt.support.config;\n\npublic class NettyConfig {\n    private int tcpBackLog = 1024;\n    private boolean tcpNoDelay = false;\n    private boolean tcpReuseAddr = true;\n    private boolean tcpKeepAlive = false;\n    private int tcpSndBuf = 65536;\n    private int tcpRcvBuf = 65536;\n    private boolean useEpoll = false;\n    private boolean pooledByteBufAllocatorEnable = false;\n\n    /**\n     * tcp port default 1883\n     */\n    private boolean startTcp = true;\n    private int tcpPort = 1883;\n\n    /**\n     * websocket port default 1884\n     */\n    private boolean startWebsocket = true;\n    private int websocketPort = 1884;\n\n    /**\n     * http port default 1881\n     */\n    private boolean startHttp = true;\n    private int httpPort = 1881;\n\n    /**\n     * tcp port with ssl default 8883\n     */\n    private boolean startSslTcp = true;\n    private int sslTcpPort = 8883;\n\n    /**\n     * websocket port with ssl default 8884\n     */\n    private boolean startSslWebsocket = true;\n    private int sslWebsocketPort = 8884;\n\n    /**\n     * SSL setting\n     */\n    private boolean useClientCA = false;\n    private String sslKeyStoreType = \"PKCS12\";\n    private String sslKeyFilePath = \"/conf/server.pfx\";\n    private String sslManagerPwd = \"654321\";\n    private String sslStorePwd = \"654321\";\n    /**\n     * max mqtt message size\n     */\n    private int maxMsgSize = 512 * 1024;\n\n    public int getTcpBackLog() {\n        return tcpBackLog;\n    }\n\n    public void setTcpBackLog(int tcpBackLog) {\n        this.tcpBackLog = tcpBackLog;\n    }\n\n    public boolean getTcpNoDelay() {\n        return tcpNoDelay;\n    }\n\n    public void setTcpNoDelay(boolean tcpNoDelay) {\n        this.tcpNoDelay = tcpNoDelay;\n    }\n\n    public boolean getTcpReuseAddr() {\n        return tcpReuseAddr;\n    }\n\n    public void setTcpReuseAddr(boolean tcpReuseAddr) {\n        this.tcpReuseAddr = tcpReuseAddr;\n    }\n\n    public boolean getTcpKeepAlive() {\n        return tcpKeepAlive;\n    }\n\n    public void setTcpKeepAlive(boolean tcpKeepAlive) {\n        this.tcpKeepAlive = tcpKeepAlive;\n    }\n\n    public int getTcpSndBuf() {\n        return tcpSndBuf;\n    }\n\n    public void setTcpSndBuf(int tcpSndBuf) {\n        this.tcpSndBuf = tcpSndBuf;\n    }\n\n    public int getTcpRcvBuf() {\n        return tcpRcvBuf;\n    }\n\n    public void setTcpRcvBuf(int tcpRcvBuf) {\n        this.tcpRcvBuf = tcpRcvBuf;\n    }\n\n    public int getTcpPort() {\n        return tcpPort;\n    }\n\n    public void setTcpPort(int tcpPort) {\n        this.tcpPort = tcpPort;\n    }\n\n    public int getMaxMsgSize() {\n        return maxMsgSize;\n    }\n\n    public void setMaxMsgSize(int maxMsgSize) {\n        this.maxMsgSize = maxMsgSize;\n    }\n\n    public boolean getUseEpoll() {\n        return useEpoll;\n    }\n\n    public void setUseEpoll(boolean useEpoll) {\n        this.useEpoll = useEpoll;\n    }\n\n    public boolean getPooledByteBufAllocatorEnable() {\n        return pooledByteBufAllocatorEnable;\n    }\n\n    public void setPooledByteBufAllocatorEnable(boolean pooledByteBufAllocatorEnable) {\n        this.pooledByteBufAllocatorEnable = pooledByteBufAllocatorEnable;\n    }\n\n    public boolean getStartWebsocket() {\n        return startWebsocket;\n    }\n\n    public void setStartWebsocket(boolean startWebsocket) {\n        this.startWebsocket = startWebsocket;\n    }\n\n    public int getWebsocketPort() {\n        return websocketPort;\n    }\n\n    public void setWebsocketPort(int websocketPort) {\n        this.websocketPort = websocketPort;\n    }\n\n    public boolean getStartTcp() {\n        return startTcp;\n    }\n\n    public void setStartTcp(boolean startTcp) {\n        this.startTcp = startTcp;\n    }\n\n\n    public boolean getStartSslTcp() {\n        return startSslTcp;\n    }\n\n    public void setStartSslTcp(boolean startSslTcp) {\n        this.startSslTcp = startSslTcp;\n    }\n\n\n    public int getSslTcpPort() {\n        return sslTcpPort;\n    }\n\n    public void setSslTcpPort(int sslTcpPort) {\n        this.sslTcpPort = sslTcpPort;\n    }\n\n    public boolean getStartSslWebsocket() {\n        return startSslWebsocket;\n    }\n\n    public void setStartSslWebsocket(boolean startSslWebsocket) {\n        this.startSslWebsocket = startSslWebsocket;\n    }\n\n    public int getSslWebsocketPort() {\n        return sslWebsocketPort;\n    }\n\n    public void setSslWebsocketPort(int sslWebsocketPort) {\n        this.sslWebsocketPort = sslWebsocketPort;\n    }\n\n    public boolean getUseClientCA() {\n        return useClientCA;\n    }\n\n    public void setUseClientCA(boolean useClientCA) {\n        this.useClientCA = useClientCA;\n    }\n\n    public String getSslKeyStoreType() {\n        return sslKeyStoreType;\n    }\n\n    public void setSslKeyStoreType(String sslKeyStoreType) {\n        this.sslKeyStoreType = sslKeyStoreType;\n    }\n\n    public String getSslKeyFilePath() {\n        return sslKeyFilePath;\n    }\n\n    public void setSslKeyFilePath(String sslKeyFilePath) {\n        this.sslKeyFilePath = sslKeyFilePath;\n    }\n\n    public String getSslManagerPwd() {\n        return sslManagerPwd;\n    }\n\n    public void setSslManagerPwd(String sslManagerPwd) {\n        this.sslManagerPwd = sslManagerPwd;\n    }\n\n    public String getSslStorePwd() {\n        return sslStorePwd;\n    }\n\n    public void setSslStorePwd(String sslStorePwd) {\n        this.sslStorePwd = sslStorePwd;\n    }\n\n    public boolean getStartHttp() {\n        return startHttp;\n    }\n\n    public void setStartHttp(boolean startHttp) {\n        this.startHttp = startHttp;\n    }\n\n    public int getHttpPort() {\n        return httpPort;\n    }\n\n    public void setHttpPort(int httpPort) {\n        this.httpPort = httpPort;\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingConnectException.java",
    "content": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingConnectException extends RemotingException implements Serializable {\n\n    private static final long serialVersionUID = -412312312370505110L;\n\n    public RemotingConnectException(String addr) {\n        this(addr,null);\n    }\n\n    public RemotingConnectException(String addr, Throwable throwable) {\n        super(\"connect to <\" + addr + \"> failed\", throwable);\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingException.java",
    "content": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingException extends Exception implements Serializable {\n    private static final long serialVersionUID = -46545454570505110L;\n\n    public RemotingException(String message){\n        super(message);\n    }\n\n    public RemotingException(String message,Throwable throwable){\n        super(message,throwable);\n    }\n\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingSendRequestException.java",
    "content": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingSendRequestException extends RemotingException implements Serializable {\n\n    public RemotingSendRequestException(String message) {\n        this(message,null);\n    }\n\n    public RemotingSendRequestException(String message, Throwable throwable) {\n        super(message, throwable);\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingTimeoutException.java",
    "content": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingTimeoutException extends RemotingException implements Serializable {\n\n    private static final long serialVersionUID = -56656565470505110L;\n\n    public RemotingTimeoutException(long timeoutMillis) {\n        this(timeoutMillis,null);\n    }\n\n    public RemotingTimeoutException(long timeoutMillis, Throwable throwable) {\n        super(\"Send timeout time: <\" + timeoutMillis + \">\", throwable);\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingTooMuchRequestException.java",
    "content": "package org.jmqtt.support.exception;\n\nimport java.io.Serializable;\n\npublic class RemotingTooMuchRequestException  extends RemotingException implements Serializable {\n\n    private static final long serialVersionUID = -865546545670505110L;\n\n    public RemotingTooMuchRequestException(String message) {\n        super(message);\n    }\n\n    public RemotingTooMuchRequestException(String message, Throwable throwable) {\n        super(message, throwable);\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/helper/MixAll.java",
    "content": "package org.jmqtt.support.helper;\n\nimport com.alibaba.fastjson.JSONObject;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.text.SimpleDateFormat;\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.zip.Deflater;\nimport java.util.zip.DeflaterOutputStream;\nimport java.util.zip.InflaterInputStream;\n\n\npublic class MixAll {\n\n    public static String MQTT_VERSION_SUPPORT = \"mqtt,mqtt3.1,mqttv3.1.1\";\n\n    public static boolean createIfNotExistsDir(File file) {\n        return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());\n    }\n\n    public static String dateFormater(long time) {\n        SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n        return dateFormat.format(time);\n    }\n\n\n    public static <T> T pluginInit(String classFullName){\n        try {\n            Class<?> clazz = Class.forName(classFullName);\n            return (T) clazz.newInstance();\n        } catch (Exception e) {\n            System.err.println(\"Load class and init error,\" + e);\n            System.exit(-1);\n        }\n        return null;\n    }\n\n    public static Map<String,Object> propToMap(String propStr) {\n        if (propStr == null) {\n            return null;\n        }\n        return JSONObject.parseObject(propStr,Map.class);\n    }\n\n    public static String propToStr(Map<String,Object> properties) {\n        if (properties == null) {\n            return null;\n        }\n        return JSONObject.toJSONString(properties);\n    }\n\n    public static boolean isEmpty(Collection collection) {\n        if (collection == null || collection.size() == 0) {\n            return true;\n        }\n        return false;\n    }\n\n    public static String getLocalIp(){\n        try {\n            return InetAddress.getLocalHost().getHostAddress();\n        } catch (UnknownHostException e) {\n            e.printStackTrace();\n        }\n        return \"UN_KNOW\";\n    }\n\n    public static byte[] uncompress(final byte[] src) throws IOException {\n        byte[] result = src;\n        byte[] uncompressData = new byte[src.length];\n        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(src);\n        InflaterInputStream inflaterInputStream = new InflaterInputStream(byteArrayInputStream);\n        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length);\n\n        try {\n            while (true) {\n                int len = inflaterInputStream.read(uncompressData, 0, uncompressData.length);\n                if (len <= 0) {\n                    break;\n                }\n                byteArrayOutputStream.write(uncompressData, 0, len);\n            }\n            byteArrayOutputStream.flush();\n            result = byteArrayOutputStream.toByteArray();\n        } catch (IOException e) {\n            throw e;\n        } finally {\n            try {\n                byteArrayInputStream.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            try {\n                inflaterInputStream.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            try {\n                byteArrayOutputStream.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        return result;\n    }\n\n    public static byte[] compress(final byte[] src, final int level) throws IOException {\n        byte[] result = src;\n        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length);\n        Deflater deflater = new Deflater(level);\n        DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);\n        try {\n            deflaterOutputStream.write(src);\n            deflaterOutputStream.flush();\n            deflaterOutputStream.close();\n            result = byteArrayOutputStream.toByteArray();\n        } catch (IOException e) {\n            deflater.end();\n            throw e;\n        } finally {\n            try {\n                byteArrayOutputStream.close();\n            } catch (IOException ignored) {\n            }\n            deflater.end();\n        }\n        return result;\n    }\n\n    public static void printProperties(Logger log, Object obj) {\n        Class clazz = obj.getClass();\n        Field[] fields = clazz.getDeclaredFields();\n        if (fields != null) {\n            for (Field field : fields) {\n                try {\n                    field.setAccessible(true);\n                    String key = field.getName();\n                    Object value = field.get(obj);\n                    LogUtil.info(log,\"{} = {}\", key, value);\n                } catch (IllegalAccessException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * transfer properties 2 pojo\n     */\n    public static void properties2POJO(Properties properties, Object obj) {\n        Method[] methods = obj.getClass().getMethods();\n        for (Method method : methods) {\n            String methodName = method.getName();\n            if (methodName.startsWith(\"set\")) {\n                try {\n                    String tmp = methodName.substring(4);\n                    String firstChar = methodName.substring(3, 4);\n                    String key = firstChar.toLowerCase() + tmp;\n                    String value = properties.getProperty(key);\n                    if (value != null) {\n                        Class<?>[] types = method.getParameterTypes();\n                        if (types != null && types.length > 0) {\n                            String type = types[0].getSimpleName();\n                            Object arg = null;\n                            if (type.equals(\"int\") || type.equals(\"Integer\")) {\n                                arg = Integer.parseInt(value);\n                            } else if (type.equals(\"float\") || type.equals(\"Float\")) {\n                                arg = Float.parseFloat(value);\n                            } else if (type.equals(\"double\") || type.equals(\"Double\")) {\n                                arg = Double.parseDouble(value);\n                            } else if (type.equals(\"long\") || type.equals(\"Long\")) {\n                                arg = Long.parseLong(value);\n                            } else if (type.equals(\"boolean\") || type.equals(\"Boolean\")) {\n                                arg = Boolean.parseBoolean(value);\n                            } else if (type.equals(\"String\")) {\n                                arg = value;\n                            } else {\n                                continue;\n                            }\n                            method.invoke(obj, arg);\n                        }\n                    }\n\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/helper/Pair.java",
    "content": "package org.jmqtt.support.helper;\n\npublic class Pair<T,K> {\n    private T object1;\n    private K object2;\n\n    public Pair(T object1, K object2) {\n        this.object1 = object1;\n        this.object2 = object2;\n    }\n\n    public T getObject1() {\n        return object1;\n    }\n\n    public void setObject1(T object1) {\n        this.object1 = object1;\n    }\n\n    public K getObject2() {\n        return object2;\n    }\n\n    public void setObject2(K object2) {\n        this.object2 = object2;\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/helper/RejectHandler.java",
    "content": "package org.jmqtt.support.helper;\n\nimport java.util.concurrent.RejectedExecutionException;\nimport java.util.concurrent.RejectedExecutionHandler;\nimport java.util.concurrent.ThreadPoolExecutor;\n\npublic class RejectHandler implements RejectedExecutionHandler {\n    private String task;\n    private int maxBlockQueueSize;\n\n    public RejectHandler(String task,int maxBlockQueueSize){\n        this.task = task;\n        this.maxBlockQueueSize = maxBlockQueueSize;\n    };\n\n    @Override\n    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {\n        throw new RejectedExecutionException(\"Task:\" + task + \",maxBlockQueueSize:\" + maxBlockQueueSize\n                + \",Thread:\" + r.toString());\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/helper/ThreadFactoryImpl.java",
    "content": "package org.jmqtt.support.helper;\n\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class ThreadFactoryImpl implements ThreadFactory {\n\n    private AtomicInteger counter = new AtomicInteger(0);\n    private String threadName;\n\n    public ThreadFactoryImpl(String threadName){\n        this.threadName = threadName;\n    }\n\n    @Override\n    public Thread newThread(Runnable r) {\n        return new Thread(r,threadName + \"_\" + this.counter.incrementAndGet());\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/log/JmqttLogger.java",
    "content": "package org.jmqtt.support.log;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic interface JmqttLogger {\n\n    Logger brokerLog = LoggerFactory.getLogger(\"brokerLog\");\n\n    Logger mqttLog = LoggerFactory.getLogger(\"mqttLog\");\n\n    Logger busLog = LoggerFactory.getLogger(\"busLog\");\n\n    Logger messageTraceLog = LoggerFactory.getLogger(\"messageTraceLog\");\n\n    Logger remotingLog = LoggerFactory.getLogger(\"remotingLog\");\n\n    Logger storeLog = LoggerFactory.getLogger(\"storeLog\");\n\n    Logger otherLog = LoggerFactory.getLogger(\"otherLog\");\n\n    Logger monitorLog = LoggerFactory.getLogger(\"monitorLog\");\n\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/log/LogUtil.java",
    "content": "package org.jmqtt.support.log;\n\nimport org.slf4j.Logger;\n\npublic class LogUtil {\n\n    public static void debug(Logger log,String desc,Object... param){\n        if (log != null) {\n            if (log.isDebugEnabled()) {\n                log.debug(desc,param);\n            }\n        }\n    }\n\n    public static void info(Logger log,String desc,Object... param){\n        if (log != null) {\n            if (log.isInfoEnabled()) {\n                log.info(desc,param);\n            }\n        }\n    }\n\n    public static void warn(Logger log,String desc,Object... param){\n        if (log != null) {\n            if (log.isWarnEnabled()) {\n                log.warn(desc,param);\n            }\n        }\n    }\n\n    public static void error(Logger log,String desc,Object... param){\n        if (log != null) {\n            if (log.isErrorEnabled()) {\n                log.error(desc,param);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/remoting/RemotingHelper.java",
    "content": "package org.jmqtt.support.remoting;\n\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport org.jmqtt.support.log.JmqttLogger;\nimport org.jmqtt.support.log.LogUtil;\nimport org.slf4j.Logger;\n\nimport java.net.*;\nimport java.util.ArrayList;\nimport java.util.Enumeration;\n\npublic class RemotingHelper {\n\n    private static final Logger log = JmqttLogger.remotingLog;\n\n    public static void closeChannel(Channel channel){\n        String remoteAddr = getRemoteAddr(channel);\n        channel.close().addListener(new ChannelFutureListener() {\n            @Override\n            public void operationComplete(ChannelFuture channelFuture) throws Exception {\n                LogUtil.info(log,\"[closeChannel] -> close the connection,addr={},result={}\",remoteAddr,channelFuture.isSuccess());\n            }\n        });\n    }\n\n    public static SocketAddress string2SocketAddress(final String addr){\n        String[] s = addr.split(\":\");\n        InetSocketAddress isa = new InetSocketAddress(s[0],Integer.parseInt(s[1]));\n        return isa;\n    }\n\n    public static String getLocalAddr(){\n        try {\n            Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();\n            ArrayList<String> ipv4Result = new ArrayList<String>();\n            ArrayList<String> ipv6Result = new ArrayList<String>();\n            while (enumeration.hasMoreElements()) {\n                final NetworkInterface networkInterface = enumeration.nextElement();\n                final Enumeration<InetAddress> en = networkInterface.getInetAddresses();\n                while (en.hasMoreElements()) {\n                    final InetAddress address = en.nextElement();\n                    if (!address.isLoopbackAddress()) {\n                        if (address instanceof Inet6Address) {\n                            ipv6Result.add(normalizeHostAddress(address));\n                        } else {\n                            ipv4Result.add(normalizeHostAddress(address));\n                        }\n                    }\n                }\n            }\n\n            // prefer ipv4\n            if (!ipv4Result.isEmpty()) {\n                for (String ip : ipv4Result) {\n                    if (ip.startsWith(\"127.0\") || ip.startsWith(\"192.168\")) {\n                        continue;\n                    }\n\n                    return ip;\n                }\n\n                return ipv4Result.get(ipv4Result.size() - 1);\n            } else if (!ipv6Result.isEmpty()) {\n                return ipv6Result.get(0);\n            }\n            //If failed to find,fall back to localhost\n            final InetAddress localHost = InetAddress.getLocalHost();\n            return normalizeHostAddress(localHost);\n        } catch (Exception e) {\n            LogUtil.error(log,\"failed to get local addr\",e);\n        }\n        return null;\n    }\n\n    private static String normalizeHostAddress(final InetAddress localHost) {\n        if (localHost instanceof Inet6Address) {\n            return \"[\" + localHost.getHostAddress() + \"]\";\n        } else {\n            return localHost.getHostAddress();\n        }\n    }\n\n    public static String getRemoteAddr(Channel channel){\n        if (null == channel) {\n            return \"\";\n        }\n        SocketAddress remote = channel.remoteAddress();\n        final String addr = remote != null ? remote.toString() : \"\";\n        if (addr.length() > 0) {\n            int index = addr.lastIndexOf(\"/\");\n            if (index >= 0) {\n                return addr.substring(index + 1);\n            }\n\n            return addr;\n        }\n        return \"\";\n    }\n\n\n}\n"
  },
  {
    "path": "jmqtt-support/src/main/java/org/jmqtt/support/remoting/RemotingService.java",
    "content": "package org.jmqtt.support.remoting;\n\npublic interface RemotingService {\n\n    /**\n     * remoting start\n     */\n    void start();\n\n    /**\n     * remoting shutdown\n     */\n    void shutdown();\n\n}\n"
  },
  {
    "path": "jmqtt-tcp/README.md",
    "content": "自定义tcp协议网关\n待实现\n"
  },
  {
    "path": "jmqtt-tcp/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" 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    <parent>\n        <artifactId>jmqtt</artifactId>\n        <groupId>org.jmqtt</groupId>\n        <version>3.0.0</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>jmqtt-tcp</artifactId>\n    <name>jmqtt-tcp</name>\n\n</project>\n"
  },
  {
    "path": "jmqtt-tcp/src/main/java/org/jmqtt/App.java",
    "content": "package org.jmqtt;\n\n/**\n * Hello world!\n *\n */\npublic class App \n{\n    public static void main( String[] args )\n    {\n        System.out.println( \"Hello World!\" );\n    }\n}\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" 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    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>2.4.2</version>\n    </parent>\n    <groupId>org.jmqtt</groupId>\n    <artifactId>jmqtt</artifactId>\n    <packaging>pom</packaging>\n    <version>3.0.0</version>\n    <modules>\n        <module>jmqtt-broker</module>\n        <module>jmqtt-example</module>\n        <module>jmqtt-admin</module>\n        <module>jmqtt-acceptance</module>\n        <module>jmqtt-mqtt</module>\n        <module>jmqtt-tcp</module>\n        <module>jmqtt-support</module>\n        <module>jmqtt-bus</module>\n        <module>jmqtt-doc</module>\n    </modules>\n\n    <name>jmqtt</name>\n    <url>https://github.com/Cicizz/jmqtt</url>\n\n    <licenses>\n        <license>\n            <name>The Apache Software License, Version 2.0</name>\n            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n            <distribution>repo</distribution>\n        </license>\n    </licenses>\n\n    <developers>\n        <developer>\n            <name>Cicizz</name>\n        </developer>\n    </developers>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <maven.compiler.source>1.8</maven.compiler.source>\n        <maven.compiler.target>1.8</maven.compiler.target>\n        <netty.version>4.1.54.Final</netty.version>\n        <fastjson.version>1.2.75</fastjson.version>\n        <slf4j.version>1.7.30</slf4j.version>\n        <commons-cli.version>1.4</commons-cli.version>\n        <commons-lang3.version>3.7</commons-lang3.version>\n        <junit.version>5.7.1</junit.version>\n        <hazelcast.version>4.0.3</hazelcast.version>\n        <HServer.version>2.9.46</HServer.version>\n        <mybatis.version>3.5.6</mybatis.version>\n        <druid.version>1.2.4</druid.version>\n        <jdbc.version>8.0.17</jdbc.version>\n        <log4j2.version>2.17.1</log4j2.version>\n        <guava.version>31.0.1-jre</guava.version>\n        <assertj.version>2.6.0</assertj.version>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n\n            <dependency>\n                <groupId>com.google.guava</groupId>\n                <artifactId>guava</artifactId>\n                <version>${guava.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.jmqtt</groupId>\n                <artifactId>jmqtt-broker</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.jmqtt</groupId>\n                <artifactId>jmqtt-bus</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.jmqtt</groupId>\n                <artifactId>jmqtt-mqtt</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.jmqtt</groupId>\n                <artifactId>jmqtt-support</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.jmqtt</groupId>\n                <artifactId>jmqtt-tcp</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.jmqtt</groupId>\n                <artifactId>jmqtt-admin</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.netty</groupId>\n                <artifactId>netty-all</artifactId>\n                <version>${netty.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.mybatis</groupId>\n                <artifactId>mybatis</artifactId>\n                <version>${mybatis.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.alibaba</groupId>\n                <artifactId>druid</artifactId>\n                <version>${druid.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>mysql</groupId>\n                <artifactId>mysql-connector-java</artifactId>\n                <version>${jdbc.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.logging.log4j</groupId>\n                <artifactId>log4j-core</artifactId>\n                <version>${log4j2.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.logging.log4j</groupId>\n                <artifactId>log4j-api</artifactId>\n                <version>${log4j2.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>top.hserver</groupId>\n                <artifactId>HServer</artifactId>\n                <version>${HServer.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <groupId>io.netty</groupId>\n                        <artifactId>netty-all</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <dependencies>\n\n        <dependency>\n            <groupId>org.assertj</groupId>\n            <artifactId>assertj-core</artifactId>\n            <version>${assertj.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter-api</artifactId>\n            <version>${junit.version}</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>fastjson</artifactId>\n            <version>${fastjson.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n            <version>${slf4j.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-slf4j-impl</artifactId>\n            <version>${log4j2.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-core</artifactId>\n            <version>${log4j2.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.logging.log4j</groupId>\n            <artifactId>log4j-api</artifactId>\n            <version>${log4j2.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>commons-cli</groupId>\n            <artifactId>commons-cli</artifactId>\n            <version>${commons-cli.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-lang3</artifactId>\n            <version>${commons-lang3.version}</version>\n        </dependency>\n    </dependencies>\n\n    <profiles>\n        <profile>\n            <id>alibaba</id>\n            <activation>\n                <activeByDefault>true</activeByDefault>\n            </activation>\n            <repositories>\n                <repository>\n                    <id>alimaven</id>\n                    <name>aliyun maven</name>\n                    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>\n                    <layout>default</layout>\n                    <snapshots>\n                        <enabled>false</enabled>\n                    </snapshots>\n                </repository>\n                <repository>\n                    <id>spring-milestones</id>\n                    <name>Spring Milestones</name>\n                    <url>https://repo.spring.io/libs-milestone</url>\n                    <snapshots>\n                        <enabled>false</enabled>\n                    </snapshots>\n                </repository>\n            </repositories>\n        </profile>\n    </profiles>\n</project>\n"
  }
]