[
  {
    "path": ".gitignore",
    "content": "# 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*.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*\n\n\n# Operating System Files  \n  \n*.DS_Store  \nThumbs.db  \n*.sw?  \n.#*  \n*#  \n*~  \n*.sublime-*  \n  \n# Build Artifacts  \n  \n.gradle/  \nbuild/  \ntarget/  \nbin/  \ndependency-reduced-pom.xml  \n  \n# Eclipse Project Files  \n  \n.classpath  \n.project  \n.settings/  \n  \n# IntelliJ IDEA Files  \n  \n*.iml  \n*.ipr  \n*.iws  \n*.idea  \n.idea\nout\n  \nREADME.html  \n/LOGS/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 xzc-coder\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\r\n# netty-mqtt-client\r\n\r\n## 1. 介绍\r\n\r\n### 1.1 基本概况\r\n\r\n该项目是基于Netty实现的MQTT3及MQTT5协议的客户端，创建目的是为了学习和使用MQTT及Netty\r\n\r\n### 1.2 技术栈\r\n\r\nJava + Netty + MQTT\r\n\r\n### 1.3 特色\r\n\r\n1.基于高性能的网络开发框架Netty实现，性能更高\r\n\r\n2.支持多个客户端使用同一个线程组，支持配置线程数量，占用的资源更少\r\n\r\n3.目前支持MQTT 3.1.1以及MQTT 5版本\r\n\r\n4.支持单向及双向SSL认证\r\n\r\n5.支持自定义实现扩展组件\r\n\r\n6.支持组件拦截，可实现插件扩展\r\n\r\n7.代码全中文注释\r\n\r\n8.支持消息持久化（目前支持Redis、内存、文件），仅保存不清理会话且未完成的客户端消息\r\n\r\n9.支持遗嘱消息\r\n\r\n10.支持QoS等级为：0、1、2\r\n\r\n11.支持MQTT 3.1.1版本和MQTT 5版本相互切换，并且相互兼容\r\n\r\n12.支持设置客户端的TCP连接参数\r\n\r\n### 1.4 组件介绍\r\n\r\n#### MqttConfiguration\r\n\r\n​\tMQTT全局配置组件，可支持配置TCP连接参数，代理工厂，拦截器，IO线程数，组件创建器及消息存储器\r\n\r\n#### MqttClientFactory\r\n\r\n​\tMQTT客户端工厂，用于创建客户端，只需要传递连接参数，即可根据全局配置创建对应的MQTT客户端\r\n\r\n#### MqttMsgStore\r\n\r\n​\tMQTT消息存储器，默认是用内存消息存储器，如果需要持久化，可使用Redis或文件消息存储器\r\n\r\n#### MqttClient\r\n\r\n​\tMQTT客户端，面向开发者的接口，包含所有的客户端操作API\r\n\r\n#### MqttConnectParameter\r\n\r\n​\tMQTT连接参数，包含MQTT3及MQTT5参数组合，通过设置不同的参数，可创建不同的客户端\r\n\r\n#### MqttCallback\r\n\r\n​\tMQTT回调器，包含MQTT客户端中的所有回调，如消息发送完成回调、消息发送成功回调、连接相关回调、心跳回调、订阅回调等\r\n\r\n#### MqttRetrier\r\n\r\n​\tMQTT重试器，用于重试QoS1及QoS2中失败或未完成的消息，可通过连接配置修改重试时间及间隔\r\n\r\n#### MqttDelegateHandler\r\n\r\n​\tMQTT消息委托器，即MQTT客户端和Netty之间的桥梁，主要是把MQTT的消息和Netty之间的消息进行转换处理\r\n\r\n#### MqttConnector\r\n\r\n​\tMQTT连接器，用于连接MQTT Broker，只负责连接工作\r\n\r\n#### MqttChannelHandler\r\n\r\n​\tMQTT客户端在Netty中的出入栈的处理器，同时负责开启心跳的定时任务\r\n\r\n#### MqttMsgIdCache\r\n\r\n​\tMQTT消息ID缓存器，用于生成MQTT协议层消息的ID\r\n\r\n#### ObjectCreator\r\n\r\n​\t对象创建器，用于创建MqttClient、MqttConnector、MqttDelegateHandler三大组件，可自定义实现三大组件的创建及替换\r\n\r\n#### ProxyFactory\r\n\r\n​\t代理工厂，主要是用于拦截器，支持多种实现，目前支持JDK动态代理以及Cglib动态代理，默认使用JDK动态代理\r\n\r\n#### Interceptor\r\n\r\n​\t拦截器，仅支持拦截MqttClient、MqttConnector、MqttDelegateHandler三大组件，通过注解的方式使用，支持多层级拦截\r\n\r\n## 2.使用\r\n\r\n### 2.1 依赖\r\n\r\n```\r\n<dependency>\r\n    <groupId>io.github.xzc-coder</groupId>\r\n    <artifactId>netty-mqtt-client</artifactId>\r\n    <version>1.1.0</version>\r\n</dependency>\r\n```\r\n\r\n### 2.2 初始化\r\n\r\n```\r\n//创建MQTT全局配置器（也可以不创建）\r\nMqttConfiguration mqttConfiguration = new MqttConfiguration(2);\r\n//创建MQTT客户端工厂\r\nMqttClientFactory mqttClientFactory = new DefaultMqttClientFactory(mqttConfiguration);\r\n//使用内存消息存储器（默认）\r\nMqttMsgStore mqttMsgStore = new MemoryMqttMsgStore();\r\nmqttClientFactory.setMqttMsgStore(mqttMsgStore);\r\n//创建连接参数，设置客户端ID\r\nMqttConnectParameter mqttConnectParameter = new MqttConnectParameter(\"netty-mqtt-client-test\");\r\n//创建一个客户端\r\nMqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\r\n```\r\n\r\n### 2.3 连接\r\n\r\n#### 连接参数设置\r\n\r\n##### MQTT 3\r\n\r\n```\r\n//创建连接参数，设置客户端ID\r\nMqttConnectParameter mqttConnectParameter = new MqttConnectParameter(\"xzc_test\");\r\n//设置客户端版本（默认为3.1.1）\r\nmqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\r\n//是否自动重连\r\nmqttConnectParameter.setAutoReconnect(true);\r\n//Host\r\nmqttConnectParameter.setHost(\"broker.emqx.io\");\r\n//端口\r\nmqttConnectParameter.setPort(1883);\r\n//是否使用SSL/TLS\r\nmqttConnectParameter.setSsl(false);\r\n//遗嘱消息\r\nMqttWillMsg mqttWillMsg = new MqttWillMsg(\"test\",new byte[]{},MqttQoS.EXACTLY_ONCE);\r\nmqttConnectParameter.setWillMsg(mqttWillMsg);\r\n//是否清除会话\r\nmqttConnectParameter.setCleanSession(true);\r\n//心跳间隔\r\nmqttConnectParameter.setKeepAliveTimeSeconds(60);\r\n//连接超时时间\r\nmqttConnectParameter.setConnectTimeoutSeconds(30);\r\n//创建一个客户端\r\nMqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\r\n//添加回调器\r\nmqttClient.addMqttCallback(new DefaultMqttCallback());\r\n```\r\n\r\n##### MQTT 5\r\n\r\n```\r\n//创建连接参数，设置客户端ID\r\nMqttConnectParameter mqttConnectParameter = new MqttConnectParameter(\"xzc_test\");\r\n//设置客户端版本\r\nmqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\r\n//是否自动重连\r\nmqttConnectParameter.setAutoReconnect(true);\r\n//Host\r\nmqttConnectParameter.setHost(\"broker.emqx.io\");\r\n//端口\r\nmqttConnectParameter.setPort(1883);\r\n//是否使用SSL/TLS\r\nmqttConnectParameter.setSsl(false);\r\n//遗嘱消息\r\nMqttWillMsg mqttWillMsg = new MqttWillMsg(\"test\",new byte[]{},MqttQoS.EXACTLY_ONCE);\r\n//MQTT 5的遗嘱属性\r\nmqttWillMsg.setResponseTopic(\"test-response\");\r\nmqttWillMsg.setContentType(\"application/text\");\r\nmqttWillMsg.addMqttUserProperty(\"name\",\"test\");\r\nmqttConnectParameter.setWillMsg(mqttWillMsg);\r\n//是否清除会话\r\nmqttConnectParameter.setCleanSession(true);\r\n//心跳间隔\r\nmqttConnectParameter.setKeepAliveTimeSeconds(60);\r\n//连接超时时间\r\nmqttConnectParameter.setConnectTimeoutSeconds(30);\r\n//MQTT 5的连接参数\r\nmqttConnectParameter.setMaximumPacketSize(100);\r\nmqttConnectParameter.setSessionExpiryIntervalSeconds(100);\r\nmqttConnectParameter.addMqttUserProperty(\"name\",\"test\");\r\n//创建一个客户端\r\nMqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\r\n//添加回调器\r\nmqttClient.addMqttCallback(new DefaultMqttCallback());\r\n```\r\n\r\n#### 连接API\r\n\r\n```\r\n/**\r\n * 进行连接，会阻塞至超时或者连接成功\r\n */\r\nvoid connect();\r\n\r\n/**\r\n * 进行连接，不会阻塞\r\n *\r\n * @return MqttFutureWrapper\r\n */\r\nMqttFutureWrapper connectFuture();\r\n```\r\n\r\n#### 示例\r\n\r\n```\r\n//阻塞连接\r\nmqttClient.connect();\r\n```\r\n\r\n```\r\n//非阻塞连接\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.connectFuture();\r\n//添加监听器\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if (mqttFuture.isSuccess()) {\r\n        System.out.println(\"mqtt client connect success\");\r\n    } else {\r\n        System.out.println(\"mqtt client connect failure\");\r\n    }\r\n});\r\n```\r\n\r\n### 2.4 断开连接\r\n\r\n#### 断开连接API\r\n\r\n```\r\n/**\r\n * 断开连接，会阻塞至TCP断开\r\n */\r\nvoid disconnect();\r\n\r\n/**\r\n * 断开连接\r\n *\r\n * @return Future\r\n */\r\nMqttFutureWrapper disconnectFuture();\r\n\r\n/**\r\n * 断开连接（MQTT 5）\r\n *\r\n * @param mqttDisconnectMsg 断开消息\r\n * @return Future\r\n */\r\nMqttFutureWrapper disconnectFuture(MqttDisconnectMsg mqttDisconnectMsg);\r\n\r\n/**\r\n * 断开连接（MQTT 5）\r\n *\r\n * @param mqttDisconnectMsg 断开消息\r\n */\r\nvoid disconnect(MqttDisconnectMsg mqttDisconnectMsg);\r\n```\r\n\r\n#### 示例\r\n\r\n##### MQTT 3\r\n\r\n```\r\n//阻塞断开连接\r\nmqttClient.disconnect();\r\n```\r\n\r\n```\r\n//非阻塞断开连接\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.disconnectFuture();\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if (mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client disconnect done\");\r\n    }\r\n});\r\n```\r\n\r\n##### MQTT 5\r\n\r\n```\r\n//设置MQTT 5的断开连接参数\r\nMqttDisconnectMsg mqttDisconnectMsg = new MqttDisconnectMsg();\r\nmqttDisconnectMsg.setReasonCode((byte) 100);\r\nmqttDisconnectMsg.setReasonString(\"test disconnect\");\r\nmqttDisconnectMsg.setSessionExpiryIntervalSeconds(100);\r\n```\r\n\r\n```\r\n//阻塞断开连接\r\nmqttClient.disconnect(mqttDisconnectMsg);\r\n```\r\n\r\n```\r\n//非阻塞断开连接\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.disconnectFuture(mqttDisconnectMsg);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if (mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client disconnect done\");\r\n    }\r\n});\r\n```\r\n\r\n### 2.5 订阅\r\n\r\n#### 订阅API\r\n\r\n```\r\n /**\r\n  * 发送一个订阅消息，会阻塞至发送完成\r\n  *\r\n  * @param topic 订阅的主题\r\n  * @param qos   订阅的QoS\r\n  */\r\n void subscribe(String topic, MqttQoS qos);\r\n\r\n /**\r\n  * 发送一个订阅消息，会阻塞至发送完成（MQTT 5）\r\n  *\r\n  * @param mqttSubInfo 订阅消息\r\n  */\r\n void subscribe(MqttSubInfo mqttSubInfo);\r\n\r\n /**\r\n  * 发送一个订阅消息，会阻塞至发送完成（MQTT 5）\r\n  *\r\n  * @param mqttSubInfo            订阅消息\r\n  * @param subscriptionIdentifier 订阅标识符\r\n  * @param mqttUserProperties     用户属性\r\n  */\r\n void subscribe(MqttSubInfo mqttSubInfo, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties);\r\n\r\n /**\r\n  * 发送一个订阅消息，会阻塞至发送完成\r\n  *\r\n  * @param mqttSubInfoList 订阅消息集合\r\n  */\r\n void subscribes(List<MqttSubInfo> mqttSubInfoList);\r\n\r\n /**\r\n  * 发送一个订阅消息，会阻塞至发送完成（MQTT 5）\r\n  *\r\n  * @param mqttSubInfoList        订阅消息集合\r\n  * @param subscriptionIdentifier 订阅标识符\r\n  * @param mqttUserProperties     用户属性\r\n  */\r\n void subscribes(List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties);\r\n\r\n /**\r\n  * 发送一个订阅消息，会阻塞至发送完成\r\n  *\r\n  * @param topicList 订阅主题集合\r\n  * @param qos       订阅的QoS\r\n  */\r\n void subscribes(List<String> topicList, MqttQoS qos);\r\n\r\n /**\r\n  * 发送一个订阅消息，不会阻塞\r\n  *\r\n  * @param topicList 订阅主题集合\r\n  * @param qos       订阅的QoS\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper subscribesFuture(List<String> topicList, MqttQoS qos);\r\n\r\n /**\r\n  * 发送一个订阅消息，不会阻塞\r\n  *\r\n  * @param topic 订阅的主题\r\n  * @param qos   订阅的QoS\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper subscribeFuture(String topic, MqttQoS qos);\r\n\r\n /**\r\n  * 发送一个订阅消息，不会阻塞（MQTT 5）\r\n  *\r\n  * @param mqttSubInfo 订阅消息\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper subscribeFuture(MqttSubInfo mqttSubInfo);\r\n\r\n /**\r\n  * 发送一个订阅消息，不会阻塞（MQTT 5）\r\n  *\r\n  * @param mqttSubInfo            订阅消息\r\n  * @param subscriptionIdentifier 订阅标识符\r\n  * @param mqttUserProperties     订阅用户属性\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper subscribeFuture(MqttSubInfo mqttSubInfo, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties);\r\n\r\n /**\r\n  * 发送一个订阅消息，不会阻塞\r\n  *\r\n  * @param mqttSubInfoList        订阅消息集合（MQTT 5）\r\n  * @param subscriptionIdentifier 订阅标识符\r\n  * @param mqttUserProperties     用户属性\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper subscribesFuture(List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties);\r\n\r\n /**\r\n  * 发送一个订阅消息，不会阻塞\r\n  *\r\n  * @param mqttSubInfoList 订阅集合\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper subscribesFuture(List<MqttSubInfo> mqttSubInfoList);\r\n```\r\n\r\n#### 示例\r\n\r\n##### MQTT 3\r\n\r\n单个订阅\r\n\r\n```\r\n//阻塞订阅\r\nmqttClient.subscribe(\"test\",MqttQoS.EXACTLY_ONCE);\r\n```\r\n\r\n```\r\n//非阻塞订阅\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.subscribeFuture(\"test\", MqttQoS.EXACTLY_ONCE);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client subscribe done\");\r\n    }\r\n});\r\n```\r\n\r\n多个订阅\r\n\r\n```\r\n//多个订阅主题\r\nList<String> topicList = Arrays.asList(\"test1\", \"test2\", \"test3\");\r\n```\r\n\r\n```\r\n//阻塞订阅\r\nmqttClient.subscribes(topicList,MqttQoS.EXACTLY_ONCE);\r\n```\r\n\r\n```\r\n//非阻塞订阅\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.subscribesFuture(topicList, MqttQoS.EXACTLY_ONCE);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client subscribe done\");\r\n    }\r\n});\r\n```\r\n\r\n##### MQTT 5\r\n\r\n单个订阅\r\n\r\n```\r\n//MQTT5订阅参数\r\nMqttSubInfo mqttSubInfo = new MqttSubInfo(\"test\",MqttQoS.AT_LEAST_ONCE,true,true, MqttSubscriptionOption.RetainedHandlingPolicy.DONT_SEND_AT_SUBSCRIBE);\r\nMqttProperties.UserProperties userProperties = new MqttProperties.UserProperties();\r\nuserProperties.add(\"name\",\"test\");\r\n```\r\n\r\n```\r\n//阻塞订阅\r\nmqttClient.subscribe(mqttSubInfo,100,userProperties);\r\n```\r\n\r\n```\r\n//非阻塞订阅\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.subscribeFuture(mqttSubInfoList,100,userProperties);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client subscribe done\");\r\n    }\r\n});\r\n```\r\n\r\n多个订阅\r\n\r\n```\r\n//MQTT5订阅参数\r\nMqttSubInfo mqttSubInfo = new MqttSubInfo(\"test\",MqttQoS.AT_LEAST_ONCE,true,true, MqttSubscriptionOption.RetainedHandlingPolicy.DONT_SEND_AT_SUBSCRIBE);\r\nMqttProperties.UserProperties userProperties = new MqttProperties.UserProperties();\r\nuserProperties.add(\"name\",\"test\");\r\n//多个订阅主题\r\nList<MqttSubInfo> mqttSubInfoList = new ArrayList<>();\r\nmqttSubInfoList.add(mqttSubInfo);\r\n```\r\n\r\n```\r\n//阻塞订阅\r\nmqttClient.subscribes(mqttSubInfoList,100,userProperties);\r\n```\r\n\r\n```\r\n//非阻塞订阅\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.subscribesFuture(mqttSubInfoList,100,userProperties);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client subscribe done\");\r\n    }\r\n});\r\n```\r\n\r\n### 2.6 取消订阅\r\n\r\n#### 取消订阅API\r\n\r\n```\r\n /**\r\n  * 取消订阅，会阻塞至消息发送完成（MQTT 5）\r\n  *\r\n  * @param topicList          取消订阅的主题集合\r\n  * @param mqttUserProperties 用户属性\r\n  */\r\n void unsubscribes(List<String> topicList, MqttProperties.UserProperties mqttUserProperties);\r\n\r\n /**\r\n  * 取消订阅，会阻塞至消息发送完成\r\n  *\r\n  * @param topicList 取消订阅的主题集合\r\n  */\r\n void unsubscribes(List<String> topicList);\r\n\r\n /**\r\n  * 取消订阅，会阻塞至消息发送完成（MQTT 5）\r\n  *\r\n  * @param topic              取消订阅的主题\r\n  * @param mqttUserProperties 用户属性\r\n  */\r\n void unsubscribe(String topic, MqttProperties.UserProperties mqttUserProperties);\r\n\r\n /**\r\n  * 取消订阅，会阻塞至消息发送完成\r\n  *\r\n  * @param topic 取消订阅的主题\r\n  */\r\n void unsubscribe(String topic);\r\n\r\n /**\r\n  * 取消订阅，不会阻塞（MQTT 5）\r\n  *\r\n  * @param topic              取消订阅的主题\r\n  * @param mqttUserProperties 用户属性\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper unsubscribeFuture(String topic, MqttProperties.UserProperties mqttUserProperties);\r\n\r\n /**\r\n  * 取消订阅，不会阻塞\r\n  *\r\n  * @param topic 取消订阅的主题\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper unsubscribeFuture(String topic);\r\n\r\n /**\r\n  * 取消订阅，不会阻塞\r\n  *\r\n  * @param topicList 取消订阅的主题集合\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper unsubscribesFuture(List<String> topicList);\r\n\r\n /**\r\n  * 取消订阅，不会阻塞（MQTT 5）\r\n  *\r\n  * @param topicList          取消订阅的主题集合\r\n  * @param mqttUserProperties 用户属性\r\n  * @return MqttFutureWrapper\r\n  */\r\n MqttFutureWrapper unsubscribesFuture(List<String> topicList, MqttProperties.UserProperties mqttUserProperties);\r\n\r\n```\r\n\r\n#### 示例\r\n\r\n##### MQTT 3\r\n\r\n单个取消订阅\r\n\r\n```\r\n//阻塞取消订阅\r\nmqttClient.unsubscribe(\"test\");\r\n```\r\n\r\n```\r\n//非阻塞取消订阅\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.unsubscribeFuture(\"test\");\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client unsubscribe done\");\r\n    }\r\n});\r\n```\r\n\r\n多个取消订阅\r\n\r\n```\r\n//多个取消订阅的主题\r\nList<String> topicList = Arrays.asList(\"test1\", \"test2\", \"test3\");\r\n```\r\n\r\n```\r\n//阻塞取消订阅\r\nmqttClient.unsubscribes(topicList);\r\n```\r\n\r\n```\r\n//非阻塞取消订阅\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.unsubscribesFuture(topicList);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client unsubscribe done\");\r\n    }\r\n});\r\n```\r\n\r\n##### MQTT 5\r\n\r\n单个取消订阅\r\n\r\n```\r\n//MQTT 5取消订阅参数\r\nMqttProperties.UserProperties userProperties = new MqttProperties.UserProperties();\r\nuserProperties.add(\"name\",\"test\");\r\n```\r\n\r\n```\r\n//阻塞取消订阅\r\nmqttClient.unsubscribe(\"test\",userProperties);\r\n```\r\n\r\n```\r\n//非阻塞取消订阅\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.unsubscribeFuture(\"test\",userProperties);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client unsubscribe done\");\r\n    }\r\n});\r\n```\r\n\r\n多个取消订阅\r\n\r\n```\r\n//MQTT 5取消订阅参数\r\nMqttProperties.UserProperties userProperties = new MqttProperties.UserProperties();\r\nuserProperties.add(\"name\",\"test\");\r\n//多个取消订阅主题\r\nList<String> topicList = Arrays.asList(\"test1\", \"test2\", \"test3\");\r\nmqttClient.unsubscribes(topicList);\r\n```\r\n\r\n```\r\n//阻塞取消订阅\r\nmqttClient.unsubscribes(topicList,userProperties);\r\n```\r\n\r\n```\r\n//非阻塞取消订阅\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.unsubscribesFuture(topicList,userProperties);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client unsubscribe done\");\r\n    }\r\n});\r\n```\r\n\r\n### 2.7 发布消息\r\n\r\n#### 发布消息API\r\n\r\n```\r\n/**\r\n * 发送一个消息，不会阻塞（MQTT 5）\r\n *\r\n * @param mqttMsgInfo mqtt消息\r\n * @return MqttFutureWrapper\r\n */\r\nMqttFutureWrapper publishFuture(MqttMsgInfo mqttMsgInfo);\r\n\r\n/**\r\n * 发送一个消息，不会阻塞\r\n *\r\n * @param payload 载荷\r\n * @param topic   主题\r\n * @param qos     服务质量\r\n * @param retain  是否保留消息\r\n * @return MqttFutureWrapper\r\n */\r\nMqttFutureWrapper publishFuture(byte[] payload, String topic, MqttQoS qos, boolean retain);\r\n\r\n/**\r\n * 发送一个消息，不会阻塞，retain 为 false\r\n *\r\n * @param payload 载荷\r\n * @param topic   主题\r\n * @param qos     服务质量\r\n * @return MqttFutureWrapper\r\n */\r\nMqttFutureWrapper publishFuture(byte[] payload, String topic, MqttQoS qos);\r\n\r\n/**\r\n * 发送一个消息，不会阻塞，retain 为 false，QoS 为 0\r\n *\r\n * @param payload 载荷\r\n * @param topic   主题\r\n * @return MqttFutureWrapper\r\n */\r\nMqttFutureWrapper publishFuture(byte[] payload, String topic);\r\n\r\n/**\r\n * 发送一个消息，会阻塞至发送完成（MQTT 5）\r\n *\r\n * @param mqttMsgInfo mqtt消息\r\n */\r\nvoid publish(MqttMsgInfo mqttMsgInfo);\r\n\r\n/**\r\n * 发送一个消息，会阻塞至发送完成\r\n *\r\n * @param payload 载荷\r\n * @param topic   主题\r\n * @param qos     服务质量\r\n * @param retain  是否保留消息\r\n */\r\nvoid publish(byte[] payload, String topic, MqttQoS qos, boolean retain);\r\n\r\n/**\r\n * 发送一个消息，会阻塞至发送完成，retain 为 false\r\n *\r\n * @param payload 载荷\r\n * @param topic   主题\r\n * @param qos     服务质量\r\n */\r\nvoid publish(byte[] payload, String topic, MqttQoS qos);\r\n\r\n/**\r\n * 发送一个消息，会阻塞至发送完成,retain 为 false，qos 为 0\r\n *\r\n * @param payload 载荷\r\n * @param topic   主题\r\n */\r\nvoid publish(byte[] payload, String topic);\r\n```\r\n\r\n#### 示例\r\n\r\n##### MQTT 3\r\n\r\n```\r\n//阻塞发送消息\r\nmqttClient.publish(new byte[]{1,2,3},\"test\",MqttQoS.EXACTLY_ONCE,true);\r\n```\r\n\r\n```\r\n//非阻塞发送消息\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.publishFuture(new byte[]{1, 2, 3}, \"test\", MqttQoS.EXACTLY_ONCE, true);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client publish done\");\r\n    }\r\n});\r\n```\r\n\r\n##### MQTT 5\r\n\r\n```\r\nMqttMsgInfo mqttMsgInfo = new MqttMsgInfo(\"test\",new byte[]{1, 2, 3},MqttQoS.AT_LEAST_ONCE,true);\r\n//MQTT 5发布消息参数\r\nmqttMsgInfo.addMqttUserProperty(\"name\",\"test\");\r\nmqttMsgInfo.setResponseTopic(\"test-response\");\r\nmqttMsgInfo.setContentType(\"application/text\");\r\nmqttMsgInfo.setTopicAlias(10);\r\n```\r\n\r\n```\r\n//阻塞发布消息\r\nmqttClient.publish(mqttMsgInfo);\r\n```\r\n\r\n```\r\n//非阻塞发送消息\r\nMqttFutureWrapper mqttFutureWrapper = mqttClient.publishFuture(mqttMsgInfo);\r\n//添加监听\r\nmqttFutureWrapper.addListener(mqttFuture -> {\r\n    if(mqttFuture.isDone()) {\r\n        System.out.println(\"mqtt client publish done\");\r\n    }\r\n});\r\n```\r\n\r\n### 2.8 回调器\r\n\r\n#### API\r\n\r\n```\r\n/**\r\n * 订阅完成回调\r\n *\r\n * @param mqttSubscribeCallbackResult 订阅结果\r\n */\r\nvoid subscribeCallback(MqttSubscribeCallbackResult mqttSubscribeCallbackResult);\r\n\r\n/**\r\n * 取消订阅完成回调\r\n *\r\n * @param mqttUnSubscribeCallbackResult 取消订阅结果\r\n */\r\nvoid unsubscribeCallback(MqttUnSubscribeCallbackResult mqttUnSubscribeCallbackResult);\r\n\r\n/**\r\n * 当发送的消息，完成时回调\r\n *\r\n * @param mqttSendCallbackResult 发送消息结果\r\n */\r\nvoid messageSendCallback(MqttSendCallbackResult mqttSendCallbackResult);\r\n\r\n/**\r\n * 接收消息完成时回调\r\n *\r\n * @param receiveCallbackResult 接收消息结果\r\n */\r\nvoid messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult);\r\n\r\n/**\r\n * TCP的连接成功时回调\r\n *\r\n * @param mqttConnectCallbackResult TCP的连接成功结果\r\n */\r\nvoid channelConnectCallback(MqttConnectCallbackResult mqttConnectCallbackResult);\r\n\r\n/**\r\n * MQTT连接完成时回调\r\n *\r\n * @param mqttConnectCallbackResult 连接完成结果\r\n */\r\nvoid connectCompleteCallback(MqttConnectCallbackResult mqttConnectCallbackResult);\r\n\r\n/**\r\n * 连接丢失时回调\r\n *\r\n * @param mqttConnectLostCallbackResult 连接丢失结果\r\n */\r\nvoid connectLostCallback(MqttConnectLostCallbackResult mqttConnectLostCallbackResult);\r\n\r\n/**\r\n * 收到心跳响应时回调\r\n *\r\n * @param mqttHeartbeatCallbackResult 心跳响应结果\r\n */\r\nvoid heartbeatCallback(MqttHeartbeatCallbackResult mqttHeartbeatCallbackResult);\r\n\r\n/**\r\n * Netty的Channel发生异常时回调\r\n *\r\n * @param mqttConnectParameter               连接时的参数\r\n * @param mqttChannelExceptionCallbackResult Channel异常结果\r\n */\r\nvoid channelExceptionCaught(MqttConnectParameter mqttConnectParameter, MqttChannelExceptionCallbackResult mqttChannelExceptionCallbackResult);\r\n```\r\n\r\n#### 示例\r\n\r\n```\r\nmqttClient.addMqttCallback(new MqttCallback() {\r\n    @Override\r\n    public void connectCompleteCallback(MqttConnectCallbackResult mqttConnectCallbackResult) {\r\n        if(mqttConnectCallbackResult.getCause() != null) {\r\n            //连接成功时，订阅主题\r\n            mqttClient.subscribe(\"test\",MqttQoS.EXACTLY_ONCE);\r\n        }\r\n    }\r\n});\r\n```\r\n\r\n### 2.9 拦截器\r\n\r\n#### 步骤\r\n\r\n支持拦截的接口：MqttClient、MqttConnector、MqttDelegateHandler\r\n\r\n使用方式：\r\n\r\n​\t1.实现拦截器接口Interceptor\r\n\r\n​\t2.类上添加注解@Intercepts，并在type值中添加支持拦截的接口，直接单个和多个\r\n\r\n​\t3.在intercept方法中进行拦截\r\n\r\n​\t4.调用Invocation的proceed()执行目标方法\r\n\r\n​\t5.添加拦截器值MQTT客户端工厂或全局配置器中\r\n\r\n#### 示例\r\n\r\n```\r\n@Intercepts(type = {MqttClient.class, MqttConnector.class})\r\npublic class LogInterceptor implements Interceptor {\r\n    @Override\r\n    public Object intercept(Invocation invocation) throws Throwable {\r\n        Object target = invocation.getTarget();\r\n        Object[] args = invocation.getArgs();\r\n        Method method = invocation.getMethod();\r\n        //执行目标方法\r\n        Object returnObj = invocation.proceed();\r\n        LogUtils.info(LogInterceptor.class, \"拦截目标：\" + target.getClass().getSimpleName() + \"，拦截方法：\" + method.getName() + \"，拦截参数：\" + Arrays.toString(args) + \"，拦截返回值：\" + returnObj);\r\n        return returnObj;\r\n    }\r\n}\r\n```\r\n\r\n```\r\n//通过MQTT客户端工厂添加拦截器\r\nmqttClientFactory.addInterceptor(new LogInterceptor());\r\n```\r\n\r\n或者\r\n\r\n```\r\n//通过全局配置器添加拦截器\r\nmqttConfiguration.addInterceptor(new LogInterceptor());\r\n```\r\n\r\n### 2.10 消息存储器\r\n\r\n目前支持三种消息存储方式\r\n\r\n#### 内存消息存储器（默认）\r\n\r\n```\r\nmqttClientFactory.setMqttMsgStore(new MemoryMqttMsgStore());\r\n```\r\n\r\n使用该方式，未完成的QoS1、QoS2的消息在JVM重启后会消失\r\n\r\n#### Redis消息存储器\r\n\r\n导入Redis的maven依赖\r\n\r\n```\r\n<dependency>\r\n    <groupId>redis.clients</groupId>\r\n    <artifactId>jedis</artifactId>\r\n    <version>5.1.0</version>\r\n</dependency>\r\n```\r\n\r\n使用Redis持久化消息存储器\r\n\r\n```\r\nJedisPool jedisPool = new JedisPool();\r\nRedisMqttMsgStore redisMqttMsgStore = new RedisMqttMsgStore(jedisPool);\r\nmqttClientFactory.setMqttMsgStore(redisMqttMsgStore);\r\n```\r\n\r\n使用该方式，未完成的QoS1、QoS2的消息会存储（仅限classSession为false生效）\r\n\r\n#### 文件消息存储器\r\n\r\n```\r\nFile mqttMsgFile = new File(\"E:/test.properties\");\r\nif(!mqttMsgFile.exists()) {\r\n    mqttMsgFile.createNewFile();\r\n}\r\nFileMqttMsgStore fileMqttMsgStore = new FileMqttMsgStore(mqttMsgFile);\r\nmqttClientFactory.setMqttMsgStore(fileMqttMsgStore);\r\n```\r\n\r\n使用该方式，必须传递一个properties的文件；该方式下未完成的QoS1、QoS2的消息会存储（仅限classSession为false生效）\r\n\r\n### 2.11 代理工厂\r\n\r\n#### 使用\r\n\r\n目前已有两种代理工厂的实现，包括：JDK的动态代理（默认）、Cglib的动态代理\r\n\r\n如果需要切换为cglib的动态代理，需要先导入cglib的maven依赖\r\n\r\n```\r\n<dependency>\r\n    <groupId>cglib</groupId>\r\n    <artifactId>cglib</artifactId>\r\n    <version>3.3.0</version>\r\n</dependency>\r\n```\r\n\r\n```\r\nmqttClientFactory.setProxyFactory(new CglibProxyFactory());\r\n```\r\n\r\n#### 扩展\r\n\r\n如果需要自行实现代理工厂，只需实现 ProxyFactory 接口即可\r\n\r\n### 2.12 消息别名\r\n\r\n当短时间内需要给同一个主题发送大量消息时，可以使用消息别名的方式（MQTT 5）\r\n\r\n```\r\nMqttMsgInfo mqttMsgInfo = new MqttMsgInfo(\"test\",new byte[]{1,2,3},MqttQoS.EXACTLY_ONCE);\r\n//消息别名\r\nmqttMsgInfo.setTopicAlias(101);\r\nMqttMsgInfo mqttMsgInfo1 = new MqttMsgInfo(\"test\",new byte[]{1,2,3},MqttQoS.EXACTLY_ONCE);\r\n//消息别名\r\nmqttMsgInfo.setTopicAlias(101);\r\nmqttClient.publish(mqttMsgInfo);\r\nmqttClient.publish(mqttMsgInfo1);\r\n//...更多同一主题的消息\r\n```\r\n\r\n只需要为同一消息主题设置相同的别名，再发送消息时，会无感的将主题名替换为 null ，从而节省主题流量。\r\n\r\n### 2.13 认证增强（MQTT 5）\r\n\r\n在连接参数设置时，添加连接时的认证内容，MQTT客户端会在收到 auth 包时，调用认证器的方法\r\n\r\n```\r\nMqttConnectParameter mqttConnectParameter = new MqttConnectParameter(\"test\");\r\n//添加认证方法和认证数据\r\nmqttConnectParameter.setAuthenticationMethod(\"test\");\r\nmqttConnectParameter.setAuthenticationData(new byte[] {1,1,1});\r\n//添加认证器\r\nMqttAuthenticator mqttAuthenticator = (s, bytes) -> {\r\n    //对bytes 数组处理....\r\n    //返回认证指示\r\n    MqttAuthInstruct mqttAuthInstruct = new MqttAuthInstruct(MqttAuthInstruct.Instruct.AUTH_CONTINUE);\r\n    mqttAuthInstruct.setAuthenticationData(new byte[]{1,2,3});\r\n    return mqttAuthInstruct;\r\n};\r\nmqttConnectParameter.setMqttAuthenticator(mqttAuthenticator);\r\n```\r\n\r\n开发者可根据接收到的认证数据进行下一步操作的判断，并且指示下一步的认证操作，当MQTT Broker认证完成后，将会执行连接完成回调\r\n\r\n\r\n\r\n### 2.14 配置参数\r\n\r\n#### 全局配置参数（MqttConfiguration）\r\n\r\n| 字段/方法                                  | 类型                  | 默认值                           | 说明                                                         |\r\n| ------------------------------------------ | --------------------- | -------------------------------- | ------------------------------------------------------------ |\r\n| proxyFactory                               | ProxyFactory          | JdkProxyFactory                  | 代理工厂，用于创建三大组件（MqttClient、MqttConnector、MqttDelegateHandler）的代理对象 |\r\n| maxThreadNumber                            | int                   | 1                                | 处理IO的最大线程数即NioEventLoopGroup中的线程数量，多个客户端时可以设置为多个 |\r\n| mqttClientObjectCreator                    | ObjectCreator         | MqttClientObjectCreator          | MQTT客户端的对象创建器                                       |\r\n| mqttConnectorObjectCreator                 | ObjectCreator         | MqttConnectorObjectCreator       | MQTT连接器的对象创建器                                       |\r\n| mqttDelegateHandlerObjectCreator           | ObjectCreator         | MqttDelegateHandlerObjectCreator | MQTT委托处理器的对象创建器                                   |\r\n| mqttMsgStore                               | MqttMsgStore          | MemoryMqttMsgStore               | MQTT消息存储器                                               |\r\n| option(ChannelOption option, Object value) | ChannelOption、Object | 无                               | Netty中的TCP连接参数                                         |\r\n| addInterceptor(Interceptor interceptor)    | Interceptor           | 无                               | 拦截器，用于拦截MqttClient、MqttConnector、MqttDelegateHandler |\r\n\r\n注意：MqttClientFactory中的配置，会放入到MqttConfiguration中。\r\n\r\n#### MQTT连接参数（MqttConnectParameter）\r\n\r\n| 字段/方法                                     | 类型              | 默认值     | 说明                                                         |\r\n| --------------------------------------------- | ----------------- | ---------- | ------------------------------------------------------------ |\r\n| clientId                                      | String            | 无         | 客户端ID，不能为null，也不能重复                             |\r\n| mqttVersion                                   | MqttVersion       | MQTT_3_1_1 | 客户端版本号                                                 |\r\n| host                                          | String            | localhost  | MQTTBroker的host                                             |\r\n| port                                          | int               | 1883       | MQTTBroker的端口                                             |\r\n| username                                      | String            | 无         | MQTT的连接账号                                               |\r\n| password                                      | char[]            | 无         | MQTT的连接密码                                               |\r\n| willMsg                                       | MqttWillMsg       | 无         | MQTT的遗嘱消息                                               |\r\n| retryIntervalMillis                           | long              | 1000毫秒   | 消息重试器的重试间隔，单位毫秒                               |\r\n| retryIntervalIncreaseMillis                   | long              | 1000毫秒   | 每次消息重试失败时，增大其重试间隔值，单位毫秒               |\r\n| retryIntervalMaxMillis                        | long              | 15000毫秒  | 重试间隔的最大值，单位毫秒                                   |\r\n| keepAliveTimeSeconds                          | int               | 30秒       | MQTT心跳间隔，单位秒                                         |\r\n| keepAliveTimeCoefficient                      | BigDecimal        | 0.75       | MQTT心跳间隔系数，由于某些Broker读超时时间为心跳间隔时间，中间发报文需要时间，可能在网络不好的情况下会导致超时，所以增加该系数，即发送心跳的时间为 心跳间隔 * 系数 ，默认0.75 |\r\n| connectTimeoutSeconds                         | long              | 30秒       | MQTT连接超时时间，单位秒                                     |\r\n| autoReconnect                                 | boolean           | false      | 是否自动重连                                                 |\r\n| cleanSession                                  | boolean           | true       | 是否清理会话                                                 |\r\n| ssl                                           | boolean           | false      | 是否开启SSL/TLS                                              |\r\n| rootCertificateFile                           | File              | 无         | 根证书文件                                                   |\r\n| clientPrivateKeyFile                          | File              | 无         | 客户端私钥文件，双向SSL时需要                                |\r\n| clientCertificateFile                         | File              | 无         | 客户端证书文件，双向SSL时需要                                |\r\n| sessionExpiryIntervalSeconds                  | int               | 无         | 会话过期时间，单位秒，MQTT 5                                 |\r\n| authenticationMethod                          | String            | 无         | 认证方法，MQTT 5                                             |\r\n| authenticationData                            | byte[]            | 无         | 认证数据，MQTT 5                                             |\r\n| requestProblemInformation                     | int               | 1          | 请求问题信息标识符，MQTT 5                                   |\r\n| requestResponseInformation                    | int               | 0          | 请求响应标识，MQTT 5                                         |\r\n| responseInformation                           | String            | 无         | 响应信息，MQTT 5                                             |\r\n| receiveMaximum                                | int               | 无         | 接收最大数量，MQTT 5                                         |\r\n| topicAliasMaximum                             | int               | 无         | 主题别名最大长度，MQTT 5                                     |\r\n| maximumPacketSize                             | int               | 无         | 最大报文长度，MQTT 5                                         |\r\n| addMqttUserProperty(String key, String value) | String、String    | 无         | 添加一个用户属性，MQTT 5                                     |\r\n| mqttAuthenticator                             | MqttAuthenticator | 无         | 认证器，MQTT 5                                               |\r\n\r\n注意：在SSL相关的参数中，rootCertificateFile不是必须的，前提是 Broker 的证书是权威CA认证的话就不需要，如果是自签名的证书就需要该文件；并且在双向认证中，如果你使用的是jks或pkcs后缀的文件（私钥和证书的结合体），那么请将其转换为证书和私钥两个文件。\r\n\r\n### 2.15 测试用例\r\n\r\n所有的测试用例均在目录：\r\n\r\n> src/test/java/io/github/netty/mqtt/client\r\n\r\n当需要执行测试用例时，需要修改pom.xml文件中的，编译插件项，如下：\r\n\r\n```\r\n            <plugin>\r\n                <groupId>org.apache.maven.plugins</groupId>\r\n                <artifactId>maven-compiler-plugin</artifactId>\r\n                <version>3.8.1</version>\r\n                <configuration>\r\n                    <source>8</source>\r\n                    <target>8</target>\r\n                    <!-- 此处修改为false -->\r\n                    <skip>false</skip>\r\n                </configuration>\r\n            </plugin>\r\n```\r\n\r\n\r\n\r\n## 3. 其它\r\n\r\n### 3.1 注意事项\r\n\r\n1.需要JDK版本1.8及以上\r\n\r\n2.日志需要导入日志框架，如果没有日志框架，则会在控制台打印日志\r\n\r\n3.以上所有的API，MQTT 3.1.1 和 MQTT 5版本之间是互相兼容的\r\n\r\n### 3.2 issue\r\n\r\n如果产生问题，请提issue\r\n\r\n格式：问题描述+复现代码示例\r\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>io.github.xzc-coder</groupId>\n    <artifactId>netty-mqtt-client</artifactId>\n    <packaging>jar</packaging>\n    <version>1.1.0</version>\n    <description>基于Netty实现的MQTT客户端</description>\n    <url>https://github.com/xzc-coder/netty-mqtt-client</url>\n\n    <developers>\n        <developer>\n            <id>xzc-coder</id>\n            <name>xzc-coder</name>\n            <email>378360221@qq.com</email>\n            <roles>\n                <role>Project Manager</role>\n                <role>Architect</role>\n            </roles>\n        </developer>\n    </developers>\n\n    <licenses>\n        <license>\n            <name>MIT License</name>\n            <url>https://www.opensource.org/licenses/mit-license.php</url>\n            <distribution>repo</distribution>\n        </license>\n    </licenses>\n\n    <scm>\n        <connection>https://github.com/xzc-coder/netty-mqtt-client.git</connection>\n        <developerConnection>scm:git:ssh://git@github.com:xzc-coder/netty-mqtt-client.git</developerConnection>\n        <url>https://github.com/xzc-coder/netty-mqtt-client</url>\n    </scm>\n\n    <build>\n        <plugins>\n            <!--   本地打包编译插件     -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-assembly-plugin</artifactId>\n                <version>2.5.5</version>\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            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <source>8</source>\n                    <target>8</target>\n                    <!--          编译时跳过测试，需要实行测试用例时，自行修改为false          -->\n                    <skip>true</skip>\n                </configuration>\n            </plugin>\n\n            <!--   central发布插件    -->\n            <plugin>\n                <groupId>org.sonatype.central</groupId>\n                <artifactId>central-publishing-maven-plugin</artifactId>\n                <version>0.4.0</version>\n                <extensions>true</extensions>\n                <configuration>\n                    <publishingServerId>xzc-coder</publishingServerId>\n                    <tokenAuth>true</tokenAuth>\n                </configuration>\n            </plugin>\n            <!--   source源码插件 -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-source-plugin</artifactId>\n                <version>2.2.1</version>\n                <executions>\n                    <execution>\n                        <id>attach-sources</id>\n                        <goals>\n                            <goal>jar-no-fork</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <!--   javadoc插件 -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <version>2.9.1</version>\n                <executions>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-gpg-plugin</artifactId>\n                <version>1.5</version>\n                <configuration>\n                    <executable>D:\\gpg4\\GnuPG\\bin\\gpg.exe</executable>\n                    <keyname>xzc-coder</keyname>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>sign-artifacts</id>\n                        <phase>verify</phase>\n                        <goals>\n                            <goal>sign</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <netty-version>4.1.111.Final</netty-version>\n        <slf4j.version>2.0.3</slf4j.version>\n        <jedis.version>5.1.0</jedis.version>\n        <junit.version>4.12</junit.version>\n        <cglib.version>3.3.0</cglib.version>\n        <!--   解决mvn test时控制台乱码问题    -->\n        <argLine>-Dfile.encoding=UTF-8</argLine>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-transport</artifactId>\n            <version>${netty-version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-codec-mqtt</artifactId>\n            <version>${netty-version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-handler</artifactId>\n            <version>${netty-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>redis.clients</groupId>\n            <artifactId>jedis</artifactId>\n            <version>${jedis.version}</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <version>${junit.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>cglib</groupId>\n            <artifactId>cglib</artifactId>\n            <version>${cglib.version}</version>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n</project>"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/AbstractMqttClient.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.callback.MqttCallback;\nimport io.github.netty.mqtt.client.connector.MqttConnector;\nimport io.github.netty.mqtt.client.constant.MqttMsgDirection;\nimport io.github.netty.mqtt.client.exception.MqttStateCheckException;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.github.netty.mqtt.client.store.MqttMsgIdCache;\nimport io.github.netty.mqtt.client.store.MqttMsgStore;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\nimport io.github.netty.mqtt.client.support.util.LogUtils;\nimport io.netty.channel.Channel;\n\nimport java.net.InetSocketAddress;\nimport java.util.*;\nimport java.util.concurrent.CopyOnWriteArraySet;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\n\n/**\n * 抽象的MQTT客户端\n * @author: xzc-coder\n */\npublic abstract class AbstractMqttClient implements MqttClient {\n\n    /**\n     * 客户端ID\n     */\n    protected final String clientId;\n    /**\n     * 客户端工厂\n     */\n    protected final MqttClientFactory mqttClientFactory;\n    /**\n     * MQTT消息委托器\n     */\n    protected final MqttDelegateHandler mqttDelegateHandler;\n    /**\n     * MQTT消息存储器\n     */\n    protected final MqttMsgStore mqttMsgStore;\n    /**\n     * MQTT连接器\n     */\n    protected final MqttConnector mqttConnector;\n    /**\n     * MQTT全局配置\n     */\n    protected final MqttConfiguration mqttConfiguration;\n    /**\n     * MQTT的连接参数\n     */\n    protected final MqttConnectParameter mqttConnectParameter;\n\n    /**\n     * 当前的Netty Channel，如果未连接则为null，只有当当前Channel关闭后，才能进行新的一次连接\n     */\n    protected volatile Channel currentChannel;\n    /**\n     * MQTT回调器集合，读多写少，适合用 CopyOnWriteArraySet\n     */\n    protected final Set<MqttCallback> mqttCallbackSet = new CopyOnWriteArraySet<>();\n    /**\n     * 客户端是否关闭\n     */\n    private final AtomicBoolean isClose = new AtomicBoolean(false);\n\n    public AbstractMqttClient(MqttConfiguration mqttConfiguration, MqttConnectParameter mqttConnectParameter, Object... connectorCreateArgs) {\n        AssertUtils.notNull(mqttConfiguration, \"mqttConfiguration is null\");\n        AssertUtils.notNull(mqttConnectParameter, \"mqttConnectParameter is null\");\n        this.mqttConfiguration = mqttConfiguration;\n        this.mqttConnectParameter = mqttConnectParameter;\n        MqttClientFactory mqttClientFactory = mqttConfiguration.getMqttClientFactory();\n        AssertUtils.notNull(mqttClientFactory, \"mqttClientFactory is null\");\n        this.mqttClientFactory = mqttClientFactory;\n        this.clientId = mqttConnectParameter.getClientId();\n        MqttConnector mqttConnector = createMqttConnector(connectorCreateArgs);\n        AssertUtils.notNull(mqttConnector, \"mqttConnector is null\");\n        this.mqttConnector = mqttConnector;\n        MqttDelegateHandler mqttMsgHandler = mqttConnector.getMqttDelegateHandler();\n        AssertUtils.notNull(mqttMsgHandler, \"mqttMsgHandler is null\");\n        this.mqttDelegateHandler = mqttMsgHandler;\n        this.mqttMsgStore = mqttConfiguration.getMqttMsgStore();\n        if (!mqttConnectParameter.isCleanSession()) {\n            occupyMsgId();\n        }\n    }\n\n    /**\n     * 不清理会话时，占用消息ID（因为旧的还未释放）\n     */\n    private void occupyMsgId() {\n        List<MqttMsg> msgList = this.mqttMsgStore.getMsgList(MqttMsgDirection.SEND, clientId);\n        if (EmptyUtils.isNotEmpty(msgList)) {\n            Set<Integer> msgIdSet = msgList.stream().map(MqttMsg::getMsgId).collect(Collectors.toSet());\n            MqttMsgIdCache.occupyMsgId(clientId, msgIdSet);\n        }\n    }\n\n\n    /**\n     * 创建一个MQTT连接器\n     *\n     * @param connectorCreateArgs 创建参数\n     * @return MQTT连接器\n     */\n    protected abstract MqttConnector createMqttConnector(Object... connectorCreateArgs);\n\n    @Override\n    public InetSocketAddress getLocalAddress() {\n        Channel channel = currentChannel;\n        InetSocketAddress localAddress = null;\n        if (channel != null) {\n            localAddress = (InetSocketAddress) channel.localAddress();\n        }\n        return localAddress;\n    }\n\n    @Override\n    public InetSocketAddress getRemoteAddress() {\n        Channel channel = currentChannel;\n        InetSocketAddress remoteAddress = null;\n        if (channel != null) {\n            remoteAddress = (InetSocketAddress) channel.remoteAddress();\n        }\n        return remoteAddress;\n    }\n\n    @Override\n    public boolean isClose() {\n        return isClose.get();\n    }\n\n    @Override\n    public void close() {\n        // cas设值，保证只执行一次关闭\n        if (isClose.compareAndSet(false, true)) {\n            try {\n                LogUtils.info(AbstractMqttClient.class,\"client:\" + getClientId() + \" is shutting down\");\n                doClose();\n            } finally {\n                //释放客户端ID，以便之后还可以继续创建\n                mqttClientFactory.releaseMqttClientId(clientId);\n            }\n        }\n    }\n\n    /**\n     * 执行关闭操作，留给子类实现\n     */\n    protected void doClose() {\n\n    }\n\n    @Override\n    public String getClientId() {\n        return clientId;\n    }\n\n    @Override\n    public MqttConnectParameter getMqttConnectParameter() {\n        return this.mqttConnectParameter;\n    }\n\n    protected Channel getChannel() {\n        return currentChannel;\n    }\n\n    @Override\n    public void addMqttCallback(MqttCallback mqttCallback) {\n        if (mqttCallback != null) {\n            mqttCallbackSet.add(mqttCallback);\n        }\n    }\n\n    @Override\n    public void addMqttCallbacks(Collection<MqttCallback> mqttCallbacks) {\n        if (mqttCallbacks != null && mqttCallbacks.size() > 0) {\n            mqttCallbackSet.addAll(mqttCallbacks);\n        }\n    }\n\n    protected void closeCheck() {\n        if (isClose()) {\n            throw new MqttStateCheckException(\"client: \" + getClientId() + \" already closed\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/DefaultMqttClient.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.callback.*;\nimport io.github.netty.mqtt.client.connector.MqttConnector;\nimport io.github.netty.mqtt.client.constant.*;\nimport io.github.netty.mqtt.client.exception.MqttStateCheckException;\nimport io.github.netty.mqtt.client.exception.MqttException;\nimport io.github.netty.mqtt.client.msg.*;\nimport io.github.netty.mqtt.client.msg.*;\nimport io.github.netty.mqtt.client.retry.MqttRetrier;\nimport io.github.netty.mqtt.client.store.MqttMsgIdCache;\nimport io.github.netty.mqtt.client.support.future.DefaultMqttFuture;\nimport io.github.netty.mqtt.client.support.future.MqttFuture;\nimport io.github.netty.mqtt.client.support.future.MqttFutureWrapper;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\nimport io.github.netty.mqtt.client.support.util.LogUtils;\nimport io.github.netty.mqtt.client.support.util.MqttUtils;\nimport io.github.netty.mqtt.client.callback.*;\nimport io.github.netty.mqtt.client.constant.*;\nimport io.netty.channel.Channel;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\nimport io.netty.util.concurrent.ScheduledFuture;\n\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.locks.ReadWriteLock;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\n/**\n * 默认的MQTT客户端实现\n * @author: xzc-coder\n */\npublic class DefaultMqttClient extends AbstractMqttClient implements MqttCallback {\n\n    /**\n     * 是否是第一次连接，用来启动自动重连的定时任务\n     */\n    private final AtomicBoolean firstConnect = new AtomicBoolean(true);\n    /**\n     * 是否是第一次连接成功，用来启动 当不清理会话时，历史消息的重试\n     */\n    private final AtomicBoolean firstConnectSuccess = new AtomicBoolean(true);\n    /**\n     * 是否是手动关闭，当调用MqttClient的 disconnect 方法时，则为手动关闭，\n     * 手动关闭后，如果开启了自动重连，则自动重连任务会停止\n     */\n    private volatile boolean manualDisconnect = false;\n\n    /**\n     * 重连的定时任务ScheduledFuture\n     */\n    private volatile ScheduledFuture reconnectScheduledFuture;\n\n    /**\n     * 读写锁，发送、订阅、取消订阅消息时使用读锁，涉及到连接相关的，则使用写锁\n     */\n    private final ReadWriteLock LOCK = new ReentrantReadWriteLock();\n\n    /**\n     * Mqtt重试器\n     */\n    private final MqttRetrier mqttRetrier;\n\n    public DefaultMqttClient(MqttConfiguration configuration, MqttConnectParameter mqttConnectParameter) {\n        super(configuration, mqttConnectParameter);\n        mqttRetrier = new MqttRetrier(mqttConnectParameter,configuration.getEventLoopGroup());\n    }\n\n    @Override\n    protected MqttConnector createMqttConnector(Object... connectorCreateArgs) {\n        return mqttConfiguration.newMqttConnector(mqttConfiguration, mqttConnectParameter, this);\n    }\n\n\n    @Override\n    public MqttFutureWrapper connectFuture() {\n        MqttFuture mqttFuture = doConnect();\n        return new MqttFutureWrapper(mqttFuture);\n    }\n\n    @Override\n    public void connect() {\n        MqttFutureWrapper mqttFutureWrapper = connectFuture();\n        try {\n            mqttFutureWrapper.sync(mqttConnectParameter.getConnectTimeoutSeconds() * 1000);\n        } catch (InterruptedException | TimeoutException e) {\n            throw new MqttException(e, clientId);\n        }\n    }\n\n\n    @Override\n    public void disconnect() {\n        disconnectFuture().syncUninterruptibly();\n    }\n\n\n    @Override\n    public MqttFutureWrapper disconnectFuture() {\n        return disconnectFuture(new MqttDisconnectMsg());\n    }\n\n    @Override\n    public MqttFutureWrapper disconnectFuture(MqttDisconnectMsg mqttDisconnectMsg) {\n        AssertUtils.notNull(mqttDisconnectMsg, \"mqttDisconnectMsg is null\");\n        LOCK.writeLock().lock();\n        try {\n            closeCheck();\n            manualDisconnect = true;\n            if (reconnectScheduledFuture != null) {\n                reconnectScheduledFuture.cancel(true);\n            }\n            Channel channel = getChannel();\n            MqttFuture mqttFuture = new DefaultMqttFuture(clientId, new Object());\n            if (isOnline(channel)) {\n                //在线，发送MQTT断开包，正常断开\n                mqttDelegateHandler.sendDisconnect(channel, mqttFuture, mqttDisconnectMsg);\n            } else {\n                //正在连接中，直接关闭\n                isConnected(channel);\n                channel.close().addListener(closeFuture -> {\n                    if (closeFuture.isSuccess()) {\n                        mqttFuture.setSuccess(null);\n                    } else {\n                        mqttFuture.setFailure(closeFuture.cause());\n                    }\n                });\n            }\n            return new MqttFutureWrapper(mqttFuture);\n        } finally {\n            LOCK.writeLock().unlock();\n        }\n    }\n\n    @Override\n    public void disconnect(MqttDisconnectMsg mqttDisconnectMsg) {\n        disconnectFuture(mqttDisconnectMsg).syncUninterruptibly();\n    }\n\n    @Override\n    public MqttFutureWrapper publishFuture(MqttMsgInfo mqttMsgInfo) {\n        LOCK.readLock().lock();\n        try {\n            Channel channel = currentChannel;\n            //发送发布消息之前进行检查\n            sendMsgCheck(channel, mqttMsgInfo.getQos(),mqttMsgInfo.getTopic());\n            //执行发布消息\n            MqttFuture msgFuture = doPublish(channel, mqttMsgInfo);\n            return new MqttFutureWrapper(msgFuture);\n        } finally {\n            LOCK.readLock().unlock();\n        }\n    }\n\n    @Override\n    public MqttFutureWrapper publishFuture(byte[] payload, String topic, MqttQoS qos, boolean retain) {\n        return publishFuture(new MqttMsgInfo(topic, payload, qos, retain));\n    }\n\n    @Override\n    public MqttFutureWrapper publishFuture(byte[] payload, String topic, MqttQoS qos) {\n        return publishFuture(payload, topic, qos, false);\n    }\n\n    @Override\n    public MqttFutureWrapper publishFuture(byte[] payload, String topic) {\n        return publishFuture(payload, topic, MqttQoS.AT_MOST_ONCE);\n    }\n\n    @Override\n    public void publish(MqttMsgInfo mqttMsgInfo) {\n        publishFuture(mqttMsgInfo).syncUninterruptibly();\n    }\n\n    @Override\n    public void publish(byte[] payload, String topic, MqttQoS qos, boolean retain) {\n        publishFuture(payload, topic, qos, retain).syncUninterruptibly();\n    }\n\n    @Override\n    public void publish(byte[] payload, String topic, MqttQoS qos) {\n        publish(payload, topic, qos, false);\n    }\n\n    @Override\n    public void publish(byte[] payload, String topic) {\n        publish(payload, topic, MqttQoS.AT_MOST_ONCE);\n    }\n\n    @Override\n    public MqttFutureWrapper subscribesFuture(List<MqttSubInfo> mqttSubInfoList) {\n        return this.subscribesFuture(mqttSubInfoList, null, null);\n    }\n\n\n    @Override\n    public MqttFutureWrapper subscribesFuture(List<String> topicList, MqttQoS qos) {\n        AssertUtils.notEmpty(topicList, \"topicList is empty\");\n        AssertUtils.notNull(qos, \"qos is null\");\n        List<MqttSubInfo> mqttSubInfoList = toSubInfoList(topicList, qos);\n        MqttFutureWrapper subscribeFutureWrapper = subscribesFuture(mqttSubInfoList);\n        return subscribeFutureWrapper;\n    }\n\n    @Override\n    public MqttFutureWrapper subscribeFuture(String topic, MqttQoS qos) {\n        return subscribeFuture(new MqttSubInfo(topic, qos), null, null);\n    }\n\n    @Override\n    public MqttFutureWrapper subscribeFuture(MqttSubInfo mqttSubInfo) {\n        return subscribeFuture(mqttSubInfo, null, null);\n    }\n\n    @Override\n    public MqttFutureWrapper subscribeFuture(MqttSubInfo mqttSubInfo, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n        AssertUtils.notNull(mqttSubInfo, \"mqttSubInfo is null\");\n        List<MqttSubInfo> mqttSubInfoList = new ArrayList<>(1);\n        mqttSubInfoList.add(mqttSubInfo);\n        return subscribesFuture(mqttSubInfoList, subscriptionIdentifier, mqttUserProperties);\n    }\n\n    @Override\n    public MqttFutureWrapper subscribesFuture(List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n        AssertUtils.notEmpty(mqttSubInfoList, \"mqttSubInfoList is empty\");\n        LOCK.readLock().lock();\n        try {\n            Channel channel = currentChannel;\n            subscribeCheck(channel, mqttSubInfoList);\n            MqttFuture subscribeFuture = doSubscribeFuture(channel, mqttSubInfoList, subscriptionIdentifier, mqttUserProperties);\n            return new MqttFutureWrapper(subscribeFuture);\n        } finally {\n            LOCK.readLock().unlock();\n        }\n    }\n\n\n    @Override\n    public void subscribes(List<MqttSubInfo> mqttSubInfoList) {\n        subscribesFuture(mqttSubInfoList).syncUninterruptibly();\n    }\n\n    @Override\n    public void subscribes(List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n        subscribesFuture(mqttSubInfoList, subscriptionIdentifier, mqttUserProperties).syncUninterruptibly();\n    }\n\n\n    @Override\n    public void subscribes(List<String> topicList, MqttQoS qos) {\n        subscribesFuture(topicList, qos).syncUninterruptibly();\n    }\n\n\n    @Override\n    public void subscribe(String topic, MqttQoS qos) {\n        subscribeFuture(topic, qos).syncUninterruptibly();\n    }\n\n    @Override\n    public void subscribe(MqttSubInfo mqttSubInfo) {\n        subscribe(mqttSubInfo, null, null);\n    }\n\n    @Override\n    public void subscribe(MqttSubInfo mqttSubInfo, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n        subscribeFuture(mqttSubInfo, subscriptionIdentifier, mqttUserProperties).syncUninterruptibly();\n    }\n\n    @Override\n    public MqttFutureWrapper unsubscribesFuture(List<String> topicList) {\n        return this.unsubscribesFuture(topicList, null);\n    }\n\n    @Override\n    public MqttFutureWrapper unsubscribesFuture(List<String> topicList, MqttProperties.UserProperties mqttUserProperties) {\n        AssertUtils.notEmpty(topicList, \"topicList is empty\");\n        LOCK.readLock().lock();\n        try {\n            Channel channel = currentChannel;\n            unsubscribeCheck(channel,topicList);\n            //进行取消订阅操作\n            MqttFuture unsubscribeFuture = doUnsubscribeFuture(channel, topicList, mqttUserProperties);\n            return new MqttFutureWrapper(unsubscribeFuture);\n        } finally {\n            LOCK.readLock().unlock();\n        }\n    }\n\n    @Override\n    public void unsubscribes(List<String> topicList, MqttProperties.UserProperties mqttUserProperties) {\n        unsubscribesFuture(topicList, mqttUserProperties).syncUninterruptibly();\n    }\n\n    @Override\n    public MqttFutureWrapper unsubscribeFuture(String topic) {\n        return unsubscribeFuture(topic, null);\n    }\n\n\n    @Override\n    public void unsubscribes(List<String> topicList) {\n        unsubscribesFuture(topicList).syncUninterruptibly();\n    }\n\n    @Override\n    public void unsubscribe(String topic, MqttProperties.UserProperties mqttUserProperties) {\n        unsubscribeFuture(topic, mqttUserProperties).syncUninterruptibly();\n    }\n\n    @Override\n    public void unsubscribe(String topic) {\n        unsubscribeFuture(topic).syncUninterruptibly();\n    }\n\n    @Override\n    public MqttFutureWrapper unsubscribeFuture(String topic, MqttProperties.UserProperties mqttUserProperties) {\n        AssertUtils.notEmpty(topic, \"topic is empty\");\n        List<String> topicList = new ArrayList<>(1);\n        topicList.add(topic);\n        MqttFutureWrapper unsubscribeFutureWrapper = unsubscribesFuture(topicList, mqttUserProperties);\n        return unsubscribeFutureWrapper;\n    }\n\n\n    /**\n     * 进行MQTT的连接\n     *\n     * @return Future\n     */\n    private MqttFuture doConnect() {\n        //写锁，保证同一时间只有一个连接\n        LOCK.writeLock().lock();\n        try {\n            Channel channel = currentChannel;\n            //进行连接（客户端在线、客户端关闭、客户端正在连接中都不能继续）检查，因为连接是异步的，需要保证只有一个Channel在线\n            connectCheck(channel);\n            //使用连接器进行异步连接\n            MqttFuture<Channel> connectFuture = mqttConnector.connect();\n            Channel newChannel = (Channel) connectFuture.getParameter();\n            //只有此处为客户端设置新的Channel，别的地方不能设置，保证来源只有一处\n            this.currentChannel = newChannel;\n            //添加一个TCP断开连接监听，当连接断开时，唤醒还在等待中的 Future\n            newChannel.closeFuture().addListener((future) -> {\n                //获取所有连接上的发送消息\n                Map<Integer, Object> incompleteMsgMap = newChannel.attr(MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get();\n                incompleteMsgMap.forEach((msgId, msg) -> {\n                    //进行失败唤醒\n                    MqttFuture notifyMqttFuture = MqttFuture.getFuture(clientId, msgId);\n                    if (notifyMqttFuture != null) {\n                        notifyMqttFuture.setFailure(new MqttException(\"connect has been lost\", clientId));\n                    }\n                });\n            });\n            //添加一个MQTT的连接监听，因为需要开启自动重连和消息重试\n            connectFuture.addListener(mqttFuture -> {\n                //是首次连接且开启了自动重连，不管连接是否成功，都需要开启自动重连定时任务\n                if (firstConnect.compareAndSet(true, false) && mqttConnectParameter.isAutoReconnect()) {\n                    startReconnectTask();\n                }\n                //当MQTT连接成功时（包括认证完成）\n                if (mqttFuture.isSuccess()) {\n                    //是首次连接成功并且不清理会话，开启旧任务重试（不清理会话的情况下，才存在旧任务）\n                    if (firstConnectSuccess.compareAndSet(true, false) && !mqttConnectParameter.isCleanSession()) {\n                        oldMsgListRetry();\n                    }\n                }\n            });\n            return connectFuture;\n        } finally {\n            LOCK.writeLock().unlock();\n        }\n    }\n\n\n    /**\n     * 调用MQTT的消息委托器进行发送\n     *\n     * @param channel     Channel\n     * @param mqttMsgInfo MQTT消息信息\n     * @return Future\n     */\n    private MqttFuture doPublish(Channel channel, MqttMsgInfo mqttMsgInfo) {\n        //创建发布消息\n        MqttMsg mqttMsg = createMsgAndMsgId(channel, true, mqttMsgInfo.getQos(), (msgId) -> new MqttMsg(msgId, mqttMsgInfo.getPayload(), mqttMsgInfo.getTopic(), mqttMsgInfo.getQos(), mqttMsgInfo.isRetain()));\n        //对于qos为0的消息，创建一个Object作为Future的key，qos 1 和 2的 则用消息ID作为Key\n        Object futureKey = (mqttMsg.getMsgId() == MqttConstant.INVALID_MSG_ID ? new Object() : mqttMsg.getMsgId());\n        boolean isHighQos = isHighQos(mqttMsg.getQos());\n        MqttFuture msgFuture = new DefaultMqttFuture(clientId, futureKey, mqttMsg);\n        //添加一个兜底监听，释放消息和消息ID\n        msgFuture.addListener(mqttFuture -> {\n            if (isHighQos) {\n                releaseMsgIdAndRemoveMsg(channel, msgFuture, mqttMsg.getMsgId(), true);\n            }\n        });\n        //高版本额外设置\n        if (mqttConnectParameter.getMqttVersion() == MqttVersion.MQTT_5_0_0) {\n            MqttProperties mqttProperties = MqttUtils.getPublishMqttProperties(mqttMsgInfo);\n            mqttMsg.setMqttProperties(mqttProperties);\n        }\n        //真正发布消息\n        mqttDelegateHandler.sendPublish(channel, mqttMsg, msgFuture);\n        //如果是高qos，添加重试任务\n        if (isHighQos) {\n            Supplier<Channel> channelSupplier;\n            if (mqttConnectParameter.isCleanSession()) {\n                channelSupplier = () -> channel;\n            } else {\n                channelSupplier = this::getChannel;\n            }\n            MqttMsgRetryTask mqttMsgRetryTask = new MqttMsgRetryTask(channelSupplier, mqttMsg.getMsgId(), msgFuture);\n            mqttRetrier.retry(msgFuture, MqttConstant.MSG_RETRY_MILLS, mqttMsgRetryTask, false);\n        }\n        return msgFuture;\n    }\n\n\n    /**\n     * 进行MQTT的订阅\n     *\n     * @param channel                Channel\n     * @param mqttSubInfoList        订阅的列表\n     * @param subscriptionIdentifier 订阅标识符\n     * @param mqttUserProperties     用户属性\n     * @return Future\n     */\n    private MqttFuture doSubscribeFuture(Channel channel, List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n        MqttSubMsg mqttSubMsg = createMsgAndMsgId(channel, false, (msgId) -> new MqttSubMsg(msgId, mqttSubInfoList, subscriptionIdentifier, mqttUserProperties));\n        MqttFuture subscribeFuture = new DefaultMqttFuture<>(clientId, mqttSubMsg.getMsgId(), mqttSubMsg);\n        subscribeFuture.addListener(mqttFuture -> releaseMsgIdAndRemoveMsg(channel, subscribeFuture, mqttSubMsg.getMsgId(), false));\n        mqttDelegateHandler.sendSubscribe(channel, mqttSubMsg);\n        return subscribeFuture;\n    }\n\n    /**\n     * 进行MQTT的取消订阅\n     *\n     * @param channel            Channel\n     * @param topicList          取消订阅主题列表\n     * @param mqttUserProperties 用户属性\n     * @return Future\n     */\n    private MqttFuture doUnsubscribeFuture(Channel channel, List<String> topicList, MqttProperties.UserProperties mqttUserProperties) {\n        //创建一个取消订阅消息\n        MqttUnsubMsg mqttUnsubMsg = createMsgAndMsgId(channel, false, (msgId) -> new MqttUnsubMsg(msgId, topicList, mqttUserProperties));\n        //取消订阅Future\n        MqttFuture unsubscribeFuture = new DefaultMqttFuture<>(clientId, mqttUnsubMsg.getMsgId(), mqttUnsubMsg);\n        //添加一个兜底监听，对于取消订阅，不管成功与否，都需要释放消息ID\n        unsubscribeFuture.addListener(mqttFuture -> releaseMsgIdAndRemoveMsg(channel, unsubscribeFuture, mqttUnsubMsg.getMsgId(), false));\n        mqttDelegateHandler.sendUnsubscribe(channel, mqttUnsubMsg);\n        return unsubscribeFuture;\n    }\n\n\n    /**\n     * 创建一个消息和消息ID\n     *\n     * @param channel        Channel\n     * @param publishMqttMsg 是否是发布消息（三种消息，发布消息，订阅消息，取消订阅消息）\n     * @param function       创建消息的方式\n     * @param <T>            消息类型\n     * @return 创建的消息\n     */\n    private <T> T createMsgAndMsgId(Channel channel, boolean publishMqttMsg, Function<Integer, T> function) {\n        return createMsgAndMsgId(channel, publishMqttMsg, null, function);\n    }\n\n    /**\n     * 创建一个消息和消息ID\n     *\n     * @param channel        Channel\n     * @param publishMqttMsg 是否是发布消息（三种消息，发布消息，订阅消息，取消订阅消息）\n     * @param qos            消息的qos（只有发布消息存在）\n     * @param function       创建消息的方式\n     * @param <T>            消息类型\n     * @return 创建的消息\n     */\n    private <T> T createMsgAndMsgId(Channel channel, boolean publishMqttMsg, MqttQoS qos, Function<Integer, T> function) {\n        //qos是0的发送消息是不需要消息ID的，直接创建即可\n        if (publishMqttMsg && qos == MqttQoS.AT_MOST_ONCE) {\n            return function.apply(MqttConstant.INVALID_MSG_ID);\n        }\n        //消息\n        T result;\n        //获取一个消息ID\n        int msgId = MqttMsgIdCache.nextMsgId(clientId);\n        //是否清理会话，对于清理会话，所有的消息都存储在Channel中\n        if (mqttConnectParameter.isCleanSession()) {\n            //创建一个消息\n            result = function.apply(msgId);\n            //添加到Channel的发送消息中（包含发布消息，订阅消息，取消订阅消息）\n            channel.attr(MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().put(msgId, result);\n        } else {\n            //不清理会话时，发布消息存储在消息存储器中，需要进行重试\n            if (publishMqttMsg) {\n                result = function.apply(msgId);\n                mqttMsgStore.putMsg(MqttMsgDirection.SEND, clientId, (MqttMsg) result);\n            } else {\n                //不清理会话，订阅消息，取消订阅消息也存在Channel中\n                result = function.apply(msgId);\n                channel.attr(MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().put(msgId, result);\n            }\n        }\n        return result;\n    }\n\n\n    /**\n     * 释放消息ID和移除消息\n     *\n     * @param channel        Channel\n     * @param mqttFuture     对应的Future\n     * @param msgId          消息ID\n     * @param publishMqttMsg 是否是发布消息（三种消息，发布消息，订阅消息，取消订阅消息）\n     */\n    private void releaseMsgIdAndRemoveMsg(Channel channel, MqttFuture mqttFuture, int msgId, boolean publishMqttMsg) {\n        //是否成功\n        if (mqttFuture.isSuccess()) {\n            //只要成功都释放消息ID\n            MqttMsgIdCache.releaseMsgId(clientId, msgId);\n            //是否清理会话\n            if (mqttConnectParameter.isCleanSession()) {\n                //成功且清理会话则不管消息类型，从Channel上删除\n                channel.attr(MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().remove(msgId);\n            } else {\n                //成功且不清理会话，对于发布消息，从消息存储器中删除\n                if (publishMqttMsg) {\n                    //删除完成的消息\n                    mqttMsgStore.removeMsg(MqttMsgDirection.SEND, clientId, msgId);\n                } else {\n                    //订阅消息和取消订阅消息，从Channel中删除\n                    channel.attr(MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().remove(msgId);\n                }\n            }\n        } else {\n            // 失败（对于清理会话，只有连接断开时，才会算失败，对于不清理会话，只有客户端关闭时才会算失败）\n            if (mqttConnectParameter.isCleanSession()) {\n                //释放消息ID\n                MqttMsgIdCache.releaseMsgId(clientId, msgId);\n                //清理会话，从Channel中删除\n                channel.attr(MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().remove(msgId);\n            } else {\n                //不清理会话，那么只删除订阅、取消订阅消息，并且释放订阅、取消订阅的消息ID\n                if (!publishMqttMsg) {\n                    //释放消息ID\n                    MqttMsgIdCache.releaseMsgId(clientId, msgId);\n                    //不清理会话，订阅消息和取消订阅消息，从Channel中删除\n                    channel.attr(MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().remove(msgId);\n                }\n            }\n        }\n    }\n\n\n    /**\n     * 发送消息检查\n     *\n     * @param channel 通道\n     * @param qos QoS\n     * @param topic 主题\n     */\n    private void sendMsgCheck(Channel channel, MqttQoS qos, String topic) {\n        //关闭检查\n        closeCheck();\n        //在线检查\n        onlineCheck(channel);\n        //qos检查\n        qosCheck(qos);\n        //主题检验\n        MqttUtils.topicCheck(topic,false);\n    }\n\n    /**\n     * 是否是高等级的qos（1,2）\n     *\n     * @param qos QoS\n     * @return true : 是 false : 否\n     */\n    private boolean isHighQos(MqttQoS qos) {\n        return (qos == MqttQoS.AT_LEAST_ONCE) || (qos == MqttQoS.EXACTLY_ONCE);\n    }\n\n    /**\n     * 订阅检查\n     *\n     * @param channel 通道\n     * @param mqttSubInfoList MqttSubInfo列表\n     */\n    private void subscribeCheck(Channel channel, List<MqttSubInfo> mqttSubInfoList) {\n        closeCheck();\n        onlineCheck(channel);\n        for (MqttSubInfo mqttSubInfo : mqttSubInfoList) {\n            String topic = mqttSubInfo.getTopic();\n            MqttQoS qos = mqttSubInfo.getQos();\n            AssertUtils.notEmpty(topic, \"topic is empty\");\n            AssertUtils.notNull(qos, \"qos is null\");\n            MqttUtils.topicCheck(topic,true);\n            qosCheck(qos);\n        }\n    }\n\n    /**\n     * 取消订阅检查\n     *\n     * @param channel 通道\n     * @param topicList 主题列表\n     */\n    private void unsubscribeCheck(Channel channel, List<String> topicList) {\n        closeCheck();\n        onlineCheck(channel);\n        for (String topic : topicList) {\n            AssertUtils.notEmpty(topic, \"topic is empty\");\n            MqttUtils.topicCheck(topic,true);\n        }\n    }\n\n    private void qosCheck(MqttQoS qos) {\n        if (qos == MqttQoS.FAILURE) {\n            throw new IllegalArgumentException(qos.value() + \" is illegal qos\");\n        }\n    }\n\n    /**\n     * 转换为MqttSubInfo列表\n     *\n     * @param topicList 主题列表\n     * @param qos QoS\n     * @return MqttSubInfo列表\n     */\n    private List<MqttSubInfo> toSubInfoList(List<String> topicList, MqttQoS qos) {\n        List<MqttSubInfo> mqttSubInfoList = new ArrayList<>(topicList.size());\n        for (String topic : topicList) {\n            mqttSubInfoList.add(new MqttSubInfo(topic, qos));\n        }\n        return mqttSubInfoList;\n    }\n\n    /**\n     * 连接检查\n     *\n     * @param channel 通道\n     */\n    private void connectCheck(Channel channel) {\n        //关闭检查\n        closeCheck();\n        //在线检查\n        if (isOnline(channel)) {\n            throw new MqttStateCheckException(\"client: \" + clientId + \" has already connected\");\n        }\n        // 正在连接中检查\n        if (isOpen(channel) || isConnected(channel)) {\n            throw new MqttStateCheckException(\"client: \" + clientId + \" is connecting\");\n        }\n    }\n\n    /**\n     * 第一次连接成功后，持久化的旧消息重试\n     */\n    private void oldMsgListRetry() {\n        LOCK.readLock().lock();\n        LogUtils.info(DefaultMqttClient.class, \"client: \" + clientId + \" start old msg list retry\");\n        try {\n            if (!isClose()) {\n                List<MqttMsg> msgList = mqttMsgStore.getMsgList(MqttMsgDirection.SEND, clientId);\n                if (EmptyUtils.isNotEmpty(msgList)) {\n                    Channel channel = currentChannel;\n                    for (MqttMsg mqttMsg : msgList) {\n                        MqttMsgState msgState = mqttMsg.getMsgState();\n                        MqttMsgDirection mqttMsgDirection = mqttMsg.getMqttMsgDirection();\n                        //只重试消息方向是发送的，且PUBLISH和PUBREL的消息\n                        if (mqttMsgDirection == MqttMsgDirection.SEND) {\n                            if (msgState == MqttMsgState.PUBLISH || msgState == MqttMsgState.PUBREL) {\n                                LogUtils.debug(DefaultMqttClient.class, \"client: \" + clientId + \" add old retry msg: \" + mqttMsg);\n                                int msgId = mqttMsg.getMsgId();\n                                MqttFuture msgFuture = new DefaultMqttFuture(clientId, msgId, mqttMsg);\n                                msgFuture.addListener(mqttFuture -> releaseMsgIdAndRemoveMsg(channel, mqttFuture, msgId, true));\n                                mqttRetrier.retry(msgFuture, MqttConstant.MSG_RETRY_MILLS, new MqttMsgRetryTask(this::getChannel, msgId, msgFuture), true);\n                            }\n                        }\n                    }\n                }\n            }\n        } finally {\n            LOCK.readLock().unlock();\n        }\n    }\n\n    /**\n     * 开启自动重连任务\n     */\n    private void startReconnectTask() {\n        LOCK.readLock().lock();\n        LogUtils.info(DefaultMqttClient.class, \"client: \" + clientId + \" start reconnecting scheduled task\");\n        try {\n            if (!isClose() && !manualDisconnect) {\n                EventLoopGroup eventLoopGroup = mqttConfiguration.getEventLoopGroup();\n                long reconnectInterval = MqttUtils.getKeepAliveTimeSeconds(getChannel(), mqttConnectParameter.getKeepAliveTimeSeconds()) + 1;\n                reconnectScheduledFuture = eventLoopGroup.scheduleWithFixedDelay(() -> {\n                    Channel channel = getChannel();\n                    if (isOpen(channel) || isClose()) {\n                        return;\n                    }\n                    try {\n                        MqttFuture<Channel> reconnectFuture = DefaultMqttClient.this.doConnect();\n                        reconnectFuture.addListener(mqttFuture -> {\n                            //重连成功\n                            if (mqttFuture.isSuccess()) {\n                                LogUtils.info(DefaultMqttClient.class, \"client: \" + clientId + \" reconnection is successful\");\n                            } else {\n                                //重连失败\n                                LogUtils.warn(DefaultMqttClient.class, \"client: \" + clientId + \" reconnection is failed,cause: \" + mqttFuture.getCause().getMessage());\n                            }\n                        });\n                    } catch (MqttStateCheckException mqttStateCheckException) {\n                        //忽略Mqtt状态检查异常\n                    }\n\n                }, reconnectInterval, reconnectInterval, TimeUnit.SECONDS);\n            }\n        } finally {\n            LOCK.readLock().unlock();\n        }\n    }\n\n    @Override\n    protected void doClose() {\n        LOCK.writeLock().lock();\n        try {\n            Channel channel = currentChannel;\n            if (channel != null) {\n                if (channel.isOpen()) {\n                    channel.close().addListener(future -> closeNotify());\n                }\n            } else {\n                closeNotify();\n            }\n        } finally {\n            LOCK.writeLock().unlock();\n        }\n    }\n\n    /**\n     * 唤醒正在等待中的线程\n     */\n    private void closeNotify() {\n        List<MqttMsg> msgList = mqttMsgStore.getMsgList(MqttMsgDirection.SEND, clientId);\n        if (EmptyUtils.isNotEmpty(msgList)) {\n            for (MqttMsg mqttMsg : msgList) {\n                Integer msgId = mqttMsg.getMsgId();\n                MqttFuture msgFuture = MqttFuture.getFuture(clientId, msgId);\n                if (msgFuture != null) {\n                    msgFuture.setFailure(new MqttException(\"client has been closed\", clientId));\n                }\n            }\n        }\n    }\n\n    /**\n     * 是否在线，TCP是连接中（ESTABLISHED），且认证完成\n     *\n     * @param channel 通道\n     * @return 是否在线\n     */\n    private boolean isOnline(Channel channel) {\n        if (channel != null && channel.isActive() && channel.attr(MqttConstant.AUTH_STATE_ATTRIBUTE_KEY).get() == MqttAuthState.AUTH_SUCCESS) {\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    public boolean isOnline() {\n        return isOnline(currentChannel);\n    }\n\n    @Override\n    public boolean isActive() {\n        return (currentChannel != null && currentChannel.isActive());\n    }\n\n\n    /**\n     * 是否连接中（ESTABLISHED）\n     *\n     * @param channel 通道\n     * @return 是否连接中\n     */\n    private boolean isConnected(Channel channel) {\n        if (channel != null && channel.isActive()) {\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Channel是否打开中\n     *\n     * @param channel 通道\n     * @return 是否打开中\n     */\n    private boolean isOpen(Channel channel) {\n        if (channel != null && channel.isOpen()) {\n            return true;\n        }\n        return false;\n    }\n\n    private void onlineCheck(Channel channel) {\n        if (!isOnline(channel)) {\n            throw new MqttStateCheckException(\"client: \" + clientId + \" is not connected.\");\n        }\n    }\n\n\n    @Override\n    public void subscribeCallback(MqttSubscribeCallbackResult mqttSubscribeCallbackResult) {\n        for (MqttCallback mqttCallback : mqttCallbackSet) {\n            mqttCallback.subscribeCallback(mqttSubscribeCallbackResult);\n        }\n    }\n\n    @Override\n    public void unsubscribeCallback(MqttUnSubscribeCallbackResult mqttUnSubscribeCallbackResult) {\n        for (MqttCallback mqttCallback : mqttCallbackSet) {\n            mqttCallback.unsubscribeCallback(mqttUnSubscribeCallbackResult);\n        }\n    }\n\n    @Override\n    public void messageSendCallback(MqttSendCallbackResult mqttSendCallbackResult) {\n        for (MqttCallback mqttCallback : mqttCallbackSet) {\n            mqttCallback.messageSendCallback(mqttSendCallbackResult);\n        }\n    }\n\n    @Override\n    public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n        for (MqttCallback mqttCallback : mqttCallbackSet) {\n            mqttCallback.messageReceiveCallback(receiveCallbackResult);\n        }\n    }\n\n    @Override\n    public void connectCompleteCallback(MqttConnectCallbackResult mqttConnectCallbackResult) {\n        for (MqttCallback mqttCallback : mqttCallbackSet) {\n            mqttCallback.connectCompleteCallback(mqttConnectCallbackResult);\n        }\n    }\n\n    @Override\n    public void channelConnectCallback(MqttConnectCallbackResult mqttConnectCallbackResult) {\n        for (MqttCallback mqttCallback : mqttCallbackSet) {\n            mqttCallback.channelConnectCallback(mqttConnectCallbackResult);\n        }\n    }\n\n\n    @Override\n    public void connectLostCallback(MqttConnectLostCallbackResult mqttConnectLostCallbackResult) {\n        for (MqttCallback mqttCallback : mqttCallbackSet) {\n            mqttCallback.connectLostCallback(mqttConnectLostCallbackResult);\n        }\n    }\n\n    @Override\n    public void heartbeatCallback(MqttHeartbeatCallbackResult mqttHeartbeatCallbackResult) {\n        for (MqttCallback mqttCallback : mqttCallbackSet) {\n            mqttCallback.heartbeatCallback(mqttHeartbeatCallbackResult);\n        }\n    }\n\n    @Override\n    public void channelExceptionCaught(MqttConnectParameter mqttConnectParameter, MqttChannelExceptionCallbackResult mqttChannelExceptionCallbackResult) {\n        for (MqttCallback mqttCallback : mqttCallbackSet) {\n            mqttCallback.channelExceptionCaught(mqttConnectParameter, mqttChannelExceptionCallbackResult);\n        }\n    }\n\n    /**\n     * Mqtt的发布消息重试任务\n     */\n    private class MqttMsgRetryTask implements Runnable {\n        /**\n         * 重试的消息ID，因为消息的状态可能会改变，所以需要每次使用消息ID查询\n         */\n        private final int msgId;\n        /**\n         * 对应消息的Future，用来判断是否完成\n         */\n        private final MqttFuture msgFuture;\n        /**\n         * 怎么获取一个Channel。对于清理会话来说，Channel是建立连接时的，对于不清理会话，Channel是每次最新的\n         */\n        private final Supplier<Channel> channelSupplier;\n\n        private MqttMsgRetryTask(Supplier<Channel> channelSupplier, int msgId, MqttFuture msgFuture) {\n            this.channelSupplier = channelSupplier;\n            this.msgId = msgId;\n            this.msgFuture = msgFuture;\n        }\n\n        @Override\n        public void run() {\n            LogUtils.debug(MqttMsgRetryTask.class, \"client: \" + clientId + \",old msg retry,msgId: \" + msgId);\n            Channel channel = channelSupplier.get();\n            MqttMsg mqttMsg;\n            //清理会话从Channel中获取消息，不清理则从消息存储器中获取\n            if (mqttConnectParameter.isCleanSession()) {\n                mqttMsg = (MqttMsg) channel.attr(MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().get(msgId);\n            } else {\n                mqttMsg = mqttMsgStore.getMsg(MqttMsgDirection.SEND, clientId, msgId);\n            }\n            //在线和未完成时才继续发送\n            if (isOnline(channel) && !msgFuture.isDone() && mqttMsg != null) {\n                MqttMsgState msgState = mqttMsg.getMsgState();\n                //只有PUBLISH和PUBREL消息的需要重新发送，别的状态不需要发送了\n                if (MqttMsgState.PUBLISH == msgState) {\n                    mqttDelegateHandler.sendPublish(channel, mqttMsg, msgFuture);\n                } else if (MqttMsgState.PUBREL == msgState) {\n                    mqttDelegateHandler.sendPubrel(channel, mqttMsg);\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/DefaultMqttClientFactory.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.connector.MqttConnector;\nimport io.github.netty.mqtt.client.createor.ObjectCreator;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.plugin.Interceptor;\nimport io.github.netty.mqtt.client.store.MqttMsgStore;\nimport io.github.netty.mqtt.client.support.proxy.ProxyFactory;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.LogUtils;\nimport io.netty.channel.ChannelOption;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * 默认的MQTT客户端工厂\n * @author: xzc-coder\n */\npublic class DefaultMqttClientFactory implements MqttClientFactory {\n\n    /**\n     * 全局的MQTT配置，此工厂下创建的所有MQTT客户端都应该使用该配置\n     */\n    private final MqttConfiguration mqttConfiguration;\n    /**\n     * MQTT客户端MAP，用来避免重复创建，同时创建相同ClientId的MqttClient会导致不可预测的问题\n     */\n    private static final Map<String, MqttClient> MQTT_CLIENT_MAP = new ConcurrentHashMap<>();\n\n    public DefaultMqttClientFactory() {\n        this(1);\n    }\n\n    public DefaultMqttClientFactory(int maxThreadNumber) {\n        this(new MqttConfiguration(maxThreadNumber));\n    }\n\n    public DefaultMqttClientFactory(MqttConfiguration mqttConfiguration) {\n        AssertUtils.notNull(mqttConfiguration, \"mqttConfiguration is null \");\n        this.mqttConfiguration = mqttConfiguration;\n        this.mqttConfiguration.setMqttClientFactory(this);\n    }\n\n    @Override\n    public MqttClient createMqttClient(MqttConnectParameter mqttConnectParameter) {\n        AssertUtils.notNull(mqttConnectParameter, \"mqttConnectParameter is null\");\n        MqttClient mqttClient = mqttConfiguration.newMqttClient(this, mqttConfiguration, mqttConnectParameter);\n        MQTT_CLIENT_MAP.put(mqttClient.getClientId(), mqttClient);\n        return mqttClient;\n    }\n\n    @Override\n    public void closeMqttClient(String clientId) {\n        MqttClient mqttClient = MQTT_CLIENT_MAP.get(clientId);\n        if (mqttClient != null) {\n            mqttClient.close();\n        }\n    }\n\n    @Override\n    public void releaseMqttClientId(String clientId) {\n        MQTT_CLIENT_MAP.remove(clientId);\n    }\n\n    @Override\n    public void setProxyFactory(ProxyFactory proxyFactory) {\n        this.mqttConfiguration.setProxyFactory(proxyFactory);\n    }\n\n    @Override\n    public void addInterceptor(Interceptor interceptor) {\n        this.mqttConfiguration.addInterceptor(interceptor);\n    }\n\n    @Override\n    public void setMqttClientObjectCreator(ObjectCreator<MqttClient> mqttClientObjectCreator) {\n        this.mqttConfiguration.setMqttClientObjectCreator(mqttClientObjectCreator);\n    }\n\n    @Override\n    public void setMqttConnectorObjectCreator(ObjectCreator<MqttConnector> mqttConnectorObjectCreator) {\n        this.mqttConfiguration.setMqttConnectorObjectCreator(mqttConnectorObjectCreator);\n    }\n\n    @Override\n    public void setMqttDelegateHandlerObjectCreator(ObjectCreator<MqttDelegateHandler> mqttDelegateHandlerObjectCreator) {\n        this.mqttConfiguration.setMqttDelegateHandlerObjectCreator(mqttDelegateHandlerObjectCreator);\n    }\n\n    @Override\n    public void setMqttMsgStore(MqttMsgStore mqttMsgStore) {\n        this.mqttConfiguration.setMqttMsgStore(mqttMsgStore);\n    }\n\n    @Override\n    public MqttConfiguration getMqttConfiguration() {\n        return this.mqttConfiguration;\n    }\n\n    @Override\n    public void option(ChannelOption option, Object value) {\n        this.mqttConfiguration.option(option, value);\n    }\n\n    @Override\n    public synchronized void close() {\n        try {\n            LogUtils.info(DefaultMqttClientFactory.class, \"MqttClientFactory close\");\n            closeClient();\n        } finally {\n            this.mqttConfiguration.close();\n        }\n    }\n\n    /**\n     * 关闭客户端\n     */\n    private void closeClient() {\n        Set<String> clientIdSet = MQTT_CLIENT_MAP.keySet();\n        for (String clientId : clientIdSet) {\n            MqttClient mqttClient = MQTT_CLIENT_MAP.get(clientId);\n            if (mqttClient != null && !mqttClient.isClose()) {\n                try {\n                    mqttClient.close();\n                } catch (Exception e) {\n                    LogUtils.warn(DefaultMqttClientFactory.class, \"client:\" + clientId + \"close failed,cause : \" + e.getMessage());\n                }\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/Endpoint.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport java.net.InetSocketAddress;\nimport java.net.URL;\n\n/**\n * 端点，即客户端和服务端的连接信息\n * @author: xzc-coder\n */\npublic interface Endpoint {\n\n    /**\n     * 获取本机的地址，Channel是open时才有值\n     *\n     * @return InetSocketAddress\n     */\n    InetSocketAddress getLocalAddress();\n\n    /**\n     * 获取服务器的地址，Channel是open时才有值\n     *\n     * @return InetSocketAddress\n     */\n    InetSocketAddress getRemoteAddress();\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/MqttClient.java",
    "content": "package io.github.netty.mqtt.client;\n\n\nimport io.github.netty.mqtt.client.callback.MqttCallback;\nimport io.github.netty.mqtt.client.msg.MqttDisconnectMsg;\nimport io.github.netty.mqtt.client.msg.MqttMsgInfo;\nimport io.github.netty.mqtt.client.msg.MqttSubInfo;\nimport io.github.netty.mqtt.client.support.future.MqttFutureWrapper;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * MQTT客户端接口\n * @author: xzc-coder\n */\npublic interface MqttClient extends Endpoint {\n\n    /**\n     * 获取客户端ID\n     *\n     * @return 客户端ID\n     */\n    String getClientId();\n\n    /**\n     * 获取MQTT的连接参数\n     *\n     * @return MQTT的连接参数\n     */\n    MqttConnectParameter getMqttConnectParameter();\n\n    /**\n     * 进行连接，不会阻塞\n     *\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper connectFuture();\n\n    /**\n     * 进行连接，会阻塞至超时或者连接成功\n     */\n    void connect();\n\n\n    /**\n     * 断开连接，会阻塞至TCP断开\n     */\n    void disconnect();\n\n\n    /**\n     * 断开连接\n     *\n     * @return Future\n     */\n    MqttFutureWrapper disconnectFuture();\n\n\n    /**\n     * 断开连接\n     *\n     * @param mqttDisconnectMsg 断开消息\n     * @return Future\n     */\n    MqttFutureWrapper disconnectFuture(MqttDisconnectMsg mqttDisconnectMsg);\n\n\n    /**\n     * 断开连接\n     *\n     * @param mqttDisconnectMsg 断开消息\n     */\n    void disconnect(MqttDisconnectMsg mqttDisconnectMsg);\n\n    /**\n     * 发送一个消息，不会阻塞（MQTT 5）\n     *\n     * @param mqttMsgInfo mqtt消息\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper publishFuture(MqttMsgInfo mqttMsgInfo);\n\n\n    /**\n     * 发送一个消息，不会阻塞\n     *\n     * @param payload 载荷\n     * @param topic   主题\n     * @param qos     服务质量\n     * @param retain  是否保留消息\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper publishFuture(byte[] payload, String topic, MqttQoS qos, boolean retain);\n\n    /**\n     * 发送一个消息，不会阻塞，retain 为 false\n     *\n     * @param payload 载荷\n     * @param topic   主题\n     * @param qos     服务质量\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper publishFuture(byte[] payload, String topic, MqttQoS qos);\n\n    /**\n     * 发送一个消息，不会阻塞，retain 为 false，qos 为 0\n     *\n     * @param payload 载荷\n     * @param topic   主题\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper publishFuture(byte[] payload, String topic);\n\n\n    /**\n     * 发送一个消息，会阻塞至发送完成（MQTT 5）\n     *\n     * @param mqttMsgInfo mqtt消息\n     */\n    void publish(MqttMsgInfo mqttMsgInfo);\n\n    /**\n     * 发送一个消息，会阻塞至发送完成\n     *\n     * @param payload 载荷\n     * @param topic   主题\n     * @param qos     服务质量\n     * @param retain  是否保留消息\n     */\n    void publish(byte[] payload, String topic, MqttQoS qos, boolean retain);\n\n    /**\n     * 发送一个消息，会阻塞至发送完成，retain 为 false\n     *\n     * @param payload 载荷\n     * @param topic   主题\n     * @param qos     服务质量\n     */\n    void publish(byte[] payload, String topic, MqttQoS qos);\n\n    /**\n     * 发送一个消息，会阻塞至发送完成,retain 为 false，qos 为 0\n     *\n     * @param payload 载荷\n     * @param topic   主题\n     */\n    void publish(byte[] payload, String topic);\n\n\n    /**\n     * 发送一个订阅消息，会阻塞至发送完成\n     *\n     * @param topic 订阅的主题\n     * @param qos   订阅的qos\n     */\n    void subscribe(String topic, MqttQoS qos);\n\n\n    /**\n     * 发送一个订阅消息，会阻塞至发送完成（MQTT 5）\n     *\n     * @param mqttSubInfo 订阅消息\n     */\n    void subscribe(MqttSubInfo mqttSubInfo);\n\n    /**\n     * 发送一个订阅消息，会阻塞至发送完成（MQTT 5）\n     *\n     * @param mqttSubInfo            订阅消息\n     * @param subscriptionIdentifier 订阅标识符\n     * @param mqttUserProperties     用户属性\n     */\n    void subscribe(MqttSubInfo mqttSubInfo, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties);\n\n    /**\n     * 发送一个订阅消息，会阻塞至发送完成\n     *\n     * @param mqttSubInfoList 订阅消息集合\n     */\n    void subscribes(List<MqttSubInfo> mqttSubInfoList);\n\n\n    /**\n     * 发送一个订阅消息，会阻塞至发送完成（MQTT 5）\n     *\n     * @param mqttSubInfoList        订阅消息集合\n     * @param subscriptionIdentifier 订阅标识符\n     * @param mqttUserProperties     用户属性\n     */\n    void subscribes(List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties);\n\n    /**\n     * 发送一个订阅消息，会阻塞至发送完成\n     *\n     * @param topicList 订阅主题集合\n     * @param qos       订阅的qos\n     */\n    void subscribes(List<String> topicList, MqttQoS qos);\n\n\n    /**\n     * 发送一个订阅消息，不会阻塞\n     *\n     * @param topicList 订阅主题集合\n     * @param qos       订阅的qos\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper subscribesFuture(List<String> topicList, MqttQoS qos);\n\n    /**\n     * 发送一个订阅消息，不会阻塞\n     *\n     * @param topic 订阅的主题\n     * @param qos   订阅的qos\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper subscribeFuture(String topic, MqttQoS qos);\n\n    /**\n     * 发送一个订阅消息，不会阻塞（MQTT 5）\n     *\n     * @param mqttSubInfo 订阅消息\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper subscribeFuture(MqttSubInfo mqttSubInfo);\n\n\n    /**\n     * 发送一个订阅消息，不会阻塞（MQTT 5）\n     *\n     * @param mqttSubInfo            订阅消息\n     * @param subscriptionIdentifier 订阅标识符\n     * @param mqttUserProperties     订阅用户属性\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper subscribeFuture(MqttSubInfo mqttSubInfo, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties);\n\n    /**\n     * 发送一个订阅消息，不会阻塞\n     *\n     * @param mqttSubInfoList        订阅消息集合（MQTT 5）\n     * @param subscriptionIdentifier 订阅标识符\n     * @param mqttUserProperties     用户属性\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper subscribesFuture(List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties);\n\n    /**\n     * 发送一个订阅消息，不会阻塞\n     *\n     * @param mqttSubInfoList 订阅集合\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper subscribesFuture(List<MqttSubInfo> mqttSubInfoList);\n\n    /**\n     * 取消订阅，会阻塞至消息发送完成（MQTT 5）\n     *\n     * @param topicList          取消订阅的主题集合\n     * @param mqttUserProperties 用户属性\n     */\n    void unsubscribes(List<String> topicList, MqttProperties.UserProperties mqttUserProperties);\n\n    /**\n     * 取消订阅，会阻塞至消息发送完成\n     *\n     * @param topicList 取消订阅的主题集合\n     */\n    void unsubscribes(List<String> topicList);\n\n    /**\n     * 取消订阅，会阻塞至消息发送完成（MQTT 5）\n     *\n     * @param topic              取消订阅的主题\n     * @param mqttUserProperties 用户属性\n     */\n    void unsubscribe(String topic, MqttProperties.UserProperties mqttUserProperties);\n\n    /**\n     * 取消订阅，会阻塞至消息发送完成\n     *\n     * @param topic 取消订阅的主题\n     */\n    void unsubscribe(String topic);\n\n    /**\n     * 取消订阅，不会阻塞（MQTT 5）\n     *\n     * @param topic              取消订阅的主题\n     * @param mqttUserProperties 用户属性\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper unsubscribeFuture(String topic, MqttProperties.UserProperties mqttUserProperties);\n\n    /**\n     * 取消订阅，不会阻塞\n     *\n     * @param topic 取消订阅的主题\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper unsubscribeFuture(String topic);\n\n\n    /**\n     * 取消订阅，不会阻塞\n     *\n     * @param topicList 取消订阅的主题集合\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper unsubscribesFuture(List<String> topicList);\n\n    /**\n     * 取消订阅，不会阻塞（MQTT 5）\n     *\n     * @param topicList          取消订阅的主题集合\n     * @param mqttUserProperties 用户属性\n     * @return MqttFutureWrapper\n     */\n    MqttFutureWrapper unsubscribesFuture(List<String> topicList, MqttProperties.UserProperties mqttUserProperties);\n\n\n    /**\n     * 添加一个MQTT回调器\n     *\n     * @param mqttCallback 回调器\n     */\n    void addMqttCallback(MqttCallback mqttCallback);\n\n    /**\n     * 添加MQTT回调器集合\n     *\n     * @param mqttCallbacks 回调器集合\n     */\n    void addMqttCallbacks(Collection<MqttCallback> mqttCallbacks);\n\n    /**\n     * 客户端是否在线（完成认证的才算在线）\n     *\n     * @return 是否在线\n     */\n    boolean isOnline();\n\n    /**\n     * 客户端是否活跃（指TCP连接是否是ESTABLISHED状态）\n     *\n     * @return 是否活跃\n     */\n    boolean isActive();\n\n    /**\n     * 客户端是否关闭\n     *\n     * @return 是否关闭\n     */\n    boolean isClose();\n\n    /**\n     * 关闭客户端，关闭后，无法再进行连接、发送消息、订阅、取消订阅等操作\n     */\n    void close();\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/MqttClientFactory.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.connector.MqttConnector;\nimport io.github.netty.mqtt.client.createor.ObjectCreator;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.plugin.Interceptor;\nimport io.github.netty.mqtt.client.store.MqttMsgStore;\nimport io.github.netty.mqtt.client.support.proxy.ProxyFactory;\nimport io.netty.channel.ChannelOption;\n\n\n/**\n * MQTT客户端工厂接口\n * @author: xzc-coder\n */\npublic interface MqttClientFactory {\n\n\n    /**\n     * 创建一个MQTT客户端\n     *\n     * @param mqttConnectParameter MQTT连接参数\n     * @return MQTT客户端\n     */\n    MqttClient createMqttClient(MqttConnectParameter mqttConnectParameter);\n\n    /**\n     * 关闭一个MQTT客户端\n     *\n     * @param clientId 客户端ID\n     */\n    void closeMqttClient(String clientId);\n\n    /**\n     * 释放掉一个MQTT的客户端ID\n     *\n     * @param clientId 客户端ID\n     */\n    void releaseMqttClientId(String clientId);\n\n    /**\n     * 设置客户端工厂\n     *\n     * @param proxyFactory 代理工厂\n     */\n    void setProxyFactory(ProxyFactory proxyFactory);\n\n    /**\n     * 添加一个拦截器\n     *\n     * @param interceptor 拦截器\n     */\n    void addInterceptor(Interceptor interceptor);\n\n    /**\n     * 设置MQTT客户端对象创建器\n     *\n     * @param mqttClientObjectCreator MQTT客户端对象创建器\n     */\n    void setMqttClientObjectCreator(ObjectCreator<MqttClient> mqttClientObjectCreator);\n\n    /**\n     * 设置MQTT连接器对象创建器\n     *\n     * @param mqttConnectorObjectCreator MQTT连接器对象创建器\n     */\n    void setMqttConnectorObjectCreator(ObjectCreator<MqttConnector> mqttConnectorObjectCreator);\n\n    /**\n     * 设置MQTT委托处理器对象创建器\n     *\n     * @param mqttDelegateHandlerObjectCreator MQTT委托处理器对象创建器\n     */\n    void setMqttDelegateHandlerObjectCreator(ObjectCreator<MqttDelegateHandler> mqttDelegateHandlerObjectCreator);\n\n    /**\n     * 设置一个MQTT消息存储器\n     *\n     * @param mqttMsgStore MQTT消息存储器\n     */\n    void setMqttMsgStore(MqttMsgStore mqttMsgStore);\n\n    /**\n     * 获取MQTT全局配置\n     *\n     * @return MQTT全局配置\n     */\n    MqttConfiguration getMqttConfiguration();\n\n    /**\n     * 添加或删除一个Netty的TCP配置项（value为null时为删除）\n     *\n     * @param option 连接参数项\n     * @param value 连接参数值\n     */\n    void option(ChannelOption option, Object value);\n\n    /**\n     * 关闭MQTT客户端工厂，会释放线程资源\n     */\n    void close();\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/MqttConfiguration.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.connector.MqttConnector;\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.createor.MqttClientObjectCreator;\nimport io.github.netty.mqtt.client.createor.MqttConnectorObjectCreator;\nimport io.github.netty.mqtt.client.createor.MqttDelegateHandlerObjectCreator;\nimport io.github.netty.mqtt.client.createor.ObjectCreator;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.plugin.Interceptor;\nimport io.github.netty.mqtt.client.store.MemoryMqttMsgStore;\nimport io.github.netty.mqtt.client.store.MqttMsgStore;\nimport io.github.netty.mqtt.client.support.proxy.JdkProxyFactory;\nimport io.github.netty.mqtt.client.support.proxy.ProxyFactory;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.LogUtils;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.util.concurrent.DefaultThreadFactory;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * MQTT全局配置\n * @author: xzc-coder\n */\npublic class MqttConfiguration {\n\n    /**\n     * MQTT的客户端工厂\n     */\n    private MqttClientFactory mqttClientFactory;\n    /**\n     * Netty中的TCP相关的参数Map\n     */\n    private final Map<ChannelOption, Object> optionMap = new ConcurrentHashMap<>();\n    /**\n     * 代理工厂\n     */\n    private ProxyFactory proxyFactory = new JdkProxyFactory();\n    /**\n     * 拦截器集合\n     */\n    private final List<Interceptor> interceptorList = new CopyOnWriteArrayList<>();\n    /**\n     * IO的最大线程数，默认为1，根据要创建的客户端数量进行调整\n     */\n    private final int maxThreadNumber;\n    /**\n     * Netty的线程组\n     */\n    private final EventLoopGroup eventLoopGroup;\n    /**\n     * MQTT客户端创建器\n     */\n    private ObjectCreator<MqttClient> mqttClientObjectCreator = new MqttClientObjectCreator();\n    /**\n     * MQTT连接器创建器\n     */\n    private ObjectCreator<MqttConnector> mqttConnectorObjectCreator = new MqttConnectorObjectCreator();\n    /**\n     * MQTT委托处理器创建器\n     */\n    private ObjectCreator<MqttDelegateHandler> mqttDelegateHandlerObjectCreator = new MqttDelegateHandlerObjectCreator();\n    /**\n     * MQTT消息存储器\n     */\n    private MqttMsgStore mqttMsgStore = new MemoryMqttMsgStore();\n\n\n    public EventLoopGroup getEventLoopGroup() {\n        return eventLoopGroup;\n    }\n\n    public MqttConfiguration() {\n        this(1);\n    }\n\n    public MqttConfiguration(int maxThreadNumber) {\n        if (maxThreadNumber <= 0) {\n            maxThreadNumber = 1;\n        }\n        this.maxThreadNumber = maxThreadNumber;\n        this.eventLoopGroup = new NioEventLoopGroup(maxThreadNumber, new DefaultThreadFactory(MqttConstant.THREAD_FACTORY_POOL_NAME));\n\n    }\n\n    public void addInterceptor(Interceptor interceptor) {\n        if (interceptor != null) {\n            interceptorList.add(interceptor);\n        }\n    }\n\n    public MqttClient newMqttClient(Object... constructorArgs) {\n        MqttClient mqttClient = mqttClientObjectCreator.createObject(constructorArgs);\n        return (MqttClient) proxyFactory.getProxy(mqttClient, interceptorList);\n    }\n\n    public MqttConnector newMqttConnector(Object... constructorArgs) {\n        MqttConnector mqttConnector = mqttConnectorObjectCreator.createObject(constructorArgs);\n        return (MqttConnector) proxyFactory.getProxy(mqttConnector, interceptorList);\n    }\n\n    public MqttDelegateHandler newMqttMsgHandler(Object... constructorArgs) {\n        MqttDelegateHandler mqttDelegateHandler = mqttDelegateHandlerObjectCreator.createObject(constructorArgs);\n        return (MqttDelegateHandler) proxyFactory.getProxy(mqttDelegateHandler, interceptorList);\n    }\n\n    public List<Interceptor> getInterceptorList() {\n        return Collections.unmodifiableList(interceptorList);\n    }\n\n    public int getMaxThreadNumber() {\n        return maxThreadNumber;\n    }\n\n    public ProxyFactory getProxyFactory() {\n        return proxyFactory;\n    }\n\n    public void setProxyFactory(ProxyFactory proxyFactory) {\n        AssertUtils.notNull(proxyFactory,\"proxyFactory is null\");\n        this.proxyFactory = proxyFactory;\n    }\n\n    /**\n     * 添加一个Netty中的TCP参数\n     *\n     * @param option 参数项\n     * @param value  值（值不能为null，为null时则为删除该参数项）\n     */\n    public void option(ChannelOption option, Object value) {\n        if (value == null) {\n            optionMap.remove(option);\n        } else {\n            optionMap.put(option, value);\n        }\n    }\n\n    /**\n     * 获取Netty中的TCP参数Map\n     *\n     * @return 不可修改的TCP参数Map\n     */\n    public Map<ChannelOption, Object> getOptionMap() {\n        return Collections.unmodifiableMap(optionMap);\n    }\n\n    /**\n     * 关闭配置，在关闭MqttClientFactory时调用，会释放线程资源\n     */\n    public synchronized void close() {\n        LogUtils.info(MqttConfiguration.class,\"MqttConfiguration close\");\n        eventLoopGroup.shutdownGracefully().syncUninterruptibly();\n        LogUtils.info(MqttConfiguration.class,\"MqttMsgStore close\");\n        mqttMsgStore.close();\n    }\n\n    public ObjectCreator<MqttClient> getMqttClientObjectCreator() {\n        return mqttClientObjectCreator;\n    }\n\n    public void setMqttClientObjectCreator(ObjectCreator<MqttClient> mqttClientObjectCreator) {\n        AssertUtils.notNull(mqttClientObjectCreator, \"mqttClientObjectCreator is null\");\n        this.mqttClientObjectCreator = mqttClientObjectCreator;\n    }\n\n    public ObjectCreator<MqttConnector> getMqttConnectorObjectCreator() {\n        return mqttConnectorObjectCreator;\n    }\n\n    public void setMqttConnectorObjectCreator(ObjectCreator<MqttConnector> mqttConnectorObjectCreator) {\n        AssertUtils.notNull(mqttConnectorObjectCreator, \"mqttConnectorObjectCreator is null\");\n        this.mqttConnectorObjectCreator = mqttConnectorObjectCreator;\n    }\n\n    public ObjectCreator<MqttDelegateHandler> getMqttDelegateHandlerObjectCreator() {\n        return mqttDelegateHandlerObjectCreator;\n    }\n\n    public void setMqttDelegateHandlerObjectCreator(ObjectCreator<MqttDelegateHandler> mqttDelegateHandlerObjectCreator) {\n        AssertUtils.notNull(mqttDelegateHandlerObjectCreator, \"mqttDelegateHandlerObjectCreator is null\");\n        this.mqttDelegateHandlerObjectCreator = mqttDelegateHandlerObjectCreator;\n    }\n\n    public MqttMsgStore getMqttMsgStore() {\n        return mqttMsgStore;\n    }\n\n    public void setMqttMsgStore(MqttMsgStore mqttMsgStore) {\n        AssertUtils.notNull(mqttMsgStore, \"mqttMsgStore is null\");\n        this.mqttMsgStore = mqttMsgStore;\n    }\n\n    public MqttClientFactory getMqttClientFactory() {\n        return mqttClientFactory;\n    }\n\n    public void setMqttClientFactory(MqttClientFactory mqttClientFactory) {\n        this.mqttClientFactory = mqttClientFactory;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/MqttConnectParameter.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.connector.MqttAuthenticator;\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.constant.MqttVersion;\nimport io.github.netty.mqtt.client.msg.MqttWillMsg;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\n\nimport java.io.File;\nimport java.math.BigDecimal;\nimport java.util.Arrays;\n\n/**\n * MQTT连接配置\n * @author: xzc-coder\n */\npublic class MqttConnectParameter {\n\n    /**\n     * 客户端ID，不能为null\n     */\n    private final String clientId;\n\n    /**\n     * MQTT版本\n     */\n    private MqttVersion mqttVersion = MqttConstant.DEFAULT_MQTT_VERSION;\n\n    /**\n     * 服务器Host，默认为 localhost\n     */\n    private String host = MqttConstant.DEFAULT_HOST;\n\n    /**\n     * 服务器端口，默认为 1883\n     */\n    private int port = MqttConstant.DEFAULT_PORT;\n    /**\n     * 连接认证时用户名\n     */\n    private String username;\n    /**\n     * 连接认证时密码\n     */\n    private char[] password;\n\n    /**\n     * 遗嘱消息\n     */\n    private MqttWillMsg willMsg;\n\n    /**\n     * 重试间隔基本值，第一次会以该间隔重试，然后增加递增值，达到最大时，使用最大值\n     */\n    private long retryIntervalMillis = MqttConstant.DEFAULT_RETRY_INTERVAL_MILLIS;\n\n    /**\n     * 重试间隔递增值，重试失败后增加的间隔值\n     */\n    private long retryIntervalIncreaseMillis = MqttConstant.DEFAULT_MSG_RETRY_INCREASE_MILLS;\n\n    /**\n     * 重试间隔的最大值\n     */\n    private long retryIntervalMaxMillis = MqttConstant.DEFAULT_MSG_RETRY_MAX_MILLS;\n\n    /**\n     * 心跳间隔，默认 30秒，如果设置了自动重连，也是自动重连间隔\n     */\n    private int keepAliveTimeSeconds = MqttConstant.DEFAULT_KEEP_ALIVE_TIME_SECONDS;\n\n    /**\n     * 心跳间隔的系数，默认0.75，执行心跳的定时任务会乘以该系数，因为网络传输有一定的间隔，特别是网络不好的情况，更需要该参数\n     */\n    private BigDecimal keepAliveTimeCoefficient = MqttConstant.DEFAULT_KEEP_ALIVE_TIME_COEFFICIENT;\n\n    /**\n     * 连接超时时间，默认 30秒\n     */\n    private long connectTimeoutSeconds = MqttConstant.DEFAULT_CONNECT_TIMEOUT_SECONDS;\n    /**\n     * 是否自动重连，默认 false\n     */\n    private boolean autoReconnect = MqttConstant.DEFAULT_AUTO_RECONNECT;\n\n    /**\n     * 是否清理会话，默认 true\n     */\n    private boolean cleanSession = MqttConstant.DEFAULT_CLEAR_SESSION;\n\n    /**\n     * ssl，默认 false\n     */\n    private boolean ssl;\n\n    /**\n     * 根证书\n     * 如果服务器是权威CA颁发的证书，则不需要该证书文件;\n     * 如果是自签名的证书，需要给自签名的证书授权，必须填入该证书文件\n     */\n    private File rootCertificateFile;\n    /**\n     * 客户端的私钥文件\n     */\n    private File clientPrivateKeyFile;\n    /**\n     * 客户端的证书文件\n     */\n    private File clientCertificateFile;\n\n    /**\n     * MQTT5\n     * 会话过期间隔 单位 秒\n     */\n    private Integer sessionExpiryIntervalSeconds;\n\n    /**\n     * MQTT5\n     * 认证方法\n     */\n    private String authenticationMethod;\n\n    /**\n     * MQTT5\n     * 数据\n     */\n    private byte[] authenticationData;\n\n    /**\n     * MQTT5\n     * 请求问题信息标识符\n     */\n    private Integer requestProblemInformation = MqttConstant.DEFAULT_REQUEST_PROBLEM_INFORMATION;\n\n    /**\n     * MQTT5\n     * 请求响应标识符\n     */\n    private Integer requestResponseInformation = MqttConstant.DEFAULT_REQUEST_RESPONSE_INFORMATION;\n\n    /**\n     * MQTT5\n     * 响应信息\n     */\n    private String responseInformation;\n\n    /**\n     * MQTT5\n     * 接收最大数量\n     */\n    private Integer receiveMaximum;\n\n    /**\n     * MQTT5\n     * 主题别名最大长度\n     */\n    private Integer topicAliasMaximum;\n\n    /**\n     * MQTT5\n     * 最大报文长度\n     */\n    private Integer maximumPacketSize;\n\n    /**\n     * MQTT5\n     * 用户属性\n     */\n    private final MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n\n    /**\n     * MQTT5\n     * 认证器，当有认证方法时使用\n     */\n    private MqttAuthenticator mqttAuthenticator;\n\n    public MqttConnectParameter(String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        this.clientId = clientId;\n    }\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public int getKeepAliveTimeSeconds() {\n        return keepAliveTimeSeconds;\n    }\n\n    public void setKeepAliveTimeSeconds(int keepAliveTimeSeconds) {\n        if (keepAliveTimeSeconds > 0) {\n            this.keepAliveTimeSeconds = keepAliveTimeSeconds;\n        }\n    }\n\n    public long getConnectTimeoutSeconds() {\n        return connectTimeoutSeconds;\n    }\n\n    public void setConnectTimeoutSeconds(long connectTimeoutSeconds) {\n        if (connectTimeoutSeconds > 0) {\n            this.connectTimeoutSeconds = connectTimeoutSeconds;\n        }\n    }\n\n    public boolean isAutoReconnect() {\n        return autoReconnect;\n    }\n\n    public void setAutoReconnect(boolean autoReconnect) {\n        this.autoReconnect = autoReconnect;\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 char[] getPassword() {\n        return password;\n    }\n\n    public void setPassword(char[] password) {\n        this.password = password;\n    }\n\n    public void setPassword(String password) {\n        if (password != null) {\n            this.password = password.toCharArray();\n        }\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 MqttWillMsg getWillMsg() {\n        return willMsg;\n    }\n\n    public void setWillMsg(MqttWillMsg willMsg) {\n        this.willMsg = willMsg;\n    }\n\n    public boolean hasWill() {\n        return willMsg != null;\n    }\n\n\n    public String getHost() {\n        return host;\n    }\n\n    public void setHost(String host) {\n        AssertUtils.notNull(host, \"host is null\");\n        this.host = host;\n    }\n\n    public int getPort() {\n        return port;\n    }\n\n    public void setPort(int port) {\n        if (port < 0 || port > MqttConstant.MAX_PORT) {\n            throw new IllegalArgumentException(\"port out of range:\" + port);\n        }\n        this.port = port;\n    }\n\n    public boolean isSsl() {\n        return ssl;\n    }\n\n    public void setSsl(boolean ssl) {\n        this.ssl = ssl;\n    }\n\n    public long getRetryIntervalMillis() {\n        return retryIntervalMillis;\n    }\n\n    public void setRetryIntervalMillis(long retryIntervalMillis) {\n        if (retryIntervalMillis > 0) {\n            this.retryIntervalMillis = retryIntervalMillis;\n        }\n    }\n\n    public MqttVersion getMqttVersion() {\n        return mqttVersion;\n    }\n\n    public void setMqttVersion(MqttVersion mqttVersion) {\n        if (mqttVersion != null) {\n            this.mqttVersion = mqttVersion;\n        }\n    }\n\n    public Integer getSessionExpiryIntervalSeconds() {\n        return sessionExpiryIntervalSeconds;\n    }\n\n    public void setSessionExpiryIntervalSeconds(Integer sessionExpiryIntervalSeconds) {\n        this.sessionExpiryIntervalSeconds = sessionExpiryIntervalSeconds;\n    }\n\n    public String getAuthenticationMethod() {\n        return authenticationMethod;\n    }\n\n    public void setAuthenticationMethod(String authenticationMethod) {\n        this.authenticationMethod = authenticationMethod;\n    }\n\n    public byte[] getAuthenticationData() {\n        return authenticationData;\n    }\n\n    public void setAuthenticationData(byte[] authenticationData) {\n        this.authenticationData = authenticationData;\n    }\n\n    public Integer getRequestProblemInformation() {\n        return requestProblemInformation;\n    }\n\n    public void setRequestProblemInformation(Integer requestProblemInformation) {\n        this.requestProblemInformation = requestProblemInformation;\n    }\n\n    public String getResponseInformation() {\n        return responseInformation;\n    }\n\n    public void setResponseInformation(String responseInformation) {\n        this.responseInformation = responseInformation;\n    }\n\n    public Integer getReceiveMaximum() {\n        return receiveMaximum;\n    }\n\n    public void setReceiveMaximum(Integer receiveMaximum) {\n        this.receiveMaximum = receiveMaximum;\n    }\n\n    public Integer getTopicAliasMaximum() {\n        return topicAliasMaximum;\n    }\n\n    public void setTopicAliasMaximum(Integer topicAliasMaximum) {\n        this.topicAliasMaximum = topicAliasMaximum;\n    }\n\n    public Integer getMaximumPacketSize() {\n        return maximumPacketSize;\n    }\n\n    public void setMaximumPacketSize(Integer maximumPacketSize) {\n        this.maximumPacketSize = maximumPacketSize;\n    }\n\n\n    public MqttProperties.UserProperties getMqttUserProperties() {\n        return mqttUserProperties;\n    }\n\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     *\n     * @param key   key\n     * @param value value\n     */\n    public void addMqttUserProperty(String key, String value) {\n        if (key != null && value != null) {\n            mqttUserProperties.add(key, value);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     *\n     * @param stringPair key value对象\n     */\n    public void addMqttUserProperty(MqttProperties.StringPair stringPair) {\n        if (stringPair != null) {\n            mqttUserProperties.add(stringPair);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     *\n     * @param mqttUserProperties MQTT用户属性\n     */\n    private void addMqttUserProperties(MqttProperties.UserProperties mqttUserProperties) {\n        if (mqttUserProperties != null) {\n            for (MqttProperties.StringPair stringPair : mqttUserProperties.value()) {\n                this.mqttUserProperties.add(stringPair);\n            }\n        }\n    }\n\n    public Integer getRequestResponseInformation() {\n        return requestResponseInformation;\n    }\n\n    public void setRequestResponseInformation(Integer requestResponseInformation) {\n        this.requestResponseInformation = requestResponseInformation;\n    }\n\n    public MqttAuthenticator getMqttAuthenticator() {\n        return mqttAuthenticator;\n    }\n\n    public void setMqttAuthenticator(MqttAuthenticator mqttAuthenticator) {\n        this.mqttAuthenticator = mqttAuthenticator;\n    }\n\n    public BigDecimal getKeepAliveTimeCoefficient() {\n        return keepAliveTimeCoefficient;\n    }\n\n    public void setKeepAliveTimeCoefficient(BigDecimal keepAliveTimeCoefficient) {\n        if (keepAliveTimeCoefficient != null && keepAliveTimeCoefficient.compareTo(BigDecimal.ZERO) > 0) {\n            this.keepAliveTimeCoefficient = keepAliveTimeCoefficient;\n        }\n    }\n\n    public long getRetryIntervalIncreaseMillis() {\n        return retryIntervalIncreaseMillis;\n    }\n\n    public void setRetryIntervalIncreaseMillis(long retryIntervalIncreaseMillis) {\n        this.retryIntervalIncreaseMillis = retryIntervalIncreaseMillis;\n    }\n\n    public long getRetryIntervalMaxMillis() {\n        return retryIntervalMaxMillis;\n    }\n\n    public void setRetryIntervalMaxMillis(long retryIntervalMaxMillis) {\n        this.retryIntervalMaxMillis = retryIntervalMaxMillis;\n    }\n\n    public File getRootCertificateFile() {\n        return rootCertificateFile;\n    }\n\n    public void setRootCertificateFile(File rootCertificateFile) {\n        this.rootCertificateFile = rootCertificateFile;\n    }\n\n    public File getClientPrivateKeyFile() {\n        return clientPrivateKeyFile;\n    }\n\n    public void setClientPrivateKeyFile(File clientPrivateKeyFile) {\n        this.clientPrivateKeyFile = clientPrivateKeyFile;\n    }\n\n    public File getClientCertificateFile() {\n        return clientCertificateFile;\n    }\n\n    public void setClientCertificateFile(File clientCertificateFile) {\n        this.clientCertificateFile = clientCertificateFile;\n    }\n\n    @Override\n    public String toString() {\n        return \"MqttConnectParameter{\" +\n                \"clientId='\" + clientId + '\\'' +\n                \", mqttVersion=\" + mqttVersion +\n                \", host='\" + host + '\\'' +\n                \", port=\" + port +\n                \", username='\" + username + '\\'' +\n                \", password=\" + Arrays.toString(password) +\n                \", willMsg=\" + willMsg +\n                \", retryIntervalMillis=\" + retryIntervalMillis +\n                \", retryIntervalIncreaseMillis=\" + retryIntervalIncreaseMillis +\n                \", retryIntervalMaxMillis=\" + retryIntervalMaxMillis +\n                \", keepAliveTimeSeconds=\" + keepAliveTimeSeconds +\n                \", keepAliveTimeCoefficient=\" + keepAliveTimeCoefficient +\n                \", connectTimeoutSeconds=\" + connectTimeoutSeconds +\n                \", autoReconnect=\" + autoReconnect +\n                \", cleanSession=\" + cleanSession +\n                \", ssl=\" + ssl +\n                \", rootCertificateFile=\" + rootCertificateFile +\n                \", clientPrivateKeyFile=\" + clientPrivateKeyFile +\n                \", clientCertificateFile=\" + clientCertificateFile +\n                \", sessionExpiryIntervalSeconds=\" + sessionExpiryIntervalSeconds +\n                \", authenticationMethod='\" + authenticationMethod + '\\'' +\n                \", authenticationData=\" + Arrays.toString(authenticationData) +\n                \", requestProblemInformation=\" + requestProblemInformation +\n                \", requestResponseInformation=\" + requestResponseInformation +\n                \", responseInformation='\" + responseInformation + '\\'' +\n                \", receiveMaximum=\" + receiveMaximum +\n                \", topicAliasMaximum=\" + topicAliasMaximum +\n                \", maximumPacketSize=\" + maximumPacketSize +\n                \", mqttUserProperties=\" + mqttUserProperties +\n                \", mqttAuthenticator=\" + mqttAuthenticator +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttCallback.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\nimport io.github.netty.mqtt.client.MqttConnectParameter;\n\n/**\n * MQTT对调接口\n */\npublic interface MqttCallback {\n\n    /**\n     * 订阅完成回调\n     *\n     * @param mqttSubscribeCallbackResult 订阅结果\n     */\n    default void subscribeCallback(MqttSubscribeCallbackResult mqttSubscribeCallbackResult) {\n\n    }\n\n    /**\n     * 取消订阅完成回调\n     *\n     * @param mqttUnSubscribeCallbackResult 取消订阅结果\n     */\n    default void unsubscribeCallback(MqttUnSubscribeCallbackResult mqttUnSubscribeCallbackResult) {\n\n    }\n\n\n    /**\n     * 当发送的消息，完成时回调\n     *\n     * @param mqttSendCallbackResult 发送消息结果\n     */\n    default void messageSendCallback(MqttSendCallbackResult mqttSendCallbackResult) {\n\n    }\n\n    /**\n     * 接收消息完成时回调\n     *\n     * @param receiveCallbackResult 接收消息结果\n     */\n    default void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n\n    }\n\n    /**\n     * TCP的连接成功时回调\n     *\n     * @param mqttConnectCallbackResult TCP的连接成功结果\n     */\n    default void channelConnectCallback(MqttConnectCallbackResult mqttConnectCallbackResult) {\n\n    }\n\n    /**\n     * MQTT连接完成时回调\n     *\n     * @param mqttConnectCallbackResult 连接完成结果\n     */\n    default void connectCompleteCallback(MqttConnectCallbackResult mqttConnectCallbackResult) {\n\n    }\n\n    /**\n     * 连接丢失时回调\n     *\n     * @param mqttConnectLostCallbackResult 连接丢失结果\n     */\n    default void connectLostCallback(MqttConnectLostCallbackResult mqttConnectLostCallbackResult) {\n\n    }\n\n    /**\n     * 收到心跳响应时回调\n     *\n     * @param mqttHeartbeatCallbackResult 心跳响应结果\n     */\n    default void heartbeatCallback(MqttHeartbeatCallbackResult mqttHeartbeatCallbackResult) {\n\n    }\n\n    /**\n     * Netty的Channel发生异常时回调\n     *\n     * @param mqttConnectParameter               连接时的参数\n     * @param mqttChannelExceptionCallbackResult Channel异常结果\n     */\n    default void channelExceptionCaught(MqttConnectParameter mqttConnectParameter, MqttChannelExceptionCallbackResult mqttChannelExceptionCallbackResult) {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttCallbackResult.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\n\nimport io.github.netty.mqtt.client.constant.MqttVersion;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.MqttUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\n\nimport java.util.List;\n\n/**\n * MQTT回调的结果基类\n * @author: xzc-coder\n */\npublic class MqttCallbackResult {\n\n\n    /**\n     * 客户端ID\n     */\n    protected final String clientId;\n\n    /**\n     * MQTT 版本\n     */\n    protected MqttVersion mqttVersion;\n\n    /**\n     * MQTT5\n     * MQTT属性\n     */\n    protected MqttProperties mqttProperties;\n\n    /**\n     * 创建时间戳\n     */\n    protected final long createTimestamp;\n\n\n    public MqttCallbackResult(String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        this.mqttVersion = MqttVersion.MQTT_3_1_1;\n        this.clientId = clientId;\n        this.createTimestamp = System.currentTimeMillis();\n    }\n\n    public MqttCallbackResult(MqttVersion mqttVersion, String clientId) {\n        AssertUtils.notNull(mqttVersion, \"mqttVersion is null\");\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        this.mqttVersion = mqttVersion;\n        this.clientId = clientId;\n        this.createTimestamp = System.currentTimeMillis();\n    }\n\n    public long getCreateTimestamp() {\n        return createTimestamp;\n    }\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public MqttVersion getMqttVersion() {\n        return mqttVersion;\n    }\n\n    public void setMqttVersion(MqttVersion mqttVersion) {\n        this.mqttVersion = mqttVersion;\n    }\n\n    /**\n     * 获取MQTT属性\n     *\n     * @param mqttPropertyType 属性类型\n     * @return MQTT属性\n     */\n    public MqttProperties.MqttProperty getMqttProperty(MqttProperties.MqttPropertyType mqttPropertyType) {\n        MqttProperties.MqttProperty mqttProperty = null;\n        if (mqttProperties != null && mqttPropertyType != null) {\n            mqttProperty = mqttProperties.getProperty(mqttPropertyType.value());\n        }\n        return mqttProperty;\n    }\n\n    /**\n     * 获取MQTT属性集合\n     *\n     * @param mqttPropertyType 属性类型\n     * @return MQTT属性集合\n     */\n    public List<MqttProperties.MqttProperty> getProperties(MqttProperties.MqttPropertyType mqttPropertyType) {\n        List<MqttProperties.MqttProperty> mqttPropertyList = null;\n        if (mqttProperties != null && mqttPropertyType != null) {\n            mqttPropertyList = (List<MqttProperties.MqttProperty>) mqttProperties.getProperties(mqttPropertyType.value());\n        }\n        return mqttPropertyList;\n    }\n\n    /**\n     * 获取MQTT用户属性\n     *\n     * @return MQTT用户属性\n     */\n    public MqttProperties.UserProperties getUserProperties() {\n        MqttProperties.UserProperties userProperties = null;\n        if (mqttProperties != null) {\n            userProperties = (MqttProperties.UserProperties) mqttProperties.getProperty(MqttProperties.MqttPropertyType.USER_PROPERTY.value());\n        }\n        return userProperties;\n    }\n\n    /**\n     * 获取MQTT用户属性中的某个Key的值\n     *\n     * @param key key\n     * @return Key对应的值\n     */\n    public String getUserMqttPropertyValue(String key) {\n        return MqttUtils.getUserMqttPropertyValue(mqttProperties, key);\n    }\n\n    /**\n     * 获取MQTT用户属性中的某个Key的值集合\n     *\n     * @param key key\n     * @return Key对应的值的集合\n     */\n    public List<String> getUserMqttPropertyValues(String key) {\n        return MqttUtils.getUserMqttPropertyValues(mqttProperties, key);\n    }\n\n    /**\n     * 获取MQTT用户属性中的某个Key的值\n     *\n     * @param key          key\n     * @param defaultValue 获取不到时的默认值\n     * @return Key对应的值\n     */\n\n    public String getUserMqttPropertyValue(String key, String defaultValue) {\n        return MqttUtils.getUserMqttPropertyValue(mqttProperties, key, defaultValue);\n    }\n\n    /**\n     * 获取MQTT属性中的二进制值\n     *\n     * @param mqttPropertyType MQTT属性类型\n     * @return 二进制\n     */\n    public byte[] getBinaryMqttPropertyValue(MqttProperties.MqttPropertyType mqttPropertyType) {\n        return MqttUtils.getBinaryMqttPropertyValue(mqttProperties, mqttPropertyType);\n    }\n\n    /**\n     * 获取MQTT属性中的字符串值\n     *\n     * @param mqttPropertyType MQTT属性类型\n     * @return 字符串\n     */\n    public String getStringMqttPropertyValue(MqttProperties.MqttPropertyType mqttPropertyType) {\n        return MqttUtils.getStringMqttPropertyValue(mqttProperties, mqttPropertyType);\n    }\n\n\n    /**\n     * 获取MQTT属性中的整型值\n     *\n     * @param mqttPropertyType MQTT属性类型\n     * @return 整型\n     */\n    public Integer getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType mqttPropertyType) {\n        return MqttUtils.getIntegerMqttPropertyValue(mqttProperties, mqttPropertyType);\n    }\n\n    /**\n     * 获取MQTT属性\n     *\n     * @return MQTT属性\n     */\n    public MqttProperties getMqttProperties() {\n        return mqttProperties;\n    }\n\n    public void setMqttProperties(MqttProperties mqttProperties) {\n        this.mqttProperties = mqttProperties;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"MqttCallbackResult{\" +\n                \"clientId='\" + clientId + '\\'' +\n                \", mqttVersion=\" + mqttVersion +\n                \", mqttProperties=\" + mqttProperties +\n                \", createTimestamp=\" + createTimestamp +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttChannelExceptionCallbackResult.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\nimport io.github.netty.mqtt.client.constant.MqttAuthState;\n\n/**\n * MQTT异常回调结果\n * @author: xzc-coder\n */\npublic class MqttChannelExceptionCallbackResult extends MqttCallbackResult {\n\n    /**\n     * MQTT认证状态\n     */\n    private final MqttAuthState authState;\n    /**\n     * 异常原因\n     */\n    private final Throwable cause;\n\n    public MqttChannelExceptionCallbackResult(String clientId,MqttAuthState authState,Throwable cause) {\n        super(clientId);\n        this.authState = authState;\n        this.cause = cause;\n    }\n\n\n    /**\n     * 获取认证状态\n     * @return 用于判断当前MQTT的连接状态\n     */\n    public MqttAuthState getAuthState() {\n        return authState;\n    }\n\n    /**\n     * 获取异常原因\n     * @return 异常原因\n     */\n    public Throwable getCause() {\n        return cause;\n    }\n\n    @Override\n    public String toString() {\n        return \"MqttChannelExceptionCallbackResult{\" +\n                \"authState=\" + authState +\n                \", cause=\" + cause +\n                \"} \" + super.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttConnectCallbackResult.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\nimport io.github.netty.mqtt.client.constant.MqttAuthState;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\n\n/**\n * MQTT连接回调结果\n * @author: xzc-coder\n */\npublic class MqttConnectCallbackResult extends MqttCallbackResult {\n\n\n    /**\n     * MQTT认证状态\n     */\n    private final MqttAuthState mqttAuthState;\n    /**\n     * 连接异常\n     */\n    private final Throwable cause;\n\n    /**\n     * 连接异常原因码，成功则不返回\n     */\n    private final Byte connectReturnCode;\n\n    /**\n     * Broker 是否延续之前的会话，连接失败时为null\n     */\n    private Boolean sessionPresent;\n\n    public MqttConnectCallbackResult(String clientId, MqttAuthState mqttAuthState) {\n        this(clientId, mqttAuthState, null);\n    }\n\n    public MqttConnectCallbackResult(String clientId, MqttAuthState mqttAuthState, Boolean sessionPresent) {\n        this(clientId, mqttAuthState, sessionPresent, null, null);\n    }\n\n\n    public MqttConnectCallbackResult(String clientId, MqttAuthState mqttAuthState, Throwable cause, Byte connectReturnCode) {\n        this(clientId, mqttAuthState, null, cause, connectReturnCode);\n    }\n\n    public MqttConnectCallbackResult(String clientId, MqttAuthState mqttAuthState, Boolean sessionPresent, Throwable cause, Byte connectReturnCode) {\n        super(clientId);\n        AssertUtils.notNull(mqttAuthState, \"mqttAuthState is null\");\n        this.mqttAuthState = mqttAuthState;\n        this.sessionPresent = sessionPresent;\n        this.cause = cause;\n        this.connectReturnCode = connectReturnCode;\n    }\n\n\n    public MqttAuthState getMqttAuthState() {\n        return mqttAuthState;\n    }\n\n    public Throwable getCause() {\n        return cause;\n    }\n\n    public Byte getConnectReturnCode() {\n        return connectReturnCode;\n    }\n\n    public Boolean getSessionPresent() {\n        return sessionPresent;\n    }\n\n    public void setSessionPresent(Boolean sessionPresent) {\n        this.sessionPresent = sessionPresent;\n    }\n\n    /**\n     * MQTT5\n     * 获取消息过期时间（秒）\n     *\n     * @return 消息过期时间\n     */\n    public Integer getSessionExpiryIntervalSeconds() {\n        Integer sessionExpiryIntervalSeconds = null;\n        if (mqttProperties != null) {\n            sessionExpiryIntervalSeconds = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.SESSION_EXPIRY_INTERVAL);\n        }\n        return sessionExpiryIntervalSeconds;\n    }\n\n    /**\n     * MQTT5\n     * 获取接受最大值\n     *\n     * @return 接受最大值\n     */\n    public Integer getReceiveMaximum() {\n        Integer receiveMaximum = null;\n        if (mqttProperties != null) {\n            receiveMaximum = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.RECEIVE_MAXIMUM);\n        }\n        return receiveMaximum;\n    }\n\n    /**\n     * MQTT5\n     * 获取最大服务质量\n     *\n     * @return 最大服务质量\n     */\n    public Integer getMaximumQos() {\n        Integer maximumQos = null;\n        if (mqttProperties != null) {\n            maximumQos = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.MAXIMUM_QOS);\n        }\n        return maximumQos;\n    }\n\n    /**\n     * MQTT5\n     * 获取保留可用标识符\n     *\n     * @return 保留可用标识符\n     */\n    public Integer getRetainAvailable() {\n        Integer retainAvailable = null;\n        if (mqttProperties != null) {\n            retainAvailable = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.RETAIN_AVAILABLE);\n        }\n        return retainAvailable;\n    }\n\n    /**\n     * MQTT5\n     * 获取最大报文长度\n     *\n     * @return 最大报文长度\n     */\n    public Integer getMaximumPacketSize() {\n        Integer maximumPacketSize = null;\n        if (mqttProperties != null) {\n            maximumPacketSize = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.MAXIMUM_PACKET_SIZE);\n        }\n        return maximumPacketSize;\n    }\n\n    /**\n     * MQTT5\n     * 获取分配的客户端标识符\n     *\n     * @return 客户端标识符\n     */\n    public String getAssignedClientIdentifier() {\n        String assignedClientIdentifier = null;\n        if (mqttProperties != null) {\n            assignedClientIdentifier = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.ASSIGNED_CLIENT_IDENTIFIER);\n        }\n        return assignedClientIdentifier;\n    }\n\n    /**\n     * MQTT5\n     * 获取主题别名最大值\n     *\n     * @return 主题别名最大值\n     */\n    public Integer getTopicAliasMaximum() {\n        Integer topicAliasMaximum = null;\n        if (mqttProperties != null) {\n            topicAliasMaximum = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.TOPIC_ALIAS_MAXIMUM);\n        }\n        return topicAliasMaximum;\n    }\n\n    /**\n     * MQTT5\n     * 获取原因字符串\n     *\n     * @return 原因字符串\n     */\n    public String getReasonString() {\n        String reasonString = null;\n        if (mqttProperties != null) {\n            reasonString = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.REASON_STRING);\n        }\n        return reasonString;\n    }\n\n    /**\n     * MQTT5\n     * 获取通配符订阅可用标识符\n     *\n     * @return 通配符订阅可用标识符\n     */\n    public Integer getWildcardSubscriptionAvailable() {\n        Integer wildcardSubscriptionAvailable = null;\n        if (mqttProperties != null) {\n            wildcardSubscriptionAvailable = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.WILDCARD_SUBSCRIPTION_AVAILABLE);\n        }\n        return wildcardSubscriptionAvailable;\n    }\n\n    /**\n     * MQTT5\n     * 获取订阅标识符可用标识符\n     *\n     * @return 订阅标识符可用标识符\n     */\n    public Integer getSubscriptionIdentifierAvailable() {\n        Integer subscriptionIdentifierAvailable = null;\n        if (mqttProperties != null) {\n            subscriptionIdentifierAvailable = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.SUBSCRIPTION_IDENTIFIER_AVAILABLE);\n        }\n        return subscriptionIdentifierAvailable;\n    }\n\n    /**\n     * MQTT5\n     * 获取共享订阅可用标识符\n     *\n     * @return 共享订阅可用标识符\n     */\n    public Integer getSharedSubscriptionAvailable() {\n        Integer sharedSubscriptionAvailable = null;\n        if (mqttProperties != null) {\n            sharedSubscriptionAvailable = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.SHARED_SUBSCRIPTION_AVAILABLE);\n        }\n        return sharedSubscriptionAvailable;\n    }\n\n    /**\n     * MQTT5\n     * 获取服务端保持连接时间间隔（秒）\n     *\n     * @return 服务端保持连接时间间隔（秒）\n     */\n    public Integer getServerKeepAliveSeconds() {\n        Integer serverKeepAlive = null;\n        if (mqttProperties != null) {\n            serverKeepAlive = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.SERVER_KEEP_ALIVE);\n        }\n        return serverKeepAlive;\n    }\n\n    /**\n     * MQTT5\n     * 获取响应消息标识符\n     *\n     * @return 响应消息标识符\n     */\n    public String getResponseInformation() {\n        String responseInformation = null;\n        if (mqttProperties != null) {\n            responseInformation = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.RESPONSE_INFORMATION);\n        }\n        return responseInformation;\n    }\n\n    /**\n     * MQTT5\n     * 获取服务端参考\n     *\n     * @return 服务端参考\n     */\n    public String getServerReference() {\n        String serverReference = null;\n        if (mqttProperties != null) {\n            serverReference = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.SERVER_REFERENCE);\n        }\n        return serverReference;\n    }\n\n    /**\n     * MQTT5\n     * 获取认证方法\n     *\n     * @return 认证方法\n     */\n    public String getAuthenticationMethod() {\n        String authenticationMethod = null;\n        if (mqttProperties != null) {\n            authenticationMethod = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.AUTHENTICATION_METHOD);\n        }\n        return authenticationMethod;\n    }\n\n    /**\n     * MQTT5\n     * 获取认证数据\n     *\n     * @return 认证数据\n     */\n    public byte[] getAuthenticationData() {\n        byte[] authenticationData = null;\n        if (mqttProperties != null) {\n            authenticationData = getBinaryMqttPropertyValue(MqttProperties.MqttPropertyType.AUTHENTICATION_DATA);\n        }\n        return authenticationData;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"MqttConnectCallbackResult{\" +\n                \"mqttAuthState=\" + mqttAuthState +\n                \", cause=\" + cause +\n                \", connectReturnCode=\" + connectReturnCode +\n                \", sessionPresent=\" + sessionPresent +\n                \"} \" + super.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttConnectLostCallbackResult.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\nimport io.github.netty.mqtt.client.constant.MqttAuthState;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\n\n/**\n * MQTT连接丢失回调结果\n * @author: xzc-coder\n */\npublic class MqttConnectLostCallbackResult extends MqttCallbackResult {\n\n    /**\n     * MQTT认证状态\n     */\n    private final MqttAuthState mqttAuthState;\n\n    /**\n     * MQTT5\n     * 原因码，正常断开则为0x00，为null或其他值为异常断开\n     */\n    private Byte reasonCode;\n\n\n    public MqttConnectLostCallbackResult(String clientId, MqttAuthState mqttAuthState) {\n        super(clientId);\n        AssertUtils.notNull(mqttAuthState, \"mqttAuthState is null\");\n        this.mqttAuthState = mqttAuthState;\n    }\n\n\n    public Byte getReasonCode() {\n        return reasonCode;\n    }\n\n    public void setReasonCode(Byte reasonCode) {\n        this.reasonCode = reasonCode;\n    }\n\n    public MqttAuthState getMqttAuthState() {\n        return mqttAuthState;\n    }\n\n    /**\n     * MQTT5\n     * 获取原因字符串\n     *\n     * @return 原因字符串\n     */\n    public String getReasonString() {\n        String reasonString = null;\n        if (mqttProperties != null) {\n            reasonString = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.REASON_STRING);\n        }\n        return reasonString;\n    }\n\n    /**\n     * MQTT5\n     * 获取服务端参考\n     *\n     * @return 服务端参考\n     */\n    public String getServerReference() {\n        String serverReference = null;\n        if (mqttProperties != null) {\n            serverReference = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.SERVER_REFERENCE);\n        }\n        return serverReference;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"MqttConnectLostCallbackResult{\" +\n                \"mqttAuthState=\" + mqttAuthState +\n                \", reasonCode=\" + reasonCode +\n                \"} \" + super.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttHeartbeatCallbackResult.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\n\n/**\n * MQTT心跳回调结果\n * @author: xzc-coder\n */\npublic class MqttHeartbeatCallbackResult extends MqttCallbackResult {\n\n\n\n    public MqttHeartbeatCallbackResult(String clientId) {\n        super(clientId);\n    }\n\n\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttReceiveCallbackResult.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\nimport io.github.netty.mqtt.client.constant.MqttMsgState;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\n\nimport java.util.Arrays;\n\n/**\n * MQTT接受到消息回调结果\n * @author: xzc-coder\n */\npublic class MqttReceiveCallbackResult extends MqttCallbackResult {\n\n    /**\n     * 接收到的MQTT消息\n     */\n    private final MqttMsg mqttMsg;\n\n    /**\n     * 消息ID\n     */\n    private final Integer msgId;\n    /**\n     * 主题\n     */\n    private final String topic;\n    /**\n     * qos\n     */\n    private final MqttQoS qos;\n    /**\n     * 是否保存\n     */\n    private final boolean retain;\n    /**\n     * 载荷\n     */\n    private final byte[] payload;\n\n    /**\n     * 是否重复发送\n     */\n    private boolean dup;\n\n    /**\n     * 消息状态\n     */\n    private MqttMsgState msgState;\n\n    /**\n     * 原因码\n     */\n    private Byte reasonCode;\n\n\n    public MqttReceiveCallbackResult(String clientId, MqttMsg mqttMsg) {\n        super(clientId);\n        AssertUtils.notNull(mqttMsg, \"mqttMsg is null\");\n        this.mqttMsg = mqttMsg;\n        this.msgId = mqttMsg.getMsgId();\n        this.payload = mqttMsg.getPayload();\n        this.topic = mqttMsg.getTopic();\n        this.qos = mqttMsg.getQos();\n        this.retain = mqttMsg.isRetain();\n        this.dup = mqttMsg.isDup();\n        this.msgState = mqttMsg.getMsgState();\n        this.mqttProperties = mqttMsg.getMqttProperties();\n        this.reasonCode = mqttMsg.getReasonCode();\n    }\n\n    @Deprecated\n    public MqttMsg getMqttMsg() {\n        return mqttMsg;\n    }\n\n    public Integer getMsgId() {\n        return msgId;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public MqttQoS getQos() {\n        return qos;\n    }\n\n    public boolean isRetain() {\n        return retain;\n    }\n\n    public byte[] getPayload() {\n        return payload;\n    }\n\n    public boolean isDup() {\n        return dup;\n    }\n\n    public void setDup(boolean dup) {\n        this.dup = dup;\n    }\n\n    public MqttMsgState getMsgState() {\n        return msgState;\n    }\n\n    public void setMsgState(MqttMsgState msgState) {\n        this.msgState = msgState;\n    }\n\n    /**\n     * MQTT5\n     * 获取原因字符串\n     *\n     * @return 原因字符串\n     */\n    public String getReasonString() {\n        String reasonString = null;\n        if (mqttProperties != null) {\n            reasonString = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.REASON_STRING);\n        }\n        return reasonString;\n    }\n\n    /**\n     * MQTT5\n     * 获取载荷标识符\n     *\n     * @return 载荷标识符\n     */\n    public Integer getPayloadFormatIndicator() {\n        Integer payloadFormatIndicator = null;\n        if (mqttProperties != null) {\n            payloadFormatIndicator = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.PAYLOAD_FORMAT_INDICATOR);\n        }\n        return payloadFormatIndicator;\n    }\n\n    /**\n     * MQTT5\n     * 获取响应主题\n     *\n     * @return 响应主题\n     */\n    public String getResponseTopic() {\n        String responseTopic = null;\n        if (mqttProperties != null) {\n            responseTopic = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.RESPONSE_TOPIC);\n        }\n        return responseTopic;\n\n    }\n\n    /**\n     * MQTT5\n     * 获取对比数据\n     *\n     * @return 对比数据\n     */\n    public byte[] getCorrelationData() {\n        byte[] correlationData = null;\n        if (mqttProperties != null) {\n            correlationData = getBinaryMqttPropertyValue(MqttProperties.MqttPropertyType.CORRELATION_DATA);\n        }\n        return correlationData;\n    }\n\n    /**\n     * MQTT5\n     * 获取订阅标识符\n     *\n     * @return 订阅标识符\n     */\n    public Integer getSubscriptionIdentifier() {\n        Integer subscriptionIdentifier = null;\n        if (mqttProperties != null) {\n            subscriptionIdentifier = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.SUBSCRIPTION_IDENTIFIER);\n        }\n        return subscriptionIdentifier;\n    }\n\n    /**\n     * MQTT5\n     * 获取内容类型\n     *\n     * @return 内容类型\n     */\n    public String getContentType() {\n        String contentType = null;\n        if (mqttProperties != null) {\n            contentType = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.CONTENT_TYPE);\n        }\n        return contentType;\n    }\n\n\n    public Byte getReasonCode() {\n        return reasonCode;\n    }\n\n    public void setReasonCode(Byte reasonCode) {\n        this.reasonCode = reasonCode;\n    }\n\n    @Override\n    public String toString() {\n        return \"MqttReceiveCallbackResult{\" +\n                \", msgId=\" + msgId +\n                \", topic='\" + topic + '\\'' +\n                \", qos=\" + qos +\n                \", retain=\" + retain +\n                \", payload=\" + Arrays.toString(payload) +\n                \", dup=\" + dup +\n                \", msgState=\" + msgState +\n                \", reasonCode=\" + reasonCode +\n                \"} \" + super.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttSendCallbackResult.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\nimport io.github.netty.mqtt.client.constant.MqttMsgState;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\n\nimport java.util.Arrays;\n\n/**\n * MQTT消息发送完成回调结果\n * @author: xzc-coder\n */\npublic class MqttSendCallbackResult extends MqttCallbackResult {\n\n    /**\n     * 发送时的MQTT消息\n     */\n    private final MqttMsg mqttMsg;\n\n    /**\n     * 消息ID\n     */\n    private final Integer msgId;\n    /**\n     * 主题\n     */\n    private final String topic;\n    /**\n     * qos\n     */\n    private final MqttQoS qos;\n    /**\n     * 是否保存\n     */\n    private final boolean retain;\n    /**\n     * 载荷\n     */\n    private final byte[] payload;\n\n    /**\n     * 是否重复发送\n     */\n    private boolean dup;\n\n    /**\n     * 消息状态\n     */\n    private MqttMsgState msgState;\n\n    /**\n     * 原因码\n     */\n    private Byte reasonCode;\n\n    public MqttSendCallbackResult(String clientId, MqttMsg mqttMsg) {\n        super(clientId);\n        AssertUtils.notNull(mqttMsg, \"mqttMsg is null\");\n        this.mqttMsg = mqttMsg;\n        this.msgId = mqttMsg.getMsgId();\n        this.payload = mqttMsg.getPayload();\n        this.topic = mqttMsg.getTopic();\n        this.qos = mqttMsg.getQos();\n        this.retain = mqttMsg.isRetain();\n        this.dup = mqttMsg.isDup();\n        this.msgState = mqttMsg.getMsgState();\n        this.mqttProperties = mqttMsg.getMqttProperties();\n        this.reasonCode = mqttMsg.getReasonCode();\n    }\n\n    @Deprecated\n    public MqttMsg getMqttMsg() {\n        return mqttMsg;\n    }\n\n\n    public Integer getMsgId() {\n        return msgId;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public MqttQoS getQos() {\n        return qos;\n    }\n\n    public boolean isRetain() {\n        return retain;\n    }\n\n    public byte[] getPayload() {\n        return payload;\n    }\n\n    public boolean isDup() {\n        return dup;\n    }\n\n    public void setDup(boolean dup) {\n        this.dup = dup;\n    }\n\n    public MqttMsgState getMsgState() {\n        return msgState;\n    }\n\n    public void setMsgState(MqttMsgState msgState) {\n        this.msgState = msgState;\n    }\n\n    /**\n     * MQTT5\n     * 获取原因字符串\n     *\n     * @return 原因字符串\n     */\n    public String getReasonString() {\n        String reasonString = null;\n        if (mqttProperties != null) {\n            reasonString = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.REASON_STRING);\n        }\n        return reasonString;\n    }\n\n    /**\n     * MQTT5\n     * 获取载荷标识符\n     *\n     * @return 载荷标识符\n     */\n    public Integer getPayloadFormatIndicator() {\n        Integer payloadFormatIndicator = null;\n        if (mqttProperties != null) {\n            payloadFormatIndicator = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.PAYLOAD_FORMAT_INDICATOR);\n        }\n        return payloadFormatIndicator;\n    }\n\n    /**\n     * MQTT5\n     * 获取消息过期间隔（秒）\n     *\n     * @return 消息过期间隔（秒）\n     */\n    public Integer getMessageExpiryIntervalSeconds() {\n        Integer messageExpiryIntervalSeconds = null;\n        if (mqttProperties != null) {\n            messageExpiryIntervalSeconds = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.PUBLICATION_EXPIRY_INTERVAL);\n        }\n        return messageExpiryIntervalSeconds;\n    }\n\n    /**\n     * MQTT5\n     * 获取主题别名\n     *\n     * @return 主题别名\n     */\n    public Integer getTopicAlias() {\n        Integer topicAlias = null;\n        if (mqttProperties != null) {\n            topicAlias = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.TOPIC_ALIAS);\n        }\n        return topicAlias;\n    }\n\n\n    /**\n     * MQTT5\n     * 获取响应主题\n     *\n     * @return 响应主题\n     */\n    public String getResponseTopic() {\n        String responseTopic = null;\n        if (mqttProperties != null) {\n            responseTopic = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.RESPONSE_TOPIC);\n        }\n        return responseTopic;\n\n    }\n\n    /**\n     * MQTT5\n     * 获取对比数据\n     *\n     * @return 对比数据\n     */\n    public byte[] getCorrelationData() {\n        byte[] correlationData = null;\n        if (mqttProperties != null) {\n            correlationData = getBinaryMqttPropertyValue(MqttProperties.MqttPropertyType.CORRELATION_DATA);\n        }\n        return correlationData;\n    }\n\n    /**\n     * MQTT5\n     * 获取订阅标识符\n     *\n     * @return 订阅标识符\n     */\n    public Integer getSubscriptionIdentifier() {\n        Integer subscriptionIdentifier = null;\n        if (mqttProperties != null) {\n            subscriptionIdentifier = getIntegerMqttPropertyValue(MqttProperties.MqttPropertyType.SUBSCRIPTION_IDENTIFIER);\n        }\n        return subscriptionIdentifier;\n    }\n\n    /**\n     * MQTT5\n     * 获取内容类型\n     *\n     * @return 内容类型\n     */\n    public String getContentType() {\n        String contentType = null;\n        if (mqttProperties != null) {\n            contentType = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.CONTENT_TYPE);\n        }\n        return contentType;\n    }\n\n\n    public Byte getReasonCode() {\n        return reasonCode;\n    }\n\n    public void setReasonCode(Byte reasonCode) {\n        this.reasonCode = reasonCode;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"MqttSendCallbackResult{\" +\n                \"mqttMsg=\" + mqttMsg +\n                \", msgId=\" + msgId +\n                \", topic='\" + topic + '\\'' +\n                \", qos=\" + qos +\n                \", retain=\" + retain +\n                \", payload=\" + Arrays.toString(payload) +\n                \", dup=\" + dup +\n                \", msgState=\" + msgState +\n                \", reasonCode=\" + reasonCode +\n                \"} \" + super.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttSubscribeCallbackInfo.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\nimport io.netty.handler.codec.mqtt.MqttQoS;\n\n/**\n * MQTT订阅回调结果项\n * @author: xzc-coder\n */\npublic class MqttSubscribeCallbackInfo {\n\n    /**\n     * 服务器返回的qos\n     */\n    private MqttQoS serverQos;\n    /**\n     * 该条主题是否订阅成功\n     */\n    private boolean isSubscribed;\n\n    /**\n     * 订阅时的qos\n     */\n    private MqttQoS subscribeQos;\n    /**\n     * 订阅时的主题\n     */\n    private String subscribeTopic;\n\n    public MqttQoS getServerQos() {\n        return serverQos;\n    }\n\n    public void setServerQos(MqttQoS serverQos) {\n        this.serverQos = serverQos;\n    }\n\n    public boolean isSubscribed() {\n        return isSubscribed;\n    }\n\n    public void setSubscribed(boolean subscribed) {\n        isSubscribed = subscribed;\n    }\n\n    public MqttQoS getSubscribeQos() {\n        return subscribeQos;\n    }\n\n    public void setSubscribeQos(MqttQoS subscribeQos) {\n        this.subscribeQos = subscribeQos;\n    }\n\n    public String getSubscribeTopic() {\n        return subscribeTopic;\n    }\n\n    public void setSubscribeTopic(String subscribeTopic) {\n        this.subscribeTopic = subscribeTopic;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"MqttSubscribeCallbackInfo{\" +\n                \"serverQos=\" + serverQos +\n                \", isSubscribed=\" + isSubscribed +\n                \", subscribeQos=\" + subscribeQos +\n                \", subscribeTopic='\" + subscribeTopic + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttSubscribeCallbackResult.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * MQTT订阅回调结果\n * @author: xzc-coder\n */\npublic class MqttSubscribeCallbackResult extends MqttCallbackResult {\n\n    /**\n     * 订阅回调消息集合\n     */\n    private final List<MqttSubscribeCallbackInfo> subscribeCallbackInfoList = new ArrayList<>();\n    /**\n     * 消息ID\n     */\n    private final int msgId;\n\n    public MqttSubscribeCallbackResult(String clientId,int msgId,List<MqttSubscribeCallbackInfo> subscribeCallbackInfoList) {\n        super(clientId);\n        AssertUtils.notNull(subscribeCallbackInfoList,\"subscribeCallbackInfoList is null\");\n        this.subscribeCallbackInfoList.addAll(subscribeCallbackInfoList);\n        this.msgId = msgId;\n    }\n\n    public List<MqttSubscribeCallbackInfo> getSubscribeCallbackInfoList() {\n        return subscribeCallbackInfoList;\n    }\n\n    public int getMsgId() {\n        return msgId;\n    }\n\n    /**\n     * MQTT5\n     * 获取原因字符串\n     * @return 原因字符串\n     */\n    public String getReasonString() {\n        String reasonString = null;\n        if(mqttProperties != null) {\n            reasonString = getStringMqttPropertyValue(MqttProperties.MqttPropertyType.REASON_STRING);\n        }\n        return reasonString;\n    }\n\n    @Override\n    public String toString() {\n        return \"MqttSubscribeCallbackResult{\" +\n                \"subscribeCallbackInfoList=\" + subscribeCallbackInfoList +\n                \", msgId=\" + msgId +\n                \"} \" + super.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttUnSubscribeCallbackInfo.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\n\nimport io.github.netty.mqtt.client.constant.MqttConstant;\n\n/**\n * MQTT取消订阅回调结果项\n * @author: xzc-coder\n */\npublic class MqttUnSubscribeCallbackInfo {\n\n    /**\n     * 该条主题是否取消订阅成功\n     */\n    private final boolean isUnSubscribed;\n    /**\n     * 取消订阅失败时，原因码\n     */\n    private final Short reasonCode;\n\n    /**\n     * 主题\n     */\n    private final String topic;\n\n    public MqttUnSubscribeCallbackInfo(boolean isUnSubscribed, String topic) {\n        this(isUnSubscribed, MqttConstant.UNSUBSCRIPTION_SUCCESS_REASON_CODE,topic);\n    }\n\n    public MqttUnSubscribeCallbackInfo(boolean isUnSubscribed, Short reasonCode, String topic) {\n        this.isUnSubscribed = isUnSubscribed;\n        this.reasonCode = reasonCode;\n        this.topic = topic;\n    }\n\n    public boolean isUnSubscribed() {\n        return isUnSubscribed;\n    }\n\n    public Short getReasonCode() {\n        return reasonCode;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/callback/MqttUnSubscribeCallbackResult.java",
    "content": "package io.github.netty.mqtt.client.callback;\n\n\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * MQTT取消订阅回调结果\n * @author: xzc-coder\n */\npublic class MqttUnSubscribeCallbackResult extends MqttCallbackResult {\n\n    /**\n     * 消息ID\n     */\n    private final int msgId;\n    /**\n     * 取消订阅的主题集合\n     */\n    private final List<String> topicList = new ArrayList<>();\n    /**\n     * MQTT5\n     * 原因码集合\n     */\n    private final List<Short> unsubscribeReasonCodeList = new ArrayList<>();\n\n    /**\n     * 取消订阅信息集合\n     */\n    private final List<MqttUnSubscribeCallbackInfo> unsubscribeInfoList = new ArrayList<>();\n\n    public MqttUnSubscribeCallbackResult(String clientId, int msgId, List<String> topicList) {\n        this(clientId,msgId,topicList,null);\n    }\n\n    public MqttUnSubscribeCallbackResult(String clientId, int msgId, List<String> topicList,List<Short> unsubscribeReasonCodeList) {\n        super(clientId);\n        AssertUtils.notNull(topicList, \"topicList is null\");\n        this.msgId = msgId;\n        this.topicList.addAll(topicList);\n        if(EmptyUtils.isEmpty(unsubscribeReasonCodeList)) {\n            for(int i = 0; i < topicList.size(); i++) {\n                unsubscribeReasonCodeList.add(MqttConstant.UNSUBSCRIPTION_SUCCESS_REASON_CODE);\n            }\n        }else {\n            this.unsubscribeReasonCodeList.addAll(unsubscribeReasonCodeList);\n        }\n        for(int i = 0; i < topicList.size(); i++) {\n            short reasonCode = unsubscribeReasonCodeList.get(i);\n            String topic = topicList.get(i);\n            MqttUnSubscribeCallbackInfo unSubscribeCallbackInfo;\n            if(MqttConstant.UNSUBSCRIPTION_SUCCESS_REASON_CODE == reasonCode) {\n                unSubscribeCallbackInfo = new MqttUnSubscribeCallbackInfo(true,topic);\n            }else {\n                unSubscribeCallbackInfo = new MqttUnSubscribeCallbackInfo(false,reasonCode,topic);\n            }\n            this.unsubscribeInfoList.add(unSubscribeCallbackInfo);\n        }\n    }\n\n    public int getMsgId() {\n        return msgId;\n    }\n\n    public List<String> getTopicList() {\n        return topicList;\n    }\n\n    public List<Short> getUnsubscribeReasonCodeList() {\n        return unsubscribeReasonCodeList;\n    }\n\n    public List<MqttUnSubscribeCallbackInfo> getUnsubscribeInfoList() {\n        return unsubscribeInfoList;\n    }\n\n    @Override\n    public String toString() {\n        return \"MqttUnSubscribeCallbackResult{\" +\n                \"msgId=\" + msgId +\n                \", topicList=\" + topicList +\n                \", unsubscribeReasonCodeList=\" + unsubscribeReasonCodeList +\n                \", unsubscribeInfoList=\" + unsubscribeInfoList +\n                \"} \" + super.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/connector/AbstractMqttConnector.java",
    "content": "package io.github.netty.mqtt.client.connector;\n\nimport io.github.netty.mqtt.client.MqttConfiguration;\nimport io.github.netty.mqtt.client.MqttConnectParameter;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.channel.*;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslContextBuilder;\nimport io.netty.handler.ssl.SslHandler;\n\nimport javax.net.ssl.SSLException;\nimport java.io.File;\nimport java.util.Iterator;\nimport java.util.Map;\n\n/**\n * 抽象的MQTT连接器\n * @author: xzc-coder\n */\npublic abstract class AbstractMqttConnector implements MqttConnector {\n\n    protected final MqttConfiguration configuration;\n    protected final MqttConnectParameter mqttConnectParameter;\n    protected final MqttDelegateHandler mqttDelegateHandler;\n\n    public AbstractMqttConnector(MqttConfiguration configuration, MqttConnectParameter mqttConnectParameter, Object... handlerCreateArgs) {\n        AssertUtils.notNull(configuration, \"configuration is null\");\n        AssertUtils.notNull(mqttConnectParameter, \"mqttConnectParameter is null\");\n        this.configuration = configuration;\n        this.mqttConnectParameter = mqttConnectParameter;\n        this.mqttDelegateHandler = createDelegateHandle(handlerCreateArgs);\n    }\n\n    /**\n     * 创建一个MQTT委托处理器，子类实现\n     *\n     * @param handlerCreateArgs 创建的参数\n     * @return MqttDelegateHandler\n     */\n    protected abstract MqttDelegateHandler createDelegateHandle(Object... handlerCreateArgs);\n\n    /**\n     * 添加Netty的TCP参数\n     *\n     * @param bootstrap 启动器\n     * @param optionMap 参数Map\n     */\n    protected void addOptions(Bootstrap bootstrap, Map<ChannelOption, Object> optionMap) {\n        Iterator<Map.Entry<ChannelOption, Object>> optionIterator = optionMap.entrySet().iterator();\n        while (optionIterator.hasNext()) {\n            Map.Entry<ChannelOption, Object> option = optionIterator.next();\n            bootstrap.option(option.getKey(), option.getValue());\n        }\n    }\n\n\n    @Override\n    public MqttDelegateHandler getMqttDelegateHandler() {\n        return this.mqttDelegateHandler;\n    }\n\n    @Override\n    public MqttConfiguration getMqttConfiguration() {\n        return this.configuration;\n    }\n\n    /**\n     * SSL处理\n     *\n     * @param allocator 内存分配器\n     * @return Ssl处理器\n     * @throws SSLException ssl异常\n     */\n    protected SslHandler getSslHandler(ByteBufAllocator allocator) throws SSLException {\n        boolean singleSsl = true;\n        File clientCertificateFile = mqttConnectParameter.getClientCertificateFile();\n        File clientPrivateKeyFile = mqttConnectParameter.getClientPrivateKeyFile();\n        File rootCertificateFile = mqttConnectParameter.getRootCertificateFile();\n        //客户端私钥和客户端证书都不为null才是双向认证\n        if (clientCertificateFile != null && clientPrivateKeyFile != null) {\n            singleSsl = false;\n        }\n        SslContext sslCtx;\n        if (singleSsl) {\n            //单向认证\n            sslCtx = SslContextBuilder.forClient().trustManager(rootCertificateFile).build();\n        } else {\n            //双向认证\n            sslCtx = SslContextBuilder.forClient().keyManager(clientCertificateFile, clientPrivateKeyFile).trustManager(rootCertificateFile).build();\n        }\n        return sslCtx.newHandler(allocator, mqttConnectParameter.getHost(), mqttConnectParameter.getPort());\n\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/connector/DefaultMqttConnector.java",
    "content": "package io.github.netty.mqtt.client.connector;\n\nimport io.github.netty.mqtt.client.MqttConfiguration;\nimport io.github.netty.mqtt.client.MqttConnectParameter;\nimport io.github.netty.mqtt.client.callback.MqttCallback;\nimport io.github.netty.mqtt.client.constant.MqttAuthState;\nimport io.github.netty.mqtt.client.constant.MqttVersion;\nimport io.github.netty.mqtt.client.exception.MqttException;\nimport io.github.netty.mqtt.client.handler.channel.MqttChannelHandler;\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.support.future.DefaultMqttFuture;\nimport io.github.netty.mqtt.client.support.future.MqttFuture;\nimport io.github.netty.mqtt.client.support.util.LogUtils;\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.channel.*;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.handler.codec.mqtt.MqttDecoder;\nimport io.netty.handler.codec.mqtt.MqttEncoder;\n\nimport javax.net.ssl.SSLException;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * 默认的MQTT连接器\n * @author: xzc-coder\n */\npublic class DefaultMqttConnector extends AbstractMqttConnector {\n\n\n    private final MqttChannelHandler mqttChannelHandler;\n    private final Bootstrap bootstrap = new Bootstrap();\n    private final int connectTimeoutMillis;\n\n    public DefaultMqttConnector(MqttConfiguration configuration, MqttConnectParameter mqttConnectParameter, MqttCallback mqttCallback) {\n        super(configuration, mqttConnectParameter, mqttCallback);\n        mqttChannelHandler = new MqttChannelHandler(mqttDelegateHandler, mqttConnectParameter);\n        connectTimeoutMillis = (int) (mqttConnectParameter.getConnectTimeoutSeconds() * 1000);\n        initNettyBootstrap();\n    }\n\n\n    private void initNettyBootstrap() {\n        addOptions(bootstrap, configuration.getOptionMap());\n        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis);\n        bootstrap.attr(MqttConstant.AUTH_STATE_ATTRIBUTE_KEY, MqttAuthState.NOT_AUTH);\n        bootstrap.attr(MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY, new ConcurrentHashMap(16));\n        bootstrap.attr(MqttConstant.RECEIVE_MSG_MAP_ATTRIBUTE_KEY, new ConcurrentHashMap(16));\n        bootstrap.attr(MqttConstant.MQTT_CLIENT_ID_ATTRIBUTE_KEY, mqttConnectParameter.getClientId());\n        if(mqttConnectParameter.getMqttVersion() == MqttVersion.MQTT_5_0_0) {\n            bootstrap.attr(MqttConstant.TOPIC_ALIAS_MAP_ATTRIBUTE_KEY,new ConcurrentHashMap<>());\n        }\n        bootstrap.group(configuration.getEventLoopGroup()).channel(NioSocketChannel.class).handler(new ChannelInitializer<NioSocketChannel>() {\n            @Override\n            protected void initChannel(NioSocketChannel ch) throws SSLException {\n                ChannelPipeline pipeline = ch.pipeline();\n                if (mqttConnectParameter.isSsl()) {\n                    pipeline.addLast(MqttConstant.NETTY_SSL_HANDLER_NAME,getSslHandler(ch.alloc()));\n                }\n                //空闲检测在连接完成后再添加\n                //Netty自带的编解码器\n                pipeline.addLast(MqttConstant.NETTY_DECODER_HANDLER_NAME, new MqttDecoder());\n                pipeline.addLast(MqttConstant.NETTY_ENCODER_HANDLER_NAME, MqttEncoder.INSTANCE);\n                //Mqtt协议处理\n                pipeline.addLast(MqttConstant.NETTY_CHANNEL_HANDLER_NAME, mqttChannelHandler);\n            }\n        });\n    }\n\n    @Override\n    public MqttFuture<Channel> connect() {\n        LogUtils.info(DefaultMqttConnector.class, \"client:\" + mqttConnectParameter.getClientId() + \" tcp connecting to \" + mqttConnectParameter.getHost() + \": mqttConnectParameter.getPort()\");\n        //Netty进行TCP连接\n        ChannelFuture nettyChannelFuture = bootstrap.connect(mqttConnectParameter.getHost(), mqttConnectParameter.getPort());\n        Channel channel = nettyChannelFuture.channel();\n        //创建一个MQTT的Future\n        MqttFuture<Channel> connectMqttFuture = new DefaultMqttFuture(mqttConnectParameter.getClientId(), channel.id().asShortText(), channel);\n        //添加TCP的成功或失败监听\n        nettyChannelFuture.addListener((ChannelFutureListener) future -> {\n            if (future.isSuccess()) {\n                LogUtils.info(DefaultMqttConnector.class, \"client:\" + mqttConnectParameter.getClientId() + \" listening to connector tcp connected successfully,local:\" + channel.localAddress() + \",remote:\" + channel.remoteAddress());\n                //TCP连接成功，下一步进行MQTT的认证连接\n                mqttDelegateHandler.sendConnect(channel);\n            } else {\n                LogUtils.info(DefaultMqttConnector.class, \"client:\" + mqttConnectParameter.getClientId() + \" listening to connector tcp connected failed,host:\" + mqttConnectParameter.getHost() + \",port:\" + mqttConnectParameter.getPort() + \",cause:\" + future.cause().getMessage());\n                //设置失败，唤醒MQTT连接的Future\n                connectMqttFuture.setFailure(future.cause());\n            }\n        });\n        //添加TCP的连接断开监听（只有TCP连接成功后再断开才会有该监听）\n        channel.closeFuture().addListener((ChannelFutureListener) future -> {\n            MqttAuthState mqttAuthState = channel.attr(MqttConstant.AUTH_STATE_ATTRIBUTE_KEY).get();\n            //判断是否完成认证，如果没有完成认证（可能TCP连接上之后直接断开，没有进行MQTT的连接认证），设置失败，唤醒MQTT连接的Future\n            if (MqttAuthState.NOT_AUTH == mqttAuthState) {\n                LogUtils.warn(DefaultMqttConnector.class, \"client:\" + mqttConnectParameter.getClientId() + \" disconnect without authentication\");\n                MqttFuture connectFuture = MqttFuture.getFuture(mqttConnectParameter.getClientId(), channel.id().asShortText());\n                if (connectFuture != null) {\n                    connectFuture.setFailure(new MqttException(\"client did not complete authentication, connection has been lost\", mqttConnectParameter.getClientId()));\n                }\n            }\n        });\n        return connectMqttFuture;\n    }\n\n    @Override\n    protected MqttDelegateHandler createDelegateHandle(Object... handlerCreateArgs) {\n        return configuration.newMqttMsgHandler(mqttConnectParameter, handlerCreateArgs[0], configuration.getMqttMsgStore());\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/connector/MqttAuthInstruct.java",
    "content": "package io.github.netty.mqtt.client.connector;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\n\nimport java.util.Arrays;\n\n/**\n * 认证指示\n */\npublic class MqttAuthInstruct {\n\n\n    public enum Instruct {\n        /**\n         * 重新认证\n         */\n        RE_AUTH,\n        /**\n         * 继续认证\n         */\n        AUTH_CONTINUE,\n        /**\n         * 认证失败\n         */\n        AUTH_FAIL;\n    }\n\n    /**\n     * 认证指示，下一步要怎么操作\n     */\n    private final Instruct nextInstruct;\n    /**\n     * 认证数据\n     */\n    private byte[] authenticationData;\n    /**\n     * 如果认证失败的话，原因字符串\n     */\n    private String reasonString;\n    /**\n     * 用户属性\n     */\n    private final MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n\n    public MqttAuthInstruct(Instruct nextInstruct) {\n        this(nextInstruct, null);\n    }\n\n    public MqttAuthInstruct(Instruct nextInstruct, byte[] authenticationData) {\n        AssertUtils.notNull(nextInstruct, \"nextInstruct is null\");\n        this.nextInstruct = nextInstruct;\n        this.authenticationData = authenticationData;\n    }\n\n    public byte[] getAuthenticationData() {\n        return authenticationData;\n    }\n\n\n    public void setAuthenticationData(byte[] authenticationData) {\n        this.authenticationData = authenticationData;\n    }\n\n    public Instruct getNextInstruct() {\n        return nextInstruct;\n    }\n\n    public String getReasonString() {\n        return reasonString;\n    }\n\n    public void setReasonString(String reasonString) {\n        this.reasonString = reasonString;\n    }\n\n    public MqttProperties.UserProperties getMqttUserProperties() {\n        return mqttUserProperties;\n    }\n\n    public void addMqttUserProperty(String key, String value) {\n        if (key != null && value != null) {\n            mqttUserProperties.add(key, value);\n        }\n    }\n\n    public void addMqttUserProperty(MqttProperties.StringPair stringPair) {\n        if (stringPair != null) {\n            mqttUserProperties.add(stringPair);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"MqttAuthInstruct{\" +\n                \"nextInstruct=\" + nextInstruct +\n                \", authenticationData=\" + Arrays.toString(authenticationData) +\n                \", reasonString='\" + reasonString + '\\'' +\n                \", mqttUserProperties=\" + mqttUserProperties +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/connector/MqttAuthenticator.java",
    "content": "package io.github.netty.mqtt.client.connector;\n\npublic interface MqttAuthenticator {\n\n    /**\n     * 继续认证（server返回成功则不会调用该方法，只有返回继续认证才会）\n     *\n     * @param authenticationMethod     认证方法\n     * @param serverAuthenticationData 服务器返回的认证数据\n     * @return 继续认证要传递的信息\n     */\n    MqttAuthInstruct authing(String authenticationMethod, byte[] serverAuthenticationData);\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/connector/MqttConnector.java",
    "content": "package io.github.netty.mqtt.client.connector;\n\nimport io.github.netty.mqtt.client.MqttConfiguration;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.support.future.MqttFuture;\nimport io.netty.channel.Channel;\n\n/**\n * MQTT连接器接口\n * @author: xzc-coder\n */\npublic interface MqttConnector {\n\n    /**\n     * 进行MQTT连接\n     *\n     * @return Future\n     */\n    MqttFuture<Channel> connect();\n\n    /**\n     * 获取消息委托处理器\n     *\n     * @return MqttDelegateHandler\n     */\n    MqttDelegateHandler getMqttDelegateHandler();\n\n    /**\n     * 获取MQTT全局配置\n     *\n     * @return MqttConfiguration\n     */\n    MqttConfiguration getMqttConfiguration();\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/constant/MqttAuthState.java",
    "content": "package io.github.netty.mqtt.client.constant;\n\n/**\n * MQTT认证状态\n * @author: xzc-coder\n */\npublic enum MqttAuthState {\n\n    /**\n     * 未认证，初始值\n     */\n    NOT_AUTH,\n    /**\n     * 认证失败\n     */\n    AUTH_FAIL,\n    /**\n     * 认证成功\n     */\n    AUTH_SUCCESS\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/constant/MqttConstant.java",
    "content": "package io.github.netty.mqtt.client.constant;\n\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.netty.util.AttributeKey;\n\nimport java.math.BigDecimal;\nimport java.util.Map;\n\n/**\n * MQTT常量\n * @author: xzc-coder\n */\npublic class MqttConstant {\n\n    private MqttConstant() {\n    }\n\n\n    public static final String AUTH_STATE_KEY = \"NETTY_MQTT_AUTH_STATE_KEY\";\n\n    public static final String SEND_MSG_MAP_KEY = \"NETTY_MQTT_SEND_SET_MAP_KEY\";\n\n    public static final String RECEIVE_MSG_MAP_KEY = \"NETTY_MQTT_RECEIVE_SET_MAP_KEY\";\n\n    public static final String MQTT_CLIENT_ID_KEY = \"NETTY_MQTT_MQTT_CLIENT_ID_KEY\";\n\n    public static final String KEEP_ALIVE_TIME_KEY = \"NETTY_KEEP_ALIVE_TIME_KEY\";\n\n    public static final String TOPIC_ALIAS_MAP_KEY = \"NETTY_TOPIC_ALIAS_MAP_KEY\";\n\n    public static final String DISCONNECT_REASON_CODE_KEY = \"NETTY_DISCONNECT_REASON_CODE_KEY\";\n\n\n    /**\n     * Channel中的MQTT认证值\n     */\n    public static final AttributeKey<MqttAuthState> AUTH_STATE_ATTRIBUTE_KEY = AttributeKey.newInstance(MqttConstant.AUTH_STATE_KEY);\n    /**\n     * Channel中的发送消息存储MAP，存储清理会话的发布消息、订阅消息、取消订阅消息和不清理会话的订阅消息、取消订阅消息\n     */\n    public static final AttributeKey<Map<Integer, Object>> SEND_MSG_MAP_ATTRIBUTE_KEY = AttributeKey.newInstance(MqttConstant.SEND_MSG_MAP_KEY);\n    /**\n     * Channel中的接收消息存储MAP，存储清理会话的接收到的qos2的消息\n     */\n    public static final AttributeKey<Map<Integer, MqttMsg>> RECEIVE_MSG_MAP_ATTRIBUTE_KEY = AttributeKey.newInstance(MqttConstant.RECEIVE_MSG_MAP_KEY);\n    /**\n     * Channel中的MQTT的客户端ID\n     */\n    public static final AttributeKey<String> MQTT_CLIENT_ID_ATTRIBUTE_KEY = AttributeKey.newInstance(MqttConstant.MQTT_CLIENT_ID_KEY);\n    /**\n     * Channel中的MQTT活跃间隔，MQTT 5中是需要使用Broker返回的值\n     */\n    public static final AttributeKey<Integer> KEEP_ALIVE_TIME_ATTRIBUTE_KEY = AttributeKey.newInstance(MqttConstant.KEEP_ALIVE_TIME_KEY);\n\n    /**\n     * Channel中的MQTT主题别名映射Map，MQTT 5中使用\n     */\n    public static final AttributeKey<Map<String,Integer>> TOPIC_ALIAS_MAP_ATTRIBUTE_KEY = AttributeKey.newInstance(MqttConstant.TOPIC_ALIAS_MAP_KEY);\n\n    /**\n     * Channel中断开连接的reasonCode，如果是正常断开reasonCode则为0X00\n     */\n    public static final AttributeKey<Byte> DISCONNECT_REASON_CODE_ATTRIBUTE_KEY = AttributeKey.newInstance(DISCONNECT_REASON_CODE_KEY);\n\n    /**\n     * 最大端口号 65535,2个字节\n     */\n    public static final int MAX_PORT = 0xFFFF;\n\n    /**\n     * MQTT最小的消息ID值\n     */\n    public static final int MQTT_MIN_MSG_ID = 1;\n    /**\n     * MQTT最大的消息ID值\n     */\n    public static final int MQTT_MAX_MSG_ID = 65535;\n\n    /**\n     * MQTT最大的消息数量\n     */\n    public static final int MQTT_MAX_MSG_ID_NUMBER = 65535;\n    /**\n     * MQTT最小的主题长度\n     */\n    public static final int MQTT_MIN_TOPIC_LEN = 1;\n    /**\n     * MQTT最大的主题长度\n     */\n    public static final int MQTT_MAX_TOPIC_LEN = 65535;\n\n    /**\n     * MQTT多级通配符\n     */\n    public static final String MQTT_MULTI_LEVEL_WILDCARD = \"#\";\n\n    /**\n     * MQTT主题级别的分隔符\n     */\n    public static final String MQTT_TOPIC_LEVEL_SEPARATOR = \"/\";\n\n    /**\n     * MQTT单级通配符\n     */\n    public static final String MQTT_SINGLE_LEVEL_WILDCARD = \"+\";\n\n    /**\n     * MQTT默认的字符集编码\n     */\n    public static final String MQTT_DEFAULT_CHARACTER = \"UTF-8\";\n\n    /**\n     * cglib创建的代理对象类名包含的内容\n     */\n    public static final String CGLIB_CONTAIN_CONTENT = \"ByCGLIB$$\";\n    /**\n     * jdk创建的代理对象类名包含的内容\n     */\n    public static final String JDK_PROXY_CONTAIN_CONTENT = \"$Proxy\";\n\n    /**\n     * 代理类型 cglib\n     */\n    public static final String PROXY_TYPE_CGLIB = \"cglib\";\n    /**\n     * 代理类型 jdk\n     */\n    public static final String PROXY_TYPE_JDK = \"jdk\";\n\n\n    /**\n     * 空字符\n     */\n    public static final char NUL = '\\u0000';\n\n    /**\n     * MQTT版本默认值\n     */\n    public static final MqttVersion DEFAULT_MQTT_VERSION = MqttVersion.MQTT_3_1_1;\n\n    /**\n     * 连接默认超时时间（秒）\n     */\n    public static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 30;\n\n    /**\n     * 自动重连默认值\n     */\n    public static final boolean DEFAULT_AUTO_RECONNECT = false;\n\n    /**\n     * 重试毫秒间隔\n     */\n    public static final long DEFAULT_RETRY_INTERVAL_MILLIS = 1000L;\n\n    /**\n     * 默认的消息重试递增值\n     */\n    public static final long DEFAULT_MSG_RETRY_INCREASE_MILLS = 1000;\n\n    /**\n     * 默认的消息重试最大时间\n     */\n    public static final long DEFAULT_MSG_RETRY_MAX_MILLS = 15000;\n\n    /**\n     * 默认心跳间隔\n     */\n    public static final int DEFAULT_KEEP_ALIVE_TIME_SECONDS = 30;\n\n    /**\n     * 默认的心跳间隔系数\n     */\n    public static final BigDecimal DEFAULT_KEEP_ALIVE_TIME_COEFFICIENT = new BigDecimal(\"0.75\");\n\n    /**\n     * 默认清理会话\n     */\n    public static final boolean DEFAULT_CLEAR_SESSION = true;\n    /**\n     * Netty中的MQTT连接的响应码，转换为16进制\n     */\n    public static final int NETTY_MQTT_CONNECT_RETURN_CODE_RADIX = 16;\n\n    /**\n     * 默认的host\n     */\n    public static final String DEFAULT_HOST = \"localhost\";\n    /**\n     * 默认的端口\n     */\n    public static final int DEFAULT_PORT = 1883;\n\n    /**\n     * 请求问题标识符\n     */\n    public static final int DEFAULT_REQUEST_PROBLEM_INFORMATION = 1;\n\n    /**\n     * 请求响应标识符\n     */\n    public static final int DEFAULT_REQUEST_RESPONSE_INFORMATION = 0;\n\n    /**\n     * 消息重试间隔，初始2000毫秒，每次失败+1000\n     */\n    public static final long MSG_RETRY_MILLS = 2000;\n    /**\n     * 默认的消息重试递增值\n     */\n    public static final long MSG_RETRY_INCREASE_MILLS = 1000;\n\n    /**\n     * 默认的消息重试最大时间\n     */\n    public static final long MSG_RETRY_MAX_MILLS = 15000;\n\n    /**\n     * 无效的消息ID值，用于qos 0的消息占位\n     */\n    public static final int INVALID_MSG_ID = 0;\n\n    /**\n     * Netty中的线程池名\n     */\n    public static final String THREAD_FACTORY_POOL_NAME = \"netty-mqtt-client-eventLoop\";\n\n    /**\n     * 空字符串\n     */\n    public static final String EMPTY_STR = \"\";\n\n\n    public static final String NETTY_SSL_HANDLER_NAME = \"sslHandler\";\n\n    public static final String NETTY_IDLE_HANDLER_NAME = \"idleStateHandler\";\n\n    public static final String NETTY_DECODER_HANDLER_NAME = \"mqttDecoder\";\n\n    public static final String NETTY_ENCODER_HANDLER_NAME = \"mqttEncoder\";\n\n    public static final String NETTY_CHANNEL_HANDLER_NAME = \"mqttChannelHandler\";\n\n\n    /**\n     * 认证原因码：继续认证\n     */\n    public static final byte AUTH_CONTINUE_REASON_CODE = 0x18;\n    /**\n     * 认证原因码：重新认证\n     */\n    public static final byte AUTH_RE_AUTH_REASON_CODE = 0x19;\n\n    /**\n     * 取消订阅成功码\n     */\n    public static final short UNSUBSCRIPTION_SUCCESS_REASON_CODE = 0x00;\n\n    /**\n     * 消息成功码\n     */\n    public static final byte MESSAGE_SUCCESS_REASON_CODE = 0x00;\n\n    /**\n     * 正常断开连接成功码\n     */\n    public static final byte DISCONNECT_SUCCESS_REASON_CODE = 0x00;\n\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/constant/MqttMsgDirection.java",
    "content": "package io.github.netty.mqtt.client.constant;\n\n\n/**\n * MQTT消息方向\n * @author: xzc-coder\n */\npublic enum MqttMsgDirection {\n\n    /**\n     * 发送方消息\n     */\n    SEND(0),\n    /**\n     * 接收方消息\n     */\n    RECEIVE(1);\n\n    /**\n     * 方向值\n     */\n    private final int direction;\n\n    MqttMsgDirection(int direction) {\n        this.direction = direction;\n    }\n\n    /**\n     * 根据方向值查询枚举值\n     * @param direction 方向值\n     * @return MqttMsgDirection\n     */\n    public static MqttMsgDirection findMqttMsgDirection(int direction) {\n        MqttMsgDirection[] mqttMsgDirections = MqttMsgDirection.values();\n        for(MqttMsgDirection mqttMsgDirection : mqttMsgDirections) {\n            if(mqttMsgDirection.direction == direction){\n                return mqttMsgDirection;\n            }\n        }\n        return null;\n    }\n\n    public int getDirection() {\n        return direction;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/constant/MqttMsgState.java",
    "content": "package io.github.netty.mqtt.client.constant;\n\n/**\n * MQTT消息状态\n * @author: xzc-coder\n */\npublic enum MqttMsgState {\n\n    /**\n     * 发布消息\n     */\n    PUBLISH(0),\n    /**\n     * 发布确认消息\n     */\n    PUBACK(1),\n    /**\n     * PUBREC，QoS 2，第一步\n     */\n    PUBREC(2),\n    /**\n     * 发布释放,QoS 2，第二步\n     */\n    PUBREL(3),\n    /**\n     * 发布完成，QoS 2，第三步\n     */\n    PUBCOMP(4),\n    /**\n     * 无效的，表示该消息没用，占用判断使用\n     */\n    INVALID(5),\n    ;\n\n    /**\n     * 状态值\n     */\n    private final int state;\n\n    MqttMsgState(int state) {\n        this.state = state;\n    }\n\n    /**\n     * 根据状态值查询枚举值\n     * @param state 状态值\n     * @return MqttMsgState\n     */\n    public static MqttMsgState findMqttMsgState(int state) {\n        MqttMsgState[] mqttMsgStates = MqttMsgState.values();\n        for (MqttMsgState mqttMsgState : mqttMsgStates) {\n            if (mqttMsgState.state == state) {\n                return mqttMsgState;\n            }\n        }\n        return null;\n    }\n\n    public int getState() {\n        return state;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/constant/MqttVersion.java",
    "content": "package io.github.netty.mqtt.client.constant;\n\n/**\n * MQTT版本\n * @author: xzc-coder\n */\npublic enum MqttVersion {\n\n    /**\n     * MQTT版本\n     */\n    MQTT_3_1_1,\n    MQTT_5_0_0,\n    ;\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/createor/MqttClientObjectCreator.java",
    "content": "package io.github.netty.mqtt.client.createor;\n\nimport io.github.netty.mqtt.client.DefaultMqttClient;\nimport io.github.netty.mqtt.client.MqttClient;\nimport io.github.netty.mqtt.client.MqttConfiguration;\nimport io.github.netty.mqtt.client.MqttConnectParameter;\n\n/**\n * 默认的MQTT客户端创建器\n * @author: xzc-coder\n */\npublic class MqttClientObjectCreator implements ObjectCreator<MqttClient> {\n\n    @Override\n    public MqttClient createObject(Object... constructorArgs) {\n        MqttConfiguration mqttConfiguration = (MqttConfiguration) constructorArgs[1];\n        MqttConnectParameter mqttConnectParameter = (MqttConnectParameter) constructorArgs[2];\n        return new DefaultMqttClient(mqttConfiguration,mqttConnectParameter);\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/createor/MqttConnectorObjectCreator.java",
    "content": "package io.github.netty.mqtt.client.createor;\n\nimport io.github.netty.mqtt.client.MqttConfiguration;\nimport io.github.netty.mqtt.client.MqttConnectParameter;\nimport io.github.netty.mqtt.client.callback.MqttCallback;\nimport io.github.netty.mqtt.client.connector.DefaultMqttConnector;\nimport io.github.netty.mqtt.client.connector.MqttConnector;\n\n/**\n * 默认的MQTT连接器创建器\n * @author: xzc-coder\n */\npublic class MqttConnectorObjectCreator implements ObjectCreator<MqttConnector> {\n\n\n    @Override\n    public MqttConnector createObject(Object... constructorArgs) {\n        MqttConfiguration mqttConfiguration = (MqttConfiguration) constructorArgs[0];\n        MqttConnectParameter mqttConnectParameter = (MqttConnectParameter) constructorArgs[1];\n        MqttCallback mqttCallback = (MqttCallback) constructorArgs[2];\n        return new DefaultMqttConnector(mqttConfiguration,mqttConnectParameter,mqttCallback);\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/createor/MqttDelegateHandlerObjectCreator.java",
    "content": "package io.github.netty.mqtt.client.createor;\n\nimport io.github.netty.mqtt.client.MqttConnectParameter;\nimport io.github.netty.mqtt.client.callback.MqttCallback;\nimport io.github.netty.mqtt.client.handler.DefaultMqttDelegateHandler;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.store.MqttMsgStore;\n\n/**\n * 默认的MQTT消息委托处理器创建器\n * @author: xzc-coder\n */\npublic class MqttDelegateHandlerObjectCreator implements ObjectCreator<MqttDelegateHandler> {\n\n\n    @Override\n    public MqttDelegateHandler createObject(Object... constructorArgs) {\n        MqttConnectParameter mqttConnectParameter = (MqttConnectParameter) constructorArgs[0];\n        MqttCallback mqttCallback = (MqttCallback) constructorArgs[1];\n        MqttMsgStore mqttMsgStore = (MqttMsgStore) constructorArgs[2];\n        return new DefaultMqttDelegateHandler(mqttConnectParameter,mqttCallback,mqttMsgStore);\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/createor/ObjectCreator.java",
    "content": "package io.github.netty.mqtt.client.createor;\n\n/**\n * 对象创建器接口\n * @param <T> 对象创建器创建的类\n * @author: xzc-coder\n */\npublic interface ObjectCreator<T> {\n\n    /**\n     * 对象创建器\n     *\n     * @param constructorArgs 构建参数\n     * @return 对象实例\n     * @param <T> T\n     */\n    <T>T createObject(Object... constructorArgs);\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/exception/MqttException.java",
    "content": "package io.github.netty.mqtt.client.exception;\n\n/**\n * MQTT异常\n * @author: xzc-coder\n */\npublic class MqttException extends RuntimeException {\n\n\n    private String clientId;\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public void setClientId(String clientId) {\n        this.clientId = clientId;\n    }\n\n\n    public MqttException(String message) {\n        super(message);\n    }\n\n    public MqttException(String message, String clientId) {\n        super(message);\n        this.clientId = clientId;\n    }\n\n    public MqttException(String message, Throwable cause, String clientId) {\n        super(message, cause);\n        this.clientId = clientId;\n    }\n\n    public MqttException(Throwable cause, String clientId) {\n        super(cause);\n        this.clientId = clientId;\n    }\n\n    public MqttException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, String clientId) {\n        super(message, cause, enableSuppression, writableStackTrace);\n        this.clientId = clientId;\n    }\n\n    public MqttException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public MqttException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/exception/MqttStateCheckException.java",
    "content": "package io.github.netty.mqtt.client.exception;\n\n/**\n * MQTT状态检测异常\n * @author: xzc-coder\n */\npublic class MqttStateCheckException extends MqttException {\n\n    public MqttStateCheckException(String message) {\n        super(message);\n    }\n\n    public MqttStateCheckException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public MqttStateCheckException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/handler/DefaultMqttDelegateHandler.java",
    "content": "package io.github.netty.mqtt.client.handler;\n\nimport io.github.netty.mqtt.client.MqttConnectParameter;\nimport io.github.netty.mqtt.client.callback.*;\nimport io.github.netty.mqtt.client.connector.MqttAuthInstruct;\nimport io.github.netty.mqtt.client.connector.MqttAuthenticator;\nimport io.github.netty.mqtt.client.constant.*;\n\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.exception.MqttException;\nimport io.github.netty.mqtt.client.msg.*;\nimport io.github.netty.mqtt.client.msg.*;\nimport io.github.netty.mqtt.client.store.MqttMsgStore;\nimport io.github.netty.mqtt.client.support.future.MqttFuture;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\nimport io.github.netty.mqtt.client.support.util.LogUtils;\nimport io.github.netty.mqtt.client.support.util.MqttUtils;\nimport io.github.netty.mqtt.client.callback.*;\nimport io.github.netty.mqtt.client.constant.MqttAuthState;\nimport io.github.netty.mqtt.client.constant.MqttMsgDirection;\nimport io.github.netty.mqtt.client.constant.MqttMsgState;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.PooledByteBufAllocator;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.handler.codec.mqtt.*;\nimport io.netty.handler.codec.mqtt.MqttVersion;\nimport io.netty.util.Attribute;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 默认的MQTT委托处理器\n * @author: xzc-coder\n */\npublic class DefaultMqttDelegateHandler implements MqttDelegateHandler {\n\n    /**\n     * MQTT连接参数\n     */\n    private final MqttConnectParameter mqttConnectParameter;\n    /**\n     * MQTT回调器\n     */\n    private final MqttCallback mqttCallback;\n    /**\n     * M客户端ID\n     */\n    private final String clientId;\n    /**\n     * MQTT消息存储器\n     */\n    private final MqttMsgStore mqttMsgStore;\n\n    public DefaultMqttDelegateHandler(MqttConnectParameter mqttConnectParameter, MqttCallback mqttCallback, MqttMsgStore mqttMsgStore) {\n        AssertUtils.notNull(mqttConnectParameter, \"mqttConnectParameter is null\");\n        AssertUtils.notNull(mqttCallback, \"mqttCallback is null\");\n        AssertUtils.notNull(mqttMsgStore, \"mqttMsgStore is null\");\n        this.mqttConnectParameter = mqttConnectParameter;\n        this.mqttCallback = mqttCallback;\n        this.mqttMsgStore = mqttMsgStore;\n        this.clientId = mqttConnectParameter.getClientId();\n    }\n\n    @Override\n    public void channelConnect(Channel channel) {\n        MqttConnectCallbackResult mqttConnectCallbackResult = new MqttConnectCallbackResult(clientId, channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.AUTH_STATE_ATTRIBUTE_KEY).get());\n        mqttConnectCallbackResult.setMqttVersion(mqttConnectParameter.getMqttVersion());\n        mqttCallback.channelConnectCallback(mqttConnectCallbackResult);\n    }\n\n    @Override\n    public void sendConnect(Channel channel) {\n        //连接相关\n        MqttVersion nettyMqttVersion;\n        if (mqttConnectParameter.getMqttVersion() == io.github.netty.mqtt.client.constant.MqttVersion.MQTT_5_0_0) {\n            nettyMqttVersion = MqttVersion.MQTT_5;\n        } else {\n            nettyMqttVersion = MqttVersion.MQTT_3_1_1;\n        }\n        int keepAliveTimeSeconds = mqttConnectParameter.getKeepAliveTimeSeconds();\n        boolean cleanSession = mqttConnectParameter.isCleanSession();\n        String clientId = mqttConnectParameter.getClientId();\n        MqttProperties mqttProperties = MqttUtils.getConnectMqttProperties(mqttConnectParameter);\n        //账号密码\n        String username = mqttConnectParameter.getUsername();\n        char[] password = mqttConnectParameter.getPassword();\n        byte[] passwordBytes = null;\n        if (password != null) {\n            passwordBytes = new String(password).getBytes(StandardCharsets.UTF_8);\n        }\n        //遗嘱相关\n        MqttWillMsg willMsg = mqttConnectParameter.getWillMsg();\n        boolean hasWill = mqttConnectParameter.hasWill();\n        MqttQoS willQos = MqttQoS.AT_MOST_ONCE;\n        boolean isWillRetain = false;\n        String willTopic = null;\n        byte[] willMessageBytes = null;\n        MqttProperties willProperties = MqttProperties.NO_PROPERTIES;\n        if (hasWill) {\n            willQos = willMsg.getWillQos();\n            isWillRetain = willMsg.isWillRetain();\n            willTopic = willMsg.getWillTopic();\n            willMessageBytes = willMsg.getWillMessageBytes();\n            willProperties = MqttUtils.getWillMqttProperties(mqttConnectParameter.getMqttVersion(), willMsg);\n        }\n        //发送报文\n        MqttConnectMessage connectMessage = MqttMessageBuilders.connect().clientId(clientId).properties(mqttProperties).username(username).password(passwordBytes).cleanSession(cleanSession).protocolVersion(nettyMqttVersion).keepAlive(keepAliveTimeSeconds).willFlag(hasWill).willMessage(willMessageBytes).willQoS(willQos).willRetain(isWillRetain).willTopic(willTopic).willProperties(willProperties).build();\n        channel.writeAndFlush(connectMessage);\n\n    }\n\n    @Override\n    public void connack(Channel channel, MqttConnAckMessage mqttConnAckMessage) {\n        String clientId = mqttConnectParameter.getClientId();\n        MqttConnAckVariableHeader mqttVariableHeader = mqttConnAckMessage.variableHeader();\n        MqttConnectReturnCode mqttConnectReturnCode = mqttVariableHeader.connectReturnCode();\n        boolean sessionPresent = mqttVariableHeader.isSessionPresent();\n        MqttProperties mqttProperties = mqttVariableHeader.properties();\n        //获取Future\n        MqttFuture<Channel> connectMqttFuture = MqttFuture.getFuture(clientId, channel.id().asShortText());\n        MqttConnectCallbackResult mqttConnectCallbackResult;\n        //根据返回Code判断是否成功\n        if (MqttConnectReturnCode.CONNECTION_ACCEPTED.equals(mqttConnectReturnCode)) {\n            LogUtils.info(DefaultMqttDelegateHandler.class, \"client: \" + clientId + \" MQTT authentication successful\");\n            //设置为已认证\n            Attribute<MqttAuthState> mqttAuthStateAttribute = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.AUTH_STATE_ATTRIBUTE_KEY);\n            mqttAuthStateAttribute.set(MqttAuthState.AUTH_SUCCESS);\n            //当Broker返回会话不存在时，需要清除消息存储器中的消息\n            if (!sessionPresent) {\n                mqttMsgStore.clearMsg(mqttConnectParameter.getClientId());\n            }\n            //设置成功\n            if (connectMqttFuture != null) {\n                connectMqttFuture.setSuccess(channel);\n            }\n            //回调\n            mqttConnectCallbackResult = new MqttConnectCallbackResult(clientId, channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.AUTH_STATE_ATTRIBUTE_KEY).get(), sessionPresent);\n            mqttConnectCallbackResult.setMqttVersion(mqttConnectParameter.getMqttVersion());\n            mqttConnectCallbackResult.setMqttProperties(mqttProperties);\n            mqttConnectCallbackResult.setSessionPresent(sessionPresent);\n            mqttCallback.connectCompleteCallback(mqttConnectCallbackResult);\n        } else {\n            String connectReturnCode = Integer.toString(mqttConnectReturnCode.byteValue(), MqttConstant.NETTY_MQTT_CONNECT_RETURN_CODE_RADIX);\n            LogUtils.info(DefaultMqttDelegateHandler.class, addReasonString(\"client: \" + clientId + \" MQTT authentication failed,returnCode:\" + connectReturnCode, mqttProperties));\n            //设置为认证失败\n            Attribute<MqttAuthState> mqttAuthStateAttribute = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.AUTH_STATE_ATTRIBUTE_KEY);\n            mqttAuthStateAttribute.set(MqttAuthState.AUTH_FAIL);\n            String exceptionMessage = \"auth failed,returnCode:\" + connectReturnCode + \",please refer to: io.netty.handler.codec.mqtt.MqttConnectReturnCode\";\n            MqttException mqttException = new MqttException(exceptionMessage, clientId);\n            //设置失败\n            if (connectMqttFuture != null) {\n                connectMqttFuture.setFailure(mqttException);\n            }\n            //回调\n            mqttConnectCallbackResult = new MqttConnectCallbackResult(clientId, channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.AUTH_STATE_ATTRIBUTE_KEY).get(), mqttException, mqttConnectReturnCode.byteValue());\n            mqttConnectCallbackResult.setMqttVersion(mqttConnectParameter.getMqttVersion());\n            mqttConnectCallbackResult.setMqttProperties(mqttProperties);\n            mqttCallback.connectCompleteCallback(mqttConnectCallbackResult);\n            //关闭连接\n            channel.close();\n        }\n    }\n\n    @Override\n    public void auth(Channel channel, MqttMessage mqttAuthMessage) {\n        MqttReasonCodeAndPropertiesVariableHeader mqttVariableHeader = (MqttReasonCodeAndPropertiesVariableHeader) mqttAuthMessage.variableHeader();\n        if (mqttVariableHeader.reasonCode() == io.github.netty.mqtt.client.constant.MqttConstant.AUTH_CONTINUE_REASON_CODE) {\n            MqttAuthenticator mqttAuthenticator = mqttConnectParameter.getMqttAuthenticator();\n            MqttProperties properties = mqttVariableHeader.properties();\n            //认证器不为空才进行认证\n            if (mqttAuthenticator != null) {\n                //Broke的认证方法和认证数据\n                MqttProperties.StringProperty authenticationMethodProperty = (MqttProperties.StringProperty) properties.getProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_METHOD.value());\n                MqttProperties.BinaryProperty authenticationDataProperty = (MqttProperties.BinaryProperty) properties.getProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_DATA.value());\n                String authenticationMethod = null;\n                byte[] authenticationData = null;\n                if (authenticationMethodProperty != null) {\n                    authenticationMethod = authenticationMethodProperty.value();\n                }\n                if (authenticationDataProperty != null) {\n                    authenticationData = authenticationDataProperty.value();\n                }\n                //进行认证交互\n                MqttAuthInstruct mqttAuthInstruct = mqttAuthenticator.authing(authenticationMethod, authenticationData);\n                if (mqttAuthInstruct != null) {\n                    //根据认证指示进行下一步的操作\n                    MqttAuthInstruct.Instruct nextInstruct = mqttAuthInstruct.getNextInstruct();\n                    //待发送的认证数据\n                    byte[] sendAuthenticationData = mqttAuthInstruct.getAuthenticationData();\n                    String reasonString = mqttAuthInstruct.getReasonString();\n                    MqttProperties.UserProperties mqttUserProperties = mqttAuthInstruct.getMqttUserProperties();\n                    MqttProperties authMqttProperties;\n                    byte authenticateReasonCode;\n                    //如果是认证失败和认证继续，则发送继续认证的原因码\n                    if (nextInstruct == MqttAuthInstruct.Instruct.AUTH_FAIL || nextInstruct == MqttAuthInstruct.Instruct.AUTH_CONTINUE) {\n                        authenticateReasonCode = io.github.netty.mqtt.client.constant.MqttConstant.AUTH_CONTINUE_REASON_CODE;\n                    } else if (nextInstruct == MqttAuthInstruct.Instruct.RE_AUTH) {\n                        //重新认证\n                        authenticateReasonCode = io.github.netty.mqtt.client.constant.MqttConstant.AUTH_RE_AUTH_REASON_CODE;\n                    } else {\n                        throw new MqttException(\"client:\" + mqttConnectParameter.getClientId() + \" auth has error occurred,nextInstruct value is illegal\");\n                    }\n                    //发送认证报文\n                    authMqttProperties = MqttUtils.getAuthMqttProperties(authenticationMethod, sendAuthenticationData, reasonString, mqttUserProperties);\n                    sendAuth(channel, authenticateReasonCode, authMqttProperties);\n                }\n            }\n        }\n    }\n\n    @Override\n    public void sendAuth(Channel channel, byte reasonCode, MqttProperties mqttProperties) {\n        MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.AUTH, false, MqttQoS.AT_MOST_ONCE, false, 0);\n        MqttReasonCodeAndPropertiesVariableHeader mqttReasonCodeAndPropertiesVariableHeader = new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, mqttProperties);\n        //认证报文\n        MqttMessage mqttMessage = new MqttMessage(mqttFixedHeader, mqttReasonCodeAndPropertiesVariableHeader);\n        channel.writeAndFlush(mqttMessage);\n    }\n\n    @Override\n    public void sendDisconnect(Channel channel, MqttFuture mqttFuture, MqttDisconnectMsg mqttDisconnectMsg) {\n        //调用该方法断开连接的都属于正常断开\n        MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);\n        byte reasonCode = mqttDisconnectMsg.getReasonCode();\n        channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.DISCONNECT_REASON_CODE_ATTRIBUTE_KEY).set(reasonCode);\n        MqttProperties mqttProperties = MqttUtils.getDisconnectMqttProperties(mqttDisconnectMsg);\n        MqttReasonCodeAndPropertiesVariableHeader mqttVariableHeader = new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, mqttProperties);\n        MqttMessage mqttMessage = new MqttMessage(mqttFixedHeader, mqttVariableHeader);\n        channel.writeAndFlush(mqttMessage).addListener(future -> {\n            ChannelFuture closeFuture = channel.close();\n            closeFuture.addListener(closeCompleteFuture -> {\n                if (closeCompleteFuture.isSuccess()) {\n                    mqttFuture.setSuccess(null);\n                } else {\n                    mqttFuture.setFailure(closeCompleteFuture.cause());\n                }\n            });\n        });\n    }\n\n    @Override\n    public void disconnect(Channel channel, MqttMessage mqttMessage) {\n        MqttProperties mqttProperties = null;\n        Byte reasonCode = null;\n        if (mqttMessage != null) {\n            MqttReasonCodeAndPropertiesVariableHeader mqttVariableHeader = (MqttReasonCodeAndPropertiesVariableHeader) mqttMessage.variableHeader();\n            reasonCode = mqttVariableHeader.reasonCode();\n            channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.DISCONNECT_REASON_CODE_ATTRIBUTE_KEY).set(reasonCode);\n            mqttProperties = mqttVariableHeader.properties();\n            String reasonString = MqttUtils.getReasonString(mqttProperties);\n            if (EmptyUtils.isNotBlank(reasonString)) {\n                LogUtils.warn(DefaultMqttDelegateHandler.class, \"client:\" + clientId + \" disconnected,reason string : \" + reasonString);\n            }\n        }\n        MqttAuthState mqttAuthState = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.AUTH_STATE_ATTRIBUTE_KEY).get();\n        MqttConnectLostCallbackResult mqttConnectLostCallbackResult = new MqttConnectLostCallbackResult(clientId, mqttAuthState);\n        mqttConnectLostCallbackResult.setReasonCode(reasonCode);\n        mqttConnectLostCallbackResult.setMqttProperties(mqttProperties);\n        mqttConnectLostCallbackResult.setMqttVersion(mqttConnectParameter.getMqttVersion());\n        mqttCallback.connectLostCallback(mqttConnectLostCallbackResult);\n    }\n\n\n    @Override\n    public void sendSubscribe(Channel channel, MqttSubMsg mqttSubMsg) {\n        //固定头，除了类型和remainingLength，其它的值都没有用，可变头长度为0\n        MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 2);\n        //订阅属性处理\n        MqttProperties mqttProperties = new MqttProperties();\n        if (mqttSubMsg.getSubscriptionIdentifier() != null) {\n            mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value(), mqttSubMsg.getSubscriptionIdentifier()));\n        }\n        //用户属性\n        mqttProperties.add(mqttSubMsg.getMqttUserProperties());\n        //可变头\n        MqttMessageIdAndPropertiesVariableHeader variableHeader = new MqttMessageIdAndPropertiesVariableHeader(mqttSubMsg.getMsgId(), mqttProperties);\n        //消息体\n        List<MqttTopicSubscription> mqttTopicSubscriptionList = toMqttTopicSubscriptionList(mqttSubMsg.getMqttSubInfoList());\n        MqttSubscribePayload mqttSubscribePayload = new MqttSubscribePayload(mqttTopicSubscriptionList);\n        //发送订阅报文\n        MqttSubscribeMessage mqttSubscribeMessage = new MqttSubscribeMessage(mqttFixedHeader, variableHeader, mqttSubscribePayload);\n        channel.writeAndFlush(mqttSubscribeMessage);\n    }\n\n\n    @Override\n    public void suback(Channel channel, MqttSubAckMessage mqttSubAckMessage) {\n        //原因码集合\n        List<Integer> serverQosList = mqttSubAckMessage.payload().reasonCodes();\n        MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader = mqttSubAckMessage.idAndPropertiesVariableHeader();\n        int msgId = mqttVariableHeader.messageId();\n        MqttProperties mqttProperties = mqttVariableHeader.properties();\n        List<MqttSubscribeCallbackInfo> subscribeCallbackInfoList = new ArrayList<>();\n        //获取Future待会进行唤醒\n        MqttFuture<Void> subscribeFuture = MqttFuture.getFuture(clientId, msgId);\n        //订阅信息集合\n        List<MqttSubInfo> mqttSubInfoList;\n        if (subscribeFuture != null) {\n            //只要Future存在则设置成功，具体的失败在响应回调中\n            subscribeFuture.setSuccess(null);\n            //拿到原始的订阅主题集合\n            mqttSubInfoList = ((MqttSubMsg) subscribeFuture.getParameter()).getMqttSubInfoList();\n            if (EmptyUtils.isNotEmpty(mqttSubInfoList) && mqttSubInfoList.size() == serverQosList.size()) {\n                //遍历Broker响应的QOS，与原来的主题进行关联\n                for (int i = 0; i < serverQosList.size(); i++) {\n                    int serverQos = serverQosList.get(i);\n                    MqttQoS qoS = MqttQoS.valueOf(serverQos);\n                    MqttSubscribeCallbackInfo mqttSubscribeCallbackInfo = new MqttSubscribeCallbackInfo();\n                    mqttSubscribeCallbackInfo.setServerQos(qoS);\n                    if (MqttQoS.AT_MOST_ONCE.equals(qoS) || MqttQoS.AT_LEAST_ONCE.equals(qoS) || MqttQoS.EXACTLY_ONCE.equals(qoS)) {\n                        mqttSubscribeCallbackInfo.setSubscribed(true);\n                    } else {\n                        mqttSubscribeCallbackInfo.setSubscribed(false);\n                    }\n                    if (!mqttSubInfoList.isEmpty()) {\n                        MqttSubInfo mqttSubInfo = mqttSubInfoList.get(i);\n                        mqttSubscribeCallbackInfo.setSubscribeTopic(mqttSubInfo.getTopic());\n                        mqttSubscribeCallbackInfo.setSubscribeQos(mqttSubInfo.getQos());\n                    }\n                    subscribeCallbackInfoList.add(mqttSubscribeCallbackInfo);\n                }\n                //订阅回调\n                MqttSubscribeCallbackResult mqttSubscribeCallbackResult = new MqttSubscribeCallbackResult(clientId, msgId, subscribeCallbackInfoList);\n                mqttSubscribeCallbackResult.setMqttVersion(mqttConnectParameter.getMqttVersion());\n                mqttSubscribeCallbackResult.setMqttProperties(mqttProperties);\n                mqttCallback.subscribeCallback(mqttSubscribeCallbackResult);\n            }\n        }\n    }\n\n    @Override\n    public void sendUnsubscribe(Channel channel, MqttUnsubMsg mqttUnsubMsg) {\n        //固定头\n        MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0x02);\n        //取消订阅属性\n        MqttProperties mqttProperties = new MqttProperties();\n        mqttProperties.add(mqttUnsubMsg.getMqttUserProperties());\n        //可变头\n        MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader = new MqttMessageIdAndPropertiesVariableHeader(mqttUnsubMsg.getMsgId(), mqttProperties);\n        //载荷\n        MqttUnsubscribePayload MqttUnsubscribeMessage = new MqttUnsubscribePayload(mqttUnsubMsg.getTopicList());\n        //取消订阅报文\n        MqttUnsubscribeMessage mqttUnsubscribeMessage = new MqttUnsubscribeMessage(mqttFixedHeader, mqttVariableHeader, MqttUnsubscribeMessage);\n        channel.writeAndFlush(mqttUnsubscribeMessage);\n    }\n\n    @Override\n    public void unsuback(Channel channel, MqttUnsubAckMessage mqttUnsubAckMessage) {\n        MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader = mqttUnsubAckMessage.idAndPropertiesVariableHeader();\n        int msgId = mqttVariableHeader.messageId();\n        MqttProperties mqttProperties = mqttVariableHeader.properties();\n        //从Future中获取原始的取消订阅主题集合\n        MqttFuture<Void> unsubscribeFuture = MqttFuture.getFuture(clientId, msgId);\n        List<String> topicList;\n        if (unsubscribeFuture != null) {\n            //取消订阅都为成功，具体的失败在回调结果中\n            unsubscribeFuture.setSuccess(null);\n            topicList = ((MqttUnsubMsg) unsubscribeFuture.getParameter()).getTopicList();\n            if (EmptyUtils.isNotEmpty(topicList)) {\n                MqttUnsubAckPayload mqttUnsubAckPayload = mqttUnsubAckMessage.payload();\n                MqttUnSubscribeCallbackResult mqttUnSubscribeCallbackResult;\n                //创建回调结果\n                if (mqttUnsubAckPayload != null) {\n                    List<Short> unsubscribeReasonCodeList = mqttUnsubAckPayload.unsubscribeReasonCodes();\n                    mqttUnSubscribeCallbackResult = new MqttUnSubscribeCallbackResult(clientId, msgId, topicList, unsubscribeReasonCodeList);\n                    mqttUnSubscribeCallbackResult.setMqttProperties(mqttProperties);\n                } else {\n                    mqttUnSubscribeCallbackResult = new MqttUnSubscribeCallbackResult(clientId, msgId, topicList);\n                }\n                //回调\n                mqttCallback.unsubscribeCallback(mqttUnSubscribeCallbackResult);\n            }\n        }\n    }\n\n    @Override\n    public void sendPingreq(Channel channel) {\n        //固定头\n        MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGREQ, false, MqttQoS.AT_MOST_ONCE, false, 0);\n        //心跳消息\n        MqttMessage mqttMessage = new MqttMessage(fixedHeader);\n        channel.writeAndFlush(mqttMessage);\n    }\n\n    @Override\n    public void pingresp(Channel channel, MqttMessage mqttPingRespMessage) {\n        mqttCallback.heartbeatCallback(new MqttHeartbeatCallbackResult(clientId));\n    }\n\n    @Override\n    public void sendPublish(Channel channel, MqttMsg mqttMsg, MqttFuture msgFuture) {\n        MqttQoS qos = mqttMsg.getQos();\n        int msgId = mqttMsg.getMsgId();\n        //别名处理\n        String topic = topicAliasHandle(channel, mqttMsg);\n        //固定头\n        MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, mqttMsg.isDup(), qos, mqttMsg.isRetain(), 0);\n        //可变头\n        MqttPublishVariableHeader mqttVariableHeader = new MqttPublishVariableHeader(topic, msgId, mqttMsg.getMqttProperties());\n        //消息\n        MqttPublishMessage mqttPublishMessage = new MqttPublishMessage(mqttFixedHeader, mqttVariableHeader, PooledByteBufAllocator.DEFAULT.directBuffer().writeBytes(mqttMsg.getPayload()));\n        //qos为0时，直接回调即可，因为不保证送达\n        if (MqttQoS.AT_MOST_ONCE == qos) {\n            channel.writeAndFlush(mqttPublishMessage).addListener(future -> {\n                msgFuture.setSuccess(null);\n                mqttCallback.messageSendCallback(new MqttSendCallbackResult(clientId, mqttMsg));\n            });\n        } else if (MqttQoS.AT_LEAST_ONCE == qos || MqttQoS.EXACTLY_ONCE == qos) {\n            channel.writeAndFlush(mqttPublishMessage);\n        }\n    }\n\n\n    @Override\n    public void publish(Channel channel, MqttPublishMessage mqttPublishMessage) {\n        //publish报文的基本信息\n        MqttPublishVariableHeader mqttVariableHeader = mqttPublishMessage.variableHeader();\n        MqttFixedHeader mqttFixedHeader = mqttPublishMessage.fixedHeader();\n        int msgId = mqttVariableHeader.packetId();\n        MqttProperties mqttProperties = mqttVariableHeader.properties();\n        String topic = mqttVariableHeader.topicName();\n        MqttQoS qos = mqttFixedHeader.qosLevel();\n        boolean retain = mqttFixedHeader.isRetain();\n        boolean dup = mqttFixedHeader.isDup();\n        ByteBuf byteBuf = mqttPublishMessage.payload();\n        byte[] payload = ByteBufUtil.getBytes(byteBuf);\n        MqttMsg mqttMsg = new MqttMsg(msgId, payload, topic, qos, retain, dup);\n        mqttMsg.setMqttMsgDirection(MqttMsgDirection.RECEIVE);\n        mqttMsg.setMqttProperties(mqttProperties);\n        if (MqttQoS.AT_MOST_ONCE == qos) {\n            mqttMsg.setMsgState(MqttMsgState.PUBLISH);\n            //qos为0的直接回调\n            MqttReceiveCallbackResult mqttReceiveCallbackResult = new MqttReceiveCallbackResult(clientId, mqttMsg);\n            mqttReceiveCallbackResult.setMqttVersion(mqttConnectParameter.getMqttVersion());\n            mqttCallback.messageReceiveCallback(mqttReceiveCallbackResult);\n        } else if (MqttQoS.AT_LEAST_ONCE == qos) {\n            mqttMsg.setMsgState(MqttMsgState.PUBACK);\n            //qos为1的 发送响应报文后也回调\n            sendPuback(channel, mqttMsg);\n        } else if (MqttQoS.EXACTLY_ONCE == qos) {\n            mqttMsg.setMsgState(MqttMsgState.PUBREC);\n            //qos2的消息需要先存储\n            putReceiveQos2Msg(channel, mqttMsg);\n            sendPubrec(channel, mqttMsg);\n        }\n    }\n\n    @Override\n    public void sendPuback(Channel channel, MqttMsg mqttMsg) {\n        MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, mqttMsg.isDup(), MqttQoS.AT_MOST_ONCE, mqttMsg.isRetain(), 0x02);\n        MqttMessageIdVariableHeader from = MqttMessageIdVariableHeader.from(mqttMsg.getMsgId());\n        //发送puback报文\n        MqttPubAckMessage mqttPubAckMessage = new MqttPubAckMessage(mqttFixedHeader, from);\n        channel.writeAndFlush(mqttPubAckMessage).addListener(future -> {\n            //发送报文出去后直接回调\n            MqttReceiveCallbackResult mqttReceiveCallbackResult = new MqttReceiveCallbackResult(clientId, mqttMsg);\n            mqttReceiveCallbackResult.setMqttVersion(mqttConnectParameter.getMqttVersion());\n            mqttCallback.messageReceiveCallback(mqttReceiveCallbackResult);\n        });\n    }\n\n    @Override\n    public void puback(Channel channel, MqttPubAckMessage mqttPubAckMessage) {\n        MqttPubReplyMessageVariableHeader mqttVariableHeader = (MqttPubReplyMessageVariableHeader) mqttPubAckMessage.variableHeader();\n        MqttFixedHeader mqttFixedHeader = mqttPubAckMessage.fixedHeader();\n        int msgId = mqttVariableHeader.messageId();\n        MqttProperties mqttProperties = mqttVariableHeader.properties();\n        logReceiveMsgReasonStringIfNecessary(msgId, mqttProperties, mqttFixedHeader.messageType());\n        MqttFuture sendFuture = MqttFuture.getFuture(clientId, msgId);\n        MqttMsg mqttMsg = removeHighQosMsg(channel, msgId, MqttMsgDirection.SEND);\n        if (sendFuture != null) {\n            sendFuture.setSuccess(null);\n        }\n        //不存在mqttMsg则表示重复接受\n        if (mqttMsg != null) {\n            byte reasonCode = mqttVariableHeader.reasonCode();\n            mqttMsg.setMsgState(MqttMsgState.PUBACK);\n            mqttMsg.setReasonCode(reasonCode);\n            mqttCallback.messageSendCallback(new MqttSendCallbackResult(clientId, mqttMsg));\n        }\n    }\n\n    @Override\n    public void sendPubrec(Channel channel, MqttMsg mqttMsg) {\n        MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, MqttQoS.AT_MOST_ONCE, false, 0x02);\n        MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(mqttMsg.getMsgId());\n        //发送pubrec报文\n        MqttMessage mqttMessage = new MqttMessage(mqttFixedHeader, mqttVariableHeader);\n        channel.writeAndFlush(mqttMessage);\n    }\n\n\n    @Override\n    public void pubrec(Channel channel, MqttMessage mqttMessage) {\n        //pubrec的基本信息\n        MqttPubReplyMessageVariableHeader mqttVariableHeader = (MqttPubReplyMessageVariableHeader) mqttMessage.variableHeader();\n        MqttFixedHeader mqttFixedHeader = mqttMessage.fixedHeader();\n        int msgId = mqttVariableHeader.messageId();\n        byte reasonCode = mqttVariableHeader.reasonCode();\n        MqttProperties mqttProperties = mqttVariableHeader.properties();\n        logReceiveMsgReasonStringIfNecessary(msgId, mqttProperties, mqttFixedHeader.messageType());\n        MqttMsg mqttMsg = getQos2SendMsg(channel, msgId);\n        if (mqttMsg != null) {\n            updateQos2MsgState(channel, msgId, MqttMsgState.PUBREL, MqttMsgDirection.SEND);\n        } else {\n            LogUtils.warn(DefaultMqttDelegateHandler.class, \"client: \" + clientId + \"received an unstored illegal pubrec packet with message ID \" + msgId);\n            mqttMsg = new MqttMsg(msgId, null, null, null, MqttMsgState.INVALID);\n        }\n        mqttMsg.setReasonCode(reasonCode);\n        //不管有没有错误都响应pubrel报文，让其继续\n        sendPubrel(channel, mqttMsg);\n    }\n\n    @Override\n    public void sendPubrel(Channel channel, MqttMsg mqttMsg) {\n        //发送pubrel报文\n        MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBREL, false, MqttQoS.AT_LEAST_ONCE, false, 0x02);\n        MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(mqttMsg.getMsgId());\n        MqttMessage mqttMessage = new MqttMessage(mqttFixedHeader, mqttVariableHeader);\n        channel.writeAndFlush(mqttMessage);\n    }\n\n    @Override\n    public void pubrel(Channel channel, MqttMessage mqttMessage) {\n        MqttPubReplyMessageVariableHeader mqttVariableHeader = (MqttPubReplyMessageVariableHeader) mqttMessage.variableHeader();\n        MqttFixedHeader mqttFixedHeader = mqttMessage.fixedHeader();\n        int msgId = mqttVariableHeader.messageId();\n        byte reasonCode = mqttVariableHeader.reasonCode();\n        MqttProperties mqttProperties = mqttVariableHeader.properties();\n        logReceiveMsgReasonStringIfNecessary(msgId, mqttProperties, mqttFixedHeader.messageType());\n        MqttMsg mqttMsg = removeHighQosMsg(channel, msgId, MqttMsgDirection.RECEIVE);\n        if (mqttMsg != null) {\n            mqttMsg.setMsgState(MqttMsgState.PUBCOMP);\n        } else {\n            LogUtils.warn(DefaultMqttDelegateHandler.class, \"client: \" + clientId + \"received an unstored illegal pubrel packet with message ID \" + msgId);\n            mqttMsg = new MqttMsg(msgId, null, null, null, MqttMsgState.INVALID);\n        }\n        mqttMsg.setReasonCode(reasonCode);\n        sendPubcomp(channel, mqttMsg);\n    }\n\n\n    @Override\n    public void sendPubcomp(Channel channel, MqttMsg mqttMsg) {\n        //发送pubcomp报文\n        MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0x02);\n        MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(mqttMsg.getMsgId());\n        MqttMessage mqttMessage = new MqttMessage(mqttFixedHeader, mqttVariableHeader);\n        channel.writeAndFlush(mqttMessage).addListener(future -> {\n            //pubcomp发送出去后回调\n            if (MqttMsgState.INVALID != mqttMsg.getMsgState()) {\n                MqttReceiveCallbackResult mqttReceiveCallbackResult = new MqttReceiveCallbackResult(clientId, mqttMsg);\n                mqttReceiveCallbackResult.setMqttVersion(mqttConnectParameter.getMqttVersion());\n                mqttCallback.messageReceiveCallback(mqttReceiveCallbackResult);\n            }\n        });\n    }\n\n\n    @Override\n    public void pubcomp(Channel channel, MqttMessage mqttMessage) {\n        MqttPubReplyMessageVariableHeader mqttVariableHeader = (MqttPubReplyMessageVariableHeader) mqttMessage.variableHeader();\n        MqttFixedHeader mqttFixedHeader = mqttMessage.fixedHeader();\n        int msgId = mqttVariableHeader.messageId();\n        byte reasonCode = mqttVariableHeader.reasonCode();\n        MqttProperties mqttProperties = mqttVariableHeader.properties();\n        logReceiveMsgReasonStringIfNecessary(msgId, mqttProperties, mqttFixedHeader.messageType());\n        //pubcomp消息，则为客户端发送，删除\n        MqttMsg mqttMsg = removeHighQosMsg(channel, msgId, MqttMsgDirection.SEND);\n        //必须在移除之后，否则会被异步的兜底监听删除\n        MqttFuture sendFuture = MqttFuture.getFuture(clientId, msgId);\n        if (sendFuture != null) {\n            sendFuture.setSuccess(null);\n        }\n        if (mqttMsg != null) {\n            mqttMsg.setReasonCode(reasonCode);\n            mqttMsg.setMsgState(MqttMsgState.PUBCOMP);\n            //回调\n            mqttCallback.messageSendCallback(new MqttSendCallbackResult(clientId, mqttMsg));\n        }\n    }\n\n    @Override\n    public void exceptionCaught(Channel channel, Throwable cause) {\n        //异常回调\n        MqttAuthState mqttAuthState = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.AUTH_STATE_ATTRIBUTE_KEY).get();\n        MqttChannelExceptionCallbackResult mqttChannelExceptionCallbackResult = new MqttChannelExceptionCallbackResult(clientId, mqttAuthState, cause);\n        mqttCallback.channelExceptionCaught(mqttConnectParameter, mqttChannelExceptionCallbackResult);\n    }\n\n\n    /**\n     * 转换为Netty的Mqtt订阅消息集合\n     *\n     * @param mqttSubInfoList MQTT订阅消息集合\n     * @return Netty的Mqtt订阅消息集合\n     */\n    private List<MqttTopicSubscription> toMqttTopicSubscriptionList(List<MqttSubInfo> mqttSubInfoList) {\n        List<MqttTopicSubscription> mqttTopicSubscriptionList = new ArrayList<>(mqttSubInfoList.size());\n        for (MqttSubInfo mqttSubInfo : mqttSubInfoList) {\n            MqttSubscriptionOption mqttSubscriptionOption = new MqttSubscriptionOption(mqttSubInfo.getQos(), mqttSubInfo.isNoLocal(), mqttSubInfo.isRetainAsPublished(), mqttSubInfo.getRetainHandling());\n            MqttTopicSubscription mqttTopicSubscription = new MqttTopicSubscription(mqttSubInfo.getTopic(), mqttSubscriptionOption);\n            mqttTopicSubscriptionList.add(mqttTopicSubscription);\n        }\n        return mqttTopicSubscriptionList;\n    }\n\n    /**\n     * 获取QOS2的发送消息（因为qos2的消息需要存储）\n     *\n     * @param channel 通道\n     * @param msgId   消息ID\n     * @return MqttMsg\n     */\n    private MqttMsg getQos2SendMsg(Channel channel, int msgId) {\n        MqttMsg mqttMsg = null;\n        if (mqttConnectParameter.isCleanSession()) {\n            //清理会话直接从Channel中获取\n            Object msg = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().get(msgId);\n            if (msg instanceof MqttMsg) {\n                mqttMsg = (MqttMsg) msg;\n            }\n        } else {\n            //不清理会话从消息存储器中获取\n            mqttMsg = mqttMsgStore.getMsg(MqttMsgDirection.SEND, clientId, msgId);\n        }\n        return mqttMsg;\n    }\n\n    private void putReceiveQos2Msg(Channel channel, MqttMsg mqttMsg) {\n        if (mqttConnectParameter.isCleanSession()) {\n            channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.RECEIVE_MSG_MAP_ATTRIBUTE_KEY).get().put(mqttMsg.getMsgId(), mqttMsg);\n        } else {\n            mqttMsgStore.putMsg(MqttMsgDirection.RECEIVE, clientId, mqttMsg);\n        }\n    }\n\n    private MqttMsg removeHighQosMsg(Channel channel, int msgId, MqttMsgDirection msgDirection) {\n        MqttMsg mqttMsg = null;\n        if (mqttConnectParameter.isCleanSession()) {\n            if (msgDirection == MqttMsgDirection.SEND) {\n                Object msg = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().remove(msgId);\n                if (msg instanceof MqttMsg) {\n                    mqttMsg = (MqttMsg) msg;\n                }\n            } else {\n                mqttMsg = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.RECEIVE_MSG_MAP_ATTRIBUTE_KEY).get().remove(msgId);\n            }\n        } else {\n            mqttMsg = mqttMsgStore.removeMsg(msgDirection, clientId, msgId);\n        }\n        return mqttMsg;\n    }\n\n    /**\n     * 更新QOS2的消息状态\n     *\n     * @param channel      channel\n     * @param msgId        消息ID\n     * @param msgState     消息状态\n     * @param msgDirection 消息方向\n     */\n    private void updateQos2MsgState(Channel channel, int msgId, MqttMsgState msgState, MqttMsgDirection msgDirection) {\n        if (mqttConnectParameter.isCleanSession()) {\n            //清除会话则从Channel中获取\n            if (msgDirection == MqttMsgDirection.SEND) {\n                Object msg = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.SEND_MSG_MAP_ATTRIBUTE_KEY).get().get(msgId);\n                if (msg instanceof MqttMsg) {\n                    MqttMsg mqttMsg = (MqttMsg) msg;\n                    mqttMsg.setMsgState(msgState);\n                }\n            } else {\n                MqttMsg mqttMsg = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.RECEIVE_MSG_MAP_ATTRIBUTE_KEY).get().get(msgId);\n                if (mqttMsg != null) {\n                    mqttMsg.setMsgState(msgState);\n                }\n            }\n        } else {\n            //不清理会话从消息存储器中获取并更新\n            MqttMsg mqttMsg = mqttMsgStore.getMsg(msgDirection, clientId, msgId);\n            if (mqttMsg != null) {\n                mqttMsg.setMsgState(msgState);\n                mqttMsgStore.putMsg(msgDirection, clientId, mqttMsg);\n            }\n        }\n    }\n\n    /**\n     * 字符串添加响应字符串\n     *\n     * @param content        内容\n     * @param mqttProperties MQTT用户属性\n     * @return 拼接后的字符串\n     */\n    private String addReasonString(String content, MqttProperties mqttProperties) {\n        String reasonString = MqttUtils.getReasonString(mqttProperties);\n        if (EmptyUtils.isNotBlank(reasonString)) {\n            content = content + \",reasonString :\" + reasonString;\n        }\n        return content;\n    }\n\n\n    /**\n     * 主题别名处理，返回新的主题\n     *\n     * @param channel 通道\n     * @param mqttMsg MQTT消息\n     * @return 主题名\n     */\n    private String topicAliasHandle(Channel channel, MqttMsg mqttMsg) {\n        String topic = mqttMsg.getTopic();\n        MqttProperties mqttProperties = mqttMsg.getMqttProperties();\n        if (mqttProperties != null && mqttConnectParameter.getMqttVersion() == io.github.netty.mqtt.client.constant.MqttVersion.MQTT_5_0_0) {\n            MqttProperties.IntegerProperty topicAliasProperty = (MqttProperties.IntegerProperty) mqttProperties.getProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS.value());\n            Attribute<Map<String, Integer>> topicAliasMapAttribute = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.TOPIC_ALIAS_MAP_ATTRIBUTE_KEY);\n            if (topicAliasProperty != null && topicAliasMapAttribute != null) {\n                int nowTopicAlias = topicAliasProperty.value();\n                Map<String, Integer> topicAliasMap = topicAliasMapAttribute.get();\n                //对主题名加锁，防止并发时，主题别名值被修改\n                synchronized (topic.intern()) {\n                    Integer topicAlias = topicAliasMap.get(topic);\n                    if (topicAlias != null) {\n                        if (topicAlias.equals(nowTopicAlias)) {\n                            //别名一致，返回空字符作为主题\n                            topic = io.github.netty.mqtt.client.constant.MqttConstant.EMPTY_STR;\n                        } else {\n                            //别名不一致时，使用现在的替换\n                            topicAliasMap.put(topic, nowTopicAlias);\n                        }\n                    } else {\n                        //暂存别名，下一次才替换主题\n                        topicAliasMap.put(topic, nowTopicAlias);\n                    }\n                }\n            }\n        }\n        return topic;\n    }\n\n    /**\n     * 打印接受消息的原因字符串（存在的时候）\n     *\n     * @param messageId       消息ID\n     * @param mqttProperties  MQTT用户属性\n     * @param mqttMessageType 消息类型\n     */\n    private void logReceiveMsgReasonStringIfNecessary(int messageId, MqttProperties mqttProperties, MqttMessageType mqttMessageType) {\n        if (mqttProperties != null) {\n            MqttProperties.StringProperty reasonStringProperty = (MqttProperties.StringProperty) mqttProperties.getProperty(MqttProperties.MqttPropertyType.REASON_STRING.value());\n            if (reasonStringProperty != null) {\n                String value = reasonStringProperty.value();\n                if (EmptyUtils.isNotBlank(value)) {\n                    LogUtils.warn(DefaultMqttDelegateHandler.class, \"client:\" + mqttConnectParameter.getClientId() + \",received an exception with a message id of \" + messageId + \" and a message type of \" + mqttMessageType + \",reason string :\" + value);\n                }\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/handler/MqttDelegateHandler.java",
    "content": "package io.github.netty.mqtt.client.handler;\n\n\nimport io.github.netty.mqtt.client.msg.MqttDisconnectMsg;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.github.netty.mqtt.client.msg.MqttSubMsg;\nimport io.github.netty.mqtt.client.msg.MqttUnsubMsg;\nimport io.github.netty.mqtt.client.support.future.MqttFuture;\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.mqtt.*;\n\n/**\n * MQTT委托处理器接口\n * @author: xzc-coder\n */\npublic interface MqttDelegateHandler {\n\n    /**\n     * 接收一个TCP建立连接\n     *\n     * @param channel 通道\n     */\n    void channelConnect(Channel channel);\n\n    /**\n     * 发送一个MQTT连接\n     *\n     * @param channel 通道\n     */\n    void sendConnect(Channel channel);\n\n    /**\n     * 收到一个MQTT connack\n     *\n     * @param channel            通道\n     * @param mqttConnAckMessage MQTT确认消息\n     */\n    void connack(Channel channel, MqttConnAckMessage mqttConnAckMessage);\n\n    /**\n     * 收到一个MQTT auth\n     *\n     * @param channel         通道\n     * @param mqttAuthMessage MQTT认证消息\n     */\n    void auth(Channel channel, MqttMessage mqttAuthMessage);\n\n    /**\n     * 发送一个MQTT认证\n     *\n     * @param channel 通道\n     * @param reasonCode 原因码\n     * @param mqttProperties MQTT属性\n     */\n    void sendAuth(Channel channel, byte reasonCode, MqttProperties mqttProperties);\n\n\n    /**\n     * 发送一个MQTT 断开连接\n     *\n     * @param channel           通道\n     * @param mqttFuture        MQTTFuture\n     * @param mqttDisconnectMsg MQTT断开消息\n     */\n    void sendDisconnect(Channel channel, MqttFuture mqttFuture, MqttDisconnectMsg mqttDisconnectMsg);\n\n    /**\n     * 接收到一个TCP断开连接\n     *\n     * @param channel     通道\n     * @param mqttMessage MQTT消息\n     */\n    void disconnect(Channel channel, MqttMessage mqttMessage);\n\n    /**\n     * 发送一个MQTT订阅\n     *\n     * @param channel    通道\n     * @param mqttSubMsg MQTT订阅消息\n     */\n    void sendSubscribe(Channel channel, MqttSubMsg mqttSubMsg);\n\n    /**\n     * 收到一个MQTT suback\n     *\n     * @param channel           通道\n     * @param mqttSubAckMessage MQTT订阅确认消息\n     */\n    void suback(Channel channel, MqttSubAckMessage mqttSubAckMessage);\n\n    /**\n     * 发送一个MQTT 取消订阅\n     *\n     * @param channel      Channel\n     * @param mqttUnsubMsg MQTT取消订阅消息\n     */\n    void sendUnsubscribe(Channel channel, MqttUnsubMsg mqttUnsubMsg);\n\n    /**\n     * 收到一个MQTT unsuback\n     *\n     * @param channel             通道\n     * @param mqttUnsubAckMessage MQTT取消订阅确认消息\n     */\n    void unsuback(Channel channel, MqttUnsubAckMessage mqttUnsubAckMessage);\n\n    /**\n     * 发送一个MQTT pingreq\n     *\n     * @param channel 通道\n     */\n    void sendPingreq(Channel channel);\n\n    /**\n     * 接收到一个MQTT pingresp\n     *\n     * @param channel             通道\n     * @param mqttPingRespMessage MQTT消息\n     */\n    void pingresp(Channel channel, MqttMessage mqttPingRespMessage);\n\n    /**\n     * 发送一个MQTT publish\n     *\n     * @param channel   通道\n     * @param mqttMsg   MQTT消息\n     * @param msgFuture MqttFuture(只有qos 为0 的消息需要)\n     */\n    void sendPublish(Channel channel, MqttMsg mqttMsg, MqttFuture msgFuture);\n\n    /**\n     * 接收到MQTT publish\n     *\n     * @param channel            通道\n     * @param mqttPublishMessage MQTT发布消息\n     */\n    void publish(Channel channel, MqttPublishMessage mqttPublishMessage);\n\n    /**\n     * 发送一个MQTT puback\n     *\n     * @param channel 通道\n     * @param mqttMsg MqttMsg\n     */\n    void sendPuback(Channel channel, MqttMsg mqttMsg);\n\n    /**\n     * 接收到一个MQTT puback\n     *\n     * @param channel           通道\n     * @param mqttPubAckMessage MQTT发布确认消息\n     */\n    void puback(Channel channel, MqttPubAckMessage mqttPubAckMessage);\n\n    /**\n     * 发送一个MQTT pubrec\n     *\n     * @param channel 通道\n     * @param mqttMsg MqttMsg\n     */\n    void sendPubrec(Channel channel, MqttMsg mqttMsg);\n\n    /**\n     * 接收到一个MQTT pubrec\n     *\n     * @param channel     通道\n     * @param mqttMessage MQTT消息\n     */\n    void pubrec(Channel channel, MqttMessage mqttMessage);\n\n    /**\n     * 发送一个MQTT pubrel\n     *\n     * @param channel 通道\n     * @param mqttMsg MQTT消息\n     */\n    void sendPubrel(Channel channel, MqttMsg mqttMsg);\n\n    /**\n     * 接收一个MQTT pubrel\n     *\n     * @param channel     通道\n     * @param mqttMessage MQTT消息\n     */\n    void pubrel(Channel channel, MqttMessage mqttMessage);\n\n    /**\n     * 发送一个MQTT pubcomp\n     *\n     * @param channel 通道\n     * @param mqttMsg MQTT消息\n     */\n    void sendPubcomp(Channel channel, MqttMsg mqttMsg);\n\n    /**\n     * 接收一个MQTT pubcomp\n     *\n     * @param channel     通道\n     * @param mqttMessage MQTT消息\n     */\n    void pubcomp(Channel channel, MqttMessage mqttMessage);\n\n    /**\n     * 接收到一个Channel异常\n     *\n     * @param channel 通道\n     * @param cause 异常\n     */\n    void exceptionCaught(Channel channel, Throwable cause);\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/handler/channel/MqttChannelHandler.java",
    "content": "package io.github.netty.mqtt.client.handler.channel;\n\n\nimport io.github.netty.mqtt.client.MqttConnectParameter;\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.exception.MqttException;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.LogUtils;\nimport io.github.netty.mqtt.client.support.util.MqttUtils;\nimport io.netty.channel.*;\nimport io.netty.handler.codec.DecoderResult;\nimport io.netty.handler.codec.mqtt.*;\nimport io.netty.handler.timeout.IdleState;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport io.netty.handler.timeout.IdleStateHandler;\n\nimport java.math.BigDecimal;\nimport java.net.SocketAddress;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * MQTT在Netty中的ChannelHandler处理器\n * @author: xzc-coder\n */\n@ChannelHandler.Sharable\npublic final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> implements ChannelOutboundHandler {\n\n    /**\n     * MQTT消息委托器\n     */\n    private final MqttDelegateHandler mqttDelegateHandler;\n\n    /**\n     * MQTT连接参数\n     */\n    private final MqttConnectParameter mqttConnectParameter;\n\n    public MqttChannelHandler(MqttDelegateHandler mqttDelegateHandler, MqttConnectParameter mqttConnectParameter) {\n        AssertUtils.notNull(mqttDelegateHandler, \"mqttDelegateHandler is null\");\n        AssertUtils.notNull(mqttConnectParameter, \"mqttConnectParameter is null\");\n        this.mqttDelegateHandler = mqttDelegateHandler;\n        this.mqttConnectParameter = mqttConnectParameter;\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        Channel channel = ctx.channel();\n        String clientId = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.MQTT_CLIENT_ID_ATTRIBUTE_KEY).get();\n        LogUtils.info(MqttChannelHandler.class, \"client:\" + clientId + \" tcp connection successful,local:\" + channel.localAddress() + \",remote:\" + channel.remoteAddress());\n        mqttDelegateHandler.channelConnect(channel);\n    }\n\n    @Override\n    protected void channelRead0(ChannelHandlerContext ctx, MqttMessage mqttMessage) throws Exception {\n        Channel channel = ctx.channel();\n        String clientId = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.MQTT_CLIENT_ID_ATTRIBUTE_KEY).get();\n        DecoderResult decoderResult = mqttMessage.decoderResult();\n        if (decoderResult.isSuccess()) {\n            MqttFixedHeader mqttFixedHeader = mqttMessage.fixedHeader();\n            MqttMessageType mqttMessageType = mqttFixedHeader.messageType();\n            LogUtils.debug(MqttChannelHandler.class, \"client:\" + clientId + \", read mqtt \" + mqttMessageType + \" package：\" + mqttMessage);\n            switch (mqttMessageType) {\n                case CONNACK:\n                    //连接确认\n                    MqttConnAckMessage mqttConnAckMessage = (MqttConnAckMessage) mqttMessage;\n                    //成功则启动心跳定时任务\n                    if (MqttConnectReturnCode.CONNECTION_ACCEPTED.equals(mqttConnAckMessage.variableHeader().connectReturnCode())) {\n                        //连接成功处理\n                        connectSuccessHandle(channel,mqttConnAckMessage);\n                    }\n                    mqttDelegateHandler.connack(channel, mqttConnAckMessage);\n                    break;\n                case DISCONNECT:\n                    mqttDelegateHandler.disconnect(channel, mqttMessage);\n                    break;\n                case AUTH:\n                    mqttDelegateHandler.auth(channel,mqttMessage);\n                    break;\n                case SUBACK:\n                    mqttDelegateHandler.suback(channel, (MqttSubAckMessage) mqttMessage);\n                    break;\n                case UNSUBACK:\n                    mqttDelegateHandler.unsuback(channel, (MqttUnsubAckMessage) mqttMessage);\n                    break;\n                case PINGRESP:\n                    mqttDelegateHandler.pingresp(channel, mqttMessage);\n                    break;\n                case PUBLISH:\n                    mqttDelegateHandler.publish(channel, (MqttPublishMessage) mqttMessage);\n                    break;\n                case PUBACK:\n                    mqttDelegateHandler.puback(channel, (MqttPubAckMessage) mqttMessage);\n                    break;\n                case PUBREC:\n                    mqttDelegateHandler.pubrec(channel, mqttMessage);\n                    break;\n                case PUBREL:\n                    mqttDelegateHandler.pubrel(channel, mqttMessage);\n                    break;\n                case PUBCOMP:\n                    mqttDelegateHandler.pubcomp(channel, mqttMessage);\n                    break;\n                default:\n                    LogUtils.warn(MqttChannelHandler.class, \"client: \" + clientId + \" received a location type message\");\n            }\n        } else {\n            throw new MqttException(decoderResult.cause(),clientId);\n        }\n    }\n\n\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n        Channel channel = ctx.channel();\n        String clientId = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.MQTT_CLIENT_ID_ATTRIBUTE_KEY).get();\n        LogUtils.info(MqttChannelHandler.class, \"client:\" + clientId + \" tcp disconnected,local:\" + channel.localAddress() + \",remote:\" + channel.remoteAddress());\n        mqttDelegateHandler.disconnect(ctx.channel(),null);\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        try {\n            Channel channel = ctx.channel();\n            String clientId = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.MQTT_CLIENT_ID_ATTRIBUTE_KEY).get();\n            LogUtils.error(MqttChannelHandler.class, \"client:\" + clientId + \" encountered an exception in the channel,excepiton:\" + cause.getMessage());\n            mqttDelegateHandler.exceptionCaught(ctx.channel(), cause);\n        } finally {\n            ctx.channel().close();\n        }\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof IdleStateEvent) {\n            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;\n            if (IdleState.READER_IDLE == idleStateEvent.state()) {\n                LogUtils.warn(MqttChannelHandler.class, \"client:\" + mqttConnectParameter.getClientId() + \" readOutTime,will disconnect.\");\n                ctx.close();\n            }\n        }\n    }\n\n    @Override\n    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {\n        ctx.bind(localAddress, promise);\n    }\n\n    @Override\n    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {\n        ctx.connect(remoteAddress, localAddress, promise);\n    }\n\n    @Override\n    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n        ctx.disconnect(promise);\n    }\n\n    @Override\n    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n        ctx.close(promise);\n    }\n\n    @Override\n    public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n        ctx.deregister(promise);\n    }\n\n    @Override\n    public void read(ChannelHandlerContext ctx) throws Exception {\n        ctx.read();\n    }\n\n    @Override\n    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n        Channel channel = ctx.channel();\n        String clientId = channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.MQTT_CLIENT_ID_ATTRIBUTE_KEY).get();\n        if (msg instanceof MqttMessage) {\n            MqttMessage mqttMessage = (MqttMessage) msg;\n            MqttMessageType mqttMessageType = mqttMessage.fixedHeader().messageType();\n            LogUtils.debug(MqttChannelHandler.class, \"client:\" + clientId + \", write mqtt \" + mqttMessageType + \" package：\" + mqttMessage);\n        }\n        ctx.write(msg, promise);\n    }\n\n    @Override\n    public void flush(ChannelHandlerContext ctx) throws Exception {\n        ctx.flush();\n    }\n\n\n    private void connectSuccessHandle(Channel channel, MqttConnAckMessage mqttConnAckMessage) {\n        //获取并设置心跳间隔\n        Integer keepAliveTimeSeconds = MqttUtils.getIntegerMqttPropertyValue(mqttConnAckMessage.variableHeader().properties(), MqttProperties.MqttPropertyType.SERVER_KEEP_ALIVE, mqttConnectParameter.getKeepAliveTimeSeconds());\n        channel.attr(io.github.netty.mqtt.client.constant.MqttConstant.KEEP_ALIVE_TIME_ATTRIBUTE_KEY).set(keepAliveTimeSeconds);\n        //1000要加L 不然会溢出\n        long keepAliveTimeMills = keepAliveTimeSeconds * 1000L;\n        //定时任务间隔\n        long scheduleMills = mqttConnectParameter.getKeepAliveTimeCoefficient().multiply(new BigDecimal(keepAliveTimeMills)).longValue();\n        long readIdleMills = keepAliveTimeMills + (keepAliveTimeMills >> 1);\n        //添加一个空闲检测处理器,读检测，即1.5倍的心跳时间内，没有读取到任何数据则断开连接\n        channel.pipeline().addBefore(io.github.netty.mqtt.client.constant.MqttConstant.NETTY_DECODER_HANDLER_NAME, MqttConstant.NETTY_IDLE_HANDLER_NAME, new IdleStateHandler(readIdleMills, 0, 0, TimeUnit.MILLISECONDS));\n        //心跳定时任务间隔执行\n        Runnable task = new Runnable() {\n            @Override\n            public void run() {\n                if (channel.isActive()) {\n                    mqttDelegateHandler.sendPingreq(channel);\n                    channel.eventLoop().schedule(this, scheduleMills, TimeUnit.MILLISECONDS);\n                }\n            }\n        };\n        channel.eventLoop().schedule(task, scheduleMills, TimeUnit.MILLISECONDS);\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/msg/MqttDisconnectMsg.java",
    "content": "package io.github.netty.mqtt.client.msg;\n\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.netty.handler.codec.mqtt.MqttProperties;\n\n/**\n * MQTT5断开连接信息\n * @author: xzc-coder\n */\npublic class MqttDisconnectMsg {\n\n\n    /**\n     * MQTT5\n     * 原因码\n     */\n    private byte reasonCode = MqttConstant.DISCONNECT_SUCCESS_REASON_CODE;\n\n    /**\n     * MQTT5\n     * 会话过期间隔(秒)\n     */\n    private Integer sessionExpiryIntervalSeconds;\n\n    /**\n     * MQTT5\n     * 原因字符串\n     */\n    private String reasonString;\n\n\n    /**\n     * MQTT5\n     * MQTT用户属性\n     */\n    private final MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n\n\n    public MqttProperties.UserProperties getMqttUserProperties() {\n        return mqttUserProperties;\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param key key\n     * @param value value\n     */\n    public void addMqttUserProperty(String key, String value) {\n        if (key != null && value != null) {\n            mqttUserProperties.add(key, value);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param stringPair key value对象\n     */\n    public void addMqttUserProperty(MqttProperties.StringPair stringPair) {\n        if (stringPair != null) {\n            mqttUserProperties.add(stringPair);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param mqttUserProperties MQTT用户属性\n     */\n    private void addMqttUserProperties(MqttProperties.UserProperties mqttUserProperties) {\n        if (mqttUserProperties != null) {\n            for (MqttProperties.StringPair stringPair : mqttUserProperties.value()) {\n                this.mqttUserProperties.add(stringPair);\n            }\n        }\n    }\n\n    public byte getReasonCode() {\n        return reasonCode;\n    }\n\n    public void setReasonCode(byte reasonCode) {\n        this.reasonCode = reasonCode;\n    }\n\n    public Integer getSessionExpiryIntervalSeconds() {\n        return sessionExpiryIntervalSeconds;\n    }\n\n    public void setSessionExpiryIntervalSeconds(Integer sessionExpiryIntervalSeconds) {\n        this.sessionExpiryIntervalSeconds = sessionExpiryIntervalSeconds;\n    }\n\n    public String getReasonString() {\n        return reasonString;\n    }\n\n    public void setReasonString(String reasonString) {\n        this.reasonString = reasonString;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"MqttDisconnectMsg{\" +\n                \"reasonCode=\" + reasonCode +\n                \", sessionExpiryIntervalSeconds=\" + sessionExpiryIntervalSeconds +\n                \", reasonString='\" + reasonString + '\\'' +\n                \", mqttUserProperties=\" + mqttUserProperties +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/msg/MqttMsg.java",
    "content": "package io.github.netty.mqtt.client.msg;\n\n\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.constant.MqttMsgDirection;\nimport io.github.netty.mqtt.client.constant.MqttMsgState;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\n\nimport java.io.Serializable;\nimport java.util.Arrays;\nimport java.util.Objects;\n\n/**\n * MQTT消息\n * @author: xzc-coder\n */\npublic class MqttMsg implements Serializable {\n\n\n    private static final long serialVersionUID = 8823986273348138028L;\n\n    /**\n     * 消息ID\n     */\n    private Integer msgId;\n    /**\n     * 主题\n     */\n    private String topic;\n    /**\n     * qos\n     */\n    private MqttQoS qos;\n    /**\n     * 是否保留消息\n     */\n    private boolean retain;\n    /**\n     * 消息状态\n     */\n    private MqttMsgState msgState;\n    /**\n     * 载荷\n     */\n    private byte[] payload;\n\n    /**\n     * 是否重复发送\n     */\n    private boolean dup;\n    /**\n     * 消息方向\n     */\n    private MqttMsgDirection mqttMsgDirection;\n\n    /**\n     * MQTT5\n     * 发布消息属性，不序列化\n     */\n    private transient MqttProperties mqttProperties;\n\n    /**\n     * 原因码\n     */\n    private byte reasonCode;\n\n    /**\n     * 创建时间戳\n     */\n    private long createTimestamp;\n\n\n    public MqttMsg(int msgId, String topic) {\n        this(msgId, new byte[0], topic);\n    }\n\n    public MqttMsg(int msgId, byte[] payload, String topic) {\n        this(msgId, payload, topic, MqttQoS.AT_MOST_ONCE);\n    }\n\n    public MqttMsg(int msgId, byte[] payload, String topic, MqttQoS qos) {\n        this(msgId, payload, topic, qos, false);\n    }\n\n    public MqttMsg(int msgId, byte[] payload, String topic, MqttQoS qos, boolean retain) {\n        this(msgId, payload, topic, qos, retain, false);\n    }\n\n    public MqttMsg(int msgId, byte[] payload, String topic, MqttQoS qos, boolean retain, MqttMsgDirection mqttMsgDirection) {\n        this(msgId, payload, topic, qos, retain, false, MqttMsgState.PUBLISH, mqttMsgDirection);\n    }\n\n    public MqttMsg(int msgId, byte[] payload, String topic, MqttQoS qos, boolean retain, MqttMsgState msgState) {\n        this(msgId, payload, topic, qos, retain, false, msgState, MqttMsgDirection.SEND);\n    }\n\n    public MqttMsg(int msgId, byte[] payload, String topic, MqttQoS qos, boolean retain, MqttMsgState msgState, MqttMsgDirection mqttMsgDirection) {\n        this(msgId, payload, topic, qos, retain, false, msgState, mqttMsgDirection);\n    }\n\n\n    public MqttMsg(int msgId, byte[] payload, String topic, MqttQoS qos, MqttMsgState msgState) {\n        this(msgId, payload, topic, qos, false, false, msgState, MqttMsgDirection.SEND);\n    }\n\n    public MqttMsg(int msgId, byte[] payload, String topic, MqttQoS qos, MqttMsgDirection mqttMsgDirection) {\n        this(msgId, payload, topic, qos, false, false, MqttMsgState.PUBLISH, mqttMsgDirection);\n    }\n\n    public MqttMsg(int msgId, byte[] payload, String topic, MqttQoS qos, boolean retain, boolean dup) {\n        this(msgId, payload, topic, qos, retain, dup, MqttMsgState.PUBLISH, MqttMsgDirection.SEND);\n    }\n\n    public MqttMsg(int msgId, byte[] payload, String topic, MqttQoS qos, boolean retain, boolean dup, MqttMsgState msgState, MqttMsgDirection mqttMsgDirection) {\n        this.msgId = msgId;\n        this.payload = payload;\n        this.topic = topic;\n        this.qos = qos;\n        this.retain = retain;\n        this.dup = dup;\n        this.msgState = msgState;\n        this.mqttMsgDirection = mqttMsgDirection;\n        this.reasonCode = MqttConstant.MESSAGE_SUCCESS_REASON_CODE;\n        this.createTimestamp = System.currentTimeMillis();\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public MqttQoS getQos() {\n        return qos;\n    }\n\n    public boolean isRetain() {\n        return retain;\n    }\n\n    public MqttMsgState getMsgState() {\n        return msgState;\n    }\n\n    public void setMsgState(MqttMsgState msgState) {\n        this.msgState = msgState;\n    }\n\n    public byte[] getPayload() {\n        return payload;\n    }\n\n    public boolean isDup() {\n        return dup;\n    }\n\n    public void setDup(boolean dup) {\n        this.dup = dup;\n    }\n\n    public int getMsgId() {\n        return msgId;\n    }\n\n    public MqttMsgDirection getMqttMsgDirection() {\n        return mqttMsgDirection;\n    }\n\n    public void setMqttMsgDirection(MqttMsgDirection mqttMsgDirection) {\n        this.mqttMsgDirection = mqttMsgDirection;\n    }\n\n    public long getCreateTimestamp() {\n        return createTimestamp;\n    }\n\n    public void setMsgId(Integer msgId) {\n        this.msgId = msgId;\n    }\n\n    public void setTopic(String topic) {\n        this.topic = topic;\n    }\n\n    public void setQos(MqttQoS qos) {\n        this.qos = qos;\n    }\n\n    public void setRetain(boolean retain) {\n        this.retain = retain;\n    }\n\n    public void setPayload(byte[] payload) {\n        this.payload = payload;\n    }\n\n    public void setCreateTimestamp(long createTimestamp) {\n        this.createTimestamp = createTimestamp;\n    }\n\n    public MqttProperties getMqttProperties() {\n        return mqttProperties;\n    }\n\n    public void setMqttProperties(MqttProperties mqttProperties) {\n        this.mqttProperties = mqttProperties;\n    }\n\n    public byte getReasonCode() {\n        return reasonCode;\n    }\n\n    public void setReasonCode(byte reasonCode) {\n        this.reasonCode = reasonCode;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (!(o instanceof MqttMsg)) return false;\n        MqttMsg mqttMsg = (MqttMsg) o;\n        return retain == mqttMsg.retain && dup == mqttMsg.dup && reasonCode == mqttMsg.reasonCode && createTimestamp == mqttMsg.createTimestamp && Objects.equals(msgId, mqttMsg.msgId) && Objects.equals(topic, mqttMsg.topic) && qos == mqttMsg.qos && msgState == mqttMsg.msgState && Arrays.equals(payload, mqttMsg.payload) && mqttMsgDirection == mqttMsg.mqttMsgDirection && Objects.equals(mqttProperties, mqttMsg.mqttProperties);\n    }\n\n    @Override\n    public int hashCode() {\n        int result = Objects.hash(msgId, topic, qos, retain, msgState, dup, mqttMsgDirection, mqttProperties, reasonCode, createTimestamp);\n        result = 31 * result + Arrays.hashCode(payload);\n        return result;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/msg/MqttMsgInfo.java",
    "content": "package io.github.netty.mqtt.client.msg;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\n\nimport java.util.Arrays;\n\n/**\n * MQTT消息信息（主要用于MQTT 5）\n * @author: xzc-coder\n */\npublic class MqttMsgInfo {\n\n    /**\n     * 主题\n     */\n    private final String topic;\n    /**\n     * qos\n     */\n    private final MqttQoS qos;\n    /**\n     * 是否保留消息\n     */\n    private final boolean retain;\n\n    /**\n     * 载荷\n     */\n    private final byte[] payload;\n\n    /**\n     * MQTT5\n     * 载荷格式指示标识符\n     */\n    private Integer payloadFormatIndicator;\n\n    /**\n     * MQTT5\n     * 消息过期间隔 单位 秒\n     */\n    private Integer messageExpiryIntervalSeconds;\n\n    /**\n     * MQTT5\n     * 主题别名，注意；某些Broker不支持使用别名时并发的发送消息（因为并发时，可能还未完成映射后面的没有主题的消息就到了，这样会导致错误）\n     */\n    private Integer topicAlias;\n\n    /**\n     * MQTT5\n     * 响应主题\n     */\n    private String responseTopic;\n\n    /**\n     * MQTT5\n     * 对比数据\n     */\n    private byte[] correlationData;\n\n    /**\n     * MQTT5\n     * 订阅标识符\n     */\n    private Integer subscriptionIdentifier;\n\n    /**\n     * MQTT5\n     * 内容类型\n     */\n    private String contentType;\n\n    /**\n     * MQTT5\n     * 用户属性\n     */\n    private final MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n\n\n    public MqttMsgInfo(String topic, byte[] payload) {\n        this(topic, payload, MqttQoS.AT_MOST_ONCE, false);\n    }\n\n\n    public MqttMsgInfo(String topic, byte[] payload, MqttQoS qos) {\n        this(topic, payload, qos, false);\n    }\n\n\n    public MqttMsgInfo(String topic, byte[] payload, MqttQoS qos, boolean retain) {\n        AssertUtils.notNull(topic, \"topic is null\");\n        AssertUtils.notNull(payload, \"payload is null\");\n        AssertUtils.notNull(qos, \"qos is null\");\n        this.topic = topic;\n        this.payload = payload;\n        this.qos = qos;\n        this.retain = retain;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n    public MqttQoS getQos() {\n        return qos;\n    }\n\n    public boolean isRetain() {\n        return retain;\n    }\n\n    public byte[] getPayload() {\n        return payload;\n    }\n\n    public Integer getPayloadFormatIndicator() {\n        return payloadFormatIndicator;\n    }\n\n    public void setPayloadFormatIndicator(Integer payloadFormatIndicator) {\n        this.payloadFormatIndicator = payloadFormatIndicator;\n    }\n\n    public Integer getMessageExpiryIntervalSeconds() {\n        return messageExpiryIntervalSeconds;\n    }\n\n    public void setMessageExpiryIntervalSeconds(int messageExpiryIntervalSeconds) {\n        if (messageExpiryIntervalSeconds > 0) {\n            this.messageExpiryIntervalSeconds = messageExpiryIntervalSeconds;\n        }\n    }\n\n    public Integer getTopicAlias() {\n        return topicAlias;\n    }\n\n    public void setTopicAlias(int topicAlias) {\n        if (topicAlias > 0) {\n            this.topicAlias = topicAlias;\n        }\n    }\n\n    public String getResponseTopic() {\n        return responseTopic;\n    }\n\n    public void setResponseTopic(String responseTopic) {\n        this.responseTopic = responseTopic;\n    }\n\n    public byte[] getCorrelationData() {\n        return correlationData;\n    }\n\n    public void setCorrelationData(byte[] correlationData) {\n        this.correlationData = correlationData;\n    }\n\n    public Integer getSubscriptionIdentifier() {\n        return subscriptionIdentifier;\n    }\n\n    public void setSubscriptionIdentifier(Integer subscriptionIdentifier) {\n        this.subscriptionIdentifier = subscriptionIdentifier;\n    }\n\n    public String getContentType() {\n        return contentType;\n    }\n\n    public void setContentType(String contentType) {\n        this.contentType = contentType;\n    }\n\n    public MqttProperties.UserProperties getMqttUserProperties() {\n        return mqttUserProperties;\n    }\n\n    public void addMqttUserProperty(String key, String value) {\n        if (key != null && value != null) {\n            mqttUserProperties.add(key, value);\n        }\n    }\n\n    public void addMqttUserProperty(MqttProperties.StringPair stringPair) {\n        if (stringPair != null) {\n            mqttUserProperties.add(stringPair);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"MqttMsgInfo{\" +\n                \"topic='\" + topic + '\\'' +\n                \", qos=\" + qos +\n                \", retain=\" + retain +\n                \", payload=\" + Arrays.toString(payload) +\n                \", payloadFormatIndicator=\" + payloadFormatIndicator +\n                \", messageExpiryIntervalSeconds=\" + messageExpiryIntervalSeconds +\n                \", topicAlias=\" + topicAlias +\n                \", responseTopic='\" + responseTopic + '\\'' +\n                \", correlationData=\" + Arrays.toString(correlationData) +\n                \", subscriptionIdentifier=\" + subscriptionIdentifier +\n                \", contentType='\" + contentType + '\\'' +\n                \", mqttUserProperties=\" + mqttUserProperties +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/msg/MqttSubInfo.java",
    "content": "package io.github.netty.mqtt.client.msg;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttQoS;\nimport io.netty.handler.codec.mqtt.MqttSubscriptionOption;\n\n/**\n * MQTT单个订阅信息\n * @author: xzc-coder\n */\npublic class MqttSubInfo {\n\n    /**\n     * 主题\n     */\n    private final String topic;\n    /**\n     * qos\n     */\n    private final MqttQoS qos;\n\n    /**\n     * MQTT5\n     * 非本地，是否不接受自己发布的消息,false表示接受，true表示不接受，主要适用于桥接场景\n     */\n    private boolean noLocal;\n\n    /**\n     * MQTT5\n     * broker转发该消息是是否保持消息中Retain的标识\n     */\n    private boolean retainAsPublished;\n    /**\n     * MQTT5\n     * 当订阅建立时，保留消息如何处理。\n     */\n    private MqttSubscriptionOption.RetainedHandlingPolicy retainHandling;\n\n\n    public MqttSubInfo(String topic, MqttQoS qos) {\n        this(topic,qos,false);\n    }\n\n    public MqttSubInfo(String topic, MqttQoS qos, boolean noLocal){\n        this(topic,qos,noLocal,false);\n    }\n\n    public MqttSubInfo(String topic, MqttQoS qos, boolean noLocal, boolean retainAsPublished) {\n        this(topic,qos,noLocal,retainAsPublished,MqttSubscriptionOption.RetainedHandlingPolicy.SEND_AT_SUBSCRIBE);\n    }\n\n    public MqttSubInfo(String topic, MqttQoS qos, boolean noLocal, boolean retainAsPublished, MqttSubscriptionOption.RetainedHandlingPolicy retainHandling) {\n        AssertUtils.notNull(topic, \"topic is null\");\n        AssertUtils.notNull(qos, \"qos is null\");\n        AssertUtils.notNull(retainHandling,\"retainHandling is null\");\n        this.topic = topic;\n        this.qos = qos;\n        this.noLocal = noLocal;\n        this.retainAsPublished = retainAsPublished;\n        this.retainHandling = retainHandling;\n    }\n\n    public String getTopic() {\n        return topic;\n    }\n\n\n    public MqttQoS getQos() {\n        return qos;\n    }\n\n    public boolean isNoLocal() {\n        return noLocal;\n    }\n\n    public void setNoLocal(boolean noLocal) {\n        this.noLocal = noLocal;\n    }\n\n    public boolean isRetainAsPublished() {\n        return retainAsPublished;\n    }\n\n    public void setRetainAsPublished(boolean retainAsPublished) {\n        this.retainAsPublished = retainAsPublished;\n    }\n\n    public MqttSubscriptionOption.RetainedHandlingPolicy getRetainHandling() {\n        return retainHandling;\n    }\n\n    public void setRetainHandling(MqttSubscriptionOption.RetainedHandlingPolicy retainHandling) {\n        if(retainHandling != null) {\n            this.retainHandling = retainHandling;\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"MqttSubInfo{\" +\n                \"topic='\" + topic + '\\'' +\n                \", qos=\" + qos +\n                \", noLocal=\" + noLocal +\n                \", retainAsPublished=\" + retainAsPublished +\n                \", retainHandling=\" + retainHandling +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/msg/MqttSubMsg.java",
    "content": "package io.github.netty.mqtt.client.msg;\n\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * MQTT订阅消息\n * @author: xzc-coder\n */\npublic class MqttSubMsg {\n\n    /**\n     * 消息ID\n     */\n    private final int msgId;\n    /**\n     * 订阅的主题列表\n     */\n    private final List<MqttSubInfo> mqttSubInfoList = new ArrayList<>();\n\n    /**\n     * MQTT5\n     * 订阅标识符\n     */\n    private Integer subscriptionIdentifier;\n\n    /**\n     * MQTT5\n     * 用户属性\n     */\n    private final MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n\n\n    public MqttSubMsg(int msgId, List<MqttSubInfo> mqttSubInfoList) {\n        AssertUtils.notEmpty(mqttSubInfoList, \"mqttSubInfoList is empty\");\n        this.msgId = msgId;\n        this.mqttSubInfoList.addAll(mqttSubInfoList);\n    }\n\n    public MqttSubMsg(int msgId, List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n        AssertUtils.notEmpty(mqttSubInfoList, \"mqttSubInfoList is empty\");\n        this.msgId = msgId;\n        this.mqttSubInfoList.addAll(mqttSubInfoList);\n        this.setSubscriptionIdentifier(subscriptionIdentifier);\n        this.addMqttUserProperties(mqttUserProperties);\n    }\n\n    public MqttSubMsg(int msgId, MqttSubInfo mqttSubInfo) {\n        AssertUtils.notNull(mqttSubInfo, \"mqttSubInfo is null\");\n        this.msgId = msgId;\n        mqttSubInfoList.add(mqttSubInfo);\n    }\n\n    public MqttSubMsg(int msgId, MqttSubInfo mqttSubInfo, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n        AssertUtils.notNull(mqttSubInfo, \"mqttSubInfo is null\");\n        this.msgId = msgId;\n        this.mqttSubInfoList.add(mqttSubInfo);\n        this.setSubscriptionIdentifier(subscriptionIdentifier);\n        this.addMqttUserProperties(mqttUserProperties);\n    }\n\n    public List<MqttSubInfo> getMqttSubInfoList() {\n        return mqttSubInfoList;\n    }\n\n    public int getMsgId() {\n        return msgId;\n    }\n\n    public MqttProperties.UserProperties getMqttUserProperties() {\n        return mqttUserProperties;\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param key key\n     * @param value value\n     */\n    public void addMqttUserProperty(String key, String value) {\n        if (key != null && value != null) {\n            mqttUserProperties.add(key, value);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param stringPair key value对象\n     */\n    public void addMqttUserProperty(MqttProperties.StringPair stringPair) {\n        if (stringPair != null) {\n            mqttUserProperties.add(stringPair);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param mqttUserProperties MQTT用户属性\n     */\n    private void addMqttUserProperties(MqttProperties.UserProperties mqttUserProperties) {\n        if (mqttUserProperties != null) {\n            for (MqttProperties.StringPair stringPair : mqttUserProperties.value()) {\n                this.mqttUserProperties.add(stringPair);\n            }\n        }\n    }\n\n    public Integer getSubscriptionIdentifier() {\n        return subscriptionIdentifier;\n    }\n\n    public void setSubscriptionIdentifier(Integer subscriptionIdentifier) {\n        this.subscriptionIdentifier = subscriptionIdentifier;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"MqttSubMsg{\" +\n                \"msgId=\" + msgId +\n                \", mqttSubInfoList=\" + mqttSubInfoList +\n                \", subscriptionIdentifier=\" + subscriptionIdentifier +\n                \", mqttUserProperties=\" + mqttUserProperties +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/msg/MqttUnsubMsg.java",
    "content": "package io.github.netty.mqtt.client.msg;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * MQTT取消订阅消息\n * @author: xzc-coder\n */\npublic class MqttUnsubMsg {\n\n    /**\n     * 消息ID\n     */\n    private final int msgId;\n    /**\n     * 取消订阅列表\n     */\n    private final List<String> topicList = new ArrayList<>();\n\n    /**\n     * MQTT 5.0.0参数\n     * 用户属性\n     */\n    private final MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n\n\n    public MqttUnsubMsg(int msgId, List<String> topicList) {\n        this(msgId,topicList,null);\n    }\n\n    public MqttUnsubMsg(int msgId, String topic) {\n        AssertUtils.notNull(topic, \"topic is null\");\n        this.msgId = msgId;\n        topicList.add(topic);\n    }\n\n\n\n    public MqttUnsubMsg(int msgId, List<String> topicList,MqttProperties.UserProperties mqttUserProperties) {\n        AssertUtils.notEmpty(topicList, \"topicList is empty\");\n        this.msgId = msgId;\n        this.topicList.addAll(topicList);\n        this.addMqttUserProperties(mqttUserProperties);\n    }\n\n    public List<String> getTopicList() {\n        return topicList;\n    }\n\n    public int getMsgId() {\n        return msgId;\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param key key\n     * @param value value\n     */\n    public void addMqttUserProperty(String key, String value) {\n        if (key != null && value != null) {\n            mqttUserProperties.add(key, value);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param stringPair key value对象\n     */\n    public void addMqttUserProperty(MqttProperties.StringPair stringPair) {\n        if (stringPair != null) {\n            mqttUserProperties.add(stringPair);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param mqttUserProperties MQTT用户属性\n     */\n    private void addMqttUserProperties(MqttProperties.UserProperties mqttUserProperties) {\n        if (mqttUserProperties != null) {\n            for (MqttProperties.StringPair stringPair : mqttUserProperties.value()) {\n                this.mqttUserProperties.add(stringPair);\n            }\n        }\n    }\n\n    public MqttProperties.UserProperties getMqttUserProperties() {\n        return mqttUserProperties;\n    }\n\n    @Override\n    public String toString() {\n        return \"MqttUnsubMsg{\" +\n                \"msgId=\" + msgId +\n                \", topicList=\" + topicList +\n                \", mqttUserProperties=\" + mqttUserProperties +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/msg/MqttWillMsg.java",
    "content": "package io.github.netty.mqtt.client.msg;\n\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.MqttUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\n\nimport java.util.Arrays;\n\n/**\n * MQTT遗嘱消息\n * @author: xzc-coder\n */\npublic class MqttWillMsg {\n\n    /**\n     * 遗嘱主题\n     */\n    private final String willTopic;\n    /**\n     * 遗嘱消息\n     */\n    private final byte[] willMessageBytes;\n    /**\n     * 遗嘱Qos\n     */\n    private final MqttQoS willQos;\n    /**\n     * 遗嘱消息是否保留\n     */\n    private final boolean isWillRetain;\n\n    /**\n     * MQTT5\n     * 遗嘱延时间隔 单位 秒\n     */\n    private Integer willDelayIntervalSeconds;\n\n    /**\n     * MQTT5\n     * 载荷格式指示\n     */\n    private Integer payloadFormatIndicator;\n\n    /**\n     * MQTT5\n     * 消息过期间隔（秒）\n     */\n    private Integer messageExpiryIntervalSeconds;\n\n    /**\n     * MQTT5\n     * 内容类型\n     */\n    private String contentType;\n\n    /**\n     * MQTT5\n     * 响应主题\n     */\n    private String responseTopic;\n\n    /**\n     * MQTT5\n     * 对比数据\n     */\n    private byte[] correlationData;\n\n    /**\n     * MQTT5\n     * 用户属性\n     */\n    private final MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n\n\n    public MqttWillMsg(String willTopic, byte[] willMessageBytes, MqttQoS willQos) {\n        this(willTopic, willMessageBytes, willQos, false);\n    }\n\n    public MqttWillMsg(String willTopic, byte[] willMessageBytes, MqttQoS willQos, boolean isWillRetain) {\n        AssertUtils.notNull(willTopic, \"willTopic is null\");\n        AssertUtils.notNull(willMessageBytes, \"willMessageBytes is null\");\n        AssertUtils.notNull(willQos, \"willQos is null\");\n        MqttUtils.topicCheck(willTopic,false);\n        this.willTopic = willTopic;\n        this.willMessageBytes = willMessageBytes;\n        this.willQos = willQos;\n        this.isWillRetain = isWillRetain;\n    }\n\n\n    public String getWillTopic() {\n        return willTopic;\n    }\n\n    public byte[] getWillMessageBytes() {\n        return willMessageBytes;\n    }\n\n    public MqttQoS getWillQos() {\n        return willQos;\n    }\n\n    public boolean isWillRetain() {\n        return isWillRetain;\n    }\n\n    public Integer getWillDelayIntervalSeconds() {\n        return willDelayIntervalSeconds;\n    }\n\n    public void setWillDelayIntervalSeconds(Integer willDelayIntervalSeconds) {\n        this.willDelayIntervalSeconds = willDelayIntervalSeconds;\n    }\n\n    public Integer getPayloadFormatIndicator() {\n        return payloadFormatIndicator;\n    }\n\n    public void setPayloadFormatIndicator(Integer payloadFormatIndicator) {\n        this.payloadFormatIndicator = payloadFormatIndicator;\n    }\n\n    public Integer getMessageExpiryIntervalSeconds() {\n        return messageExpiryIntervalSeconds;\n    }\n\n    public void setMessageExpiryIntervalSeconds(Integer messageExpiryIntervalSeconds) {\n        this.messageExpiryIntervalSeconds = messageExpiryIntervalSeconds;\n    }\n\n    public String getContentType() {\n        return contentType;\n    }\n\n    public void setContentType(String contentType) {\n        this.contentType = contentType;\n    }\n\n    public String getResponseTopic() {\n        return responseTopic;\n    }\n\n    public void setResponseTopic(String responseTopic) {\n        this.responseTopic = responseTopic;\n    }\n\n    public byte[] getCorrelationData() {\n        return correlationData;\n    }\n\n    public void setCorrelationData(byte[] correlationData) {\n        this.correlationData = correlationData;\n    }\n\n\n    public MqttProperties.UserProperties getMqttUserProperties() {\n        return mqttUserProperties;\n    }\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param key key\n     * @param value value\n     */\n    public void addMqttUserProperty(String key, String value) {\n        if (key != null && value != null) {\n            mqttUserProperties.add(key, value);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param stringPair key value对象\n     */\n    public void addMqttUserProperty(MqttProperties.StringPair stringPair) {\n        if (stringPair != null) {\n            mqttUserProperties.add(stringPair);\n        }\n    }\n\n    /**\n     * MQTT5\n     * 添加一个MQTT用户属性\n     * @param mqttUserProperties MQTT用户属性\n     */\n    private void addMqttUserProperties(MqttProperties.UserProperties mqttUserProperties) {\n        if (mqttUserProperties != null) {\n            for (MqttProperties.StringPair stringPair : mqttUserProperties.value()) {\n                this.mqttUserProperties.add(stringPair);\n            }\n        }\n    }\n\n\n    @Override\n    public String toString() {\n        return \"MqttWillMsg{\" +\n                \"willTopic='\" + willTopic + '\\'' +\n                \", willMessageBytes=\" + Arrays.toString(willMessageBytes) +\n                \", willQos=\" + willQos +\n                \", isWillRetain=\" + isWillRetain +\n                \", willDelayIntervalSeconds=\" + willDelayIntervalSeconds +\n                \", payloadFormatIndicator=\" + payloadFormatIndicator +\n                \", messageExpiryIntervalSeconds=\" + messageExpiryIntervalSeconds +\n                \", contentType='\" + contentType + '\\'' +\n                \", responseTopic='\" + responseTopic + '\\'' +\n                \", correlationData=\" + Arrays.toString(correlationData) +\n                \", mqttUserProperties=\" + mqttUserProperties +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/plugin/BaseMethodInterceptor.java",
    "content": "package io.github.netty.mqtt.client.plugin;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\n\nimport java.lang.reflect.Method;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\n/**\n * 拦截器基础类\n * @author: xzc-coder\n */\npublic class BaseMethodInterceptor {\n\n    /**\n     * 拦截器列表\n     */\n    protected final List<Interceptor> interceptors = Collections.synchronizedList(new ArrayList<>());\n    /**\n     * 目标对象\n     */\n    protected final Object target;\n    /**\n     * 要拦截的类信息集合 做缓存\n     */\n    protected final Set<Class<?>> interceptClassSet;\n    /**\n     * 方法调用缓存，返回null，则表示该方法没有被判断过是否拦截，true则表示拦截，false 则表示跳过\n     */\n    protected final Map<Method, Boolean> invokeInterceptCache = new ConcurrentHashMap<>();\n\n\n    public BaseMethodInterceptor(List<Interceptor> interceptors, Object target) {\n        if (interceptors != null) {\n            this.interceptors.addAll(interceptors);\n        }\n        AssertUtils.notNull(target, \"target is null\");\n        this.interceptClassSet = new CopyOnWriteArraySet<>();\n        filterInterceptor(this.interceptors, target);\n        this.target = target;\n    }\n\n    /**\n     * 过滤拦截器\n     *\n     * @param interceptors 拦截器\n     * @param target       目标对象\n     */\n    protected void filterInterceptor(List<Interceptor> interceptors, Object target) {\n        //翻转拦截器\n        Collections.reverse(interceptors);\n        Iterator<Interceptor> iterator = interceptors.iterator();\n        //遍历拦截器\n        while (iterator.hasNext()) {\n            //获取拦截器上的注解Intercepts信息\n            Interceptor interceptor = iterator.next();\n            Intercepts intercepts = interceptor.getClass().getAnnotation(Intercepts.class);\n            //是否删除该拦截器（无效的拦截器会被删除）\n            boolean delete = true;\n            if (intercepts != null) {\n                //获取Intercepts上的class信息集合\n                Class<?>[] classes = intercepts.type();\n                //遍历\n                for (Class<?> annotationClass : classes) {\n                    //判断注解上的要拦截的类是否是目标对象的超类或相同类\n                    if (annotationClass.isAssignableFrom(target.getClass())) {\n                        //有一个符合条件则不删除该拦截器\n                        delete = false;\n                        this.interceptClassSet.add(annotationClass);\n                    }\n                }\n            }\n            if (delete) {\n                iterator.remove();\n            }\n        }\n    }\n\n    /**\n     * 判断某个方法是否需要被调用拦截\n     *\n     * @param method 方法\n     * @return 是否要调用拦截方法\n     */\n    protected boolean invokeInterceptMethod(Method method) {\n        //先从缓存中获取\n        Boolean invoke = this.invokeInterceptCache.get(method);\n        if (invoke == null) {\n            invoke = false;\n            //判断是否是Object方法\n            if (!this.isObjectMethod(method)) {\n                //不是Object方法则遍历拦截类的集合\n                for (Class<?> clazz : this.interceptClassSet) {\n                    Method[] methods = clazz.getDeclaredMethods();\n                    for (Method m : methods) {\n                        //如果方法名，参数个数都相同，则表示要拦截当前方法\n                        if (m.getName().equals(method.getName()) && m.getParameterTypes().length == method.getParameterTypes().length) {\n                            invoke = true;\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n        //添加到缓存\n        this.invokeInterceptCache.putIfAbsent(method, invoke);\n        return invoke;\n    }\n\n    /**\n     * 判断是否是Object方法\n     *\n     * @param method 方法\n     * @return 是否是Object方法\n     */\n    protected boolean isObjectMethod(Method method) {\n        Method[] methods = Object.class.getDeclaredMethods();\n        for (Method m : methods) {\n            if (m.getName().equals(method.getName()) && m.getParameterTypes().length == method.getParameterTypes().length) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/plugin/CglibMethodInterceptor.java",
    "content": "package io.github.netty.mqtt.client.plugin;\n\nimport net.sf.cglib.proxy.MethodInterceptor;\nimport net.sf.cglib.proxy.MethodProxy;\n\nimport java.lang.reflect.Method;\nimport java.util.List;\n\n/**\n * cglib拦截器实现\n * @author: xzc-coder\n */\npublic class CglibMethodInterceptor extends BaseMethodInterceptor implements MethodInterceptor {\n\n\n    public CglibMethodInterceptor(List<Interceptor> interceptors, Object target) {\n        super(interceptors, target);\n    }\n\n    @Override\n    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {\n        //是否拦截该方法\n        boolean invoke = this.invokeInterceptMethod(method);\n        Method targetMethod = getTargetMethod(method);\n        if (invoke) {\n            //创建方法调用器\n            Invocation invocation = new Invocation(this.target, targetMethod, args, new InterceptorChain(this.interceptors));\n            //执行方法\n            return invocation.proceed();\n        } else {\n            //不拦截则调用代理对象的方法\n            return targetMethod.invoke(this.target, args);\n        }\n    }\n\n    /**\n     * 获取代理对象的方法\n     *\n     * @param method 原始的方法\n     * @return 代理对象方法\n     * @throws NoSuchMethodException\n     */\n    private Method getTargetMethod(Method method) throws NoSuchMethodException {\n        return target.getClass().getMethod(method.getName(), method.getParameterTypes());\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/plugin/CglibTargetHelper.java",
    "content": "package io.github.netty.mqtt.client.plugin;\n\nimport io.github.netty.mqtt.client.MqttClient;\nimport io.github.netty.mqtt.client.MqttConfiguration;\nimport io.github.netty.mqtt.client.MqttConnectParameter;\nimport io.github.netty.mqtt.client.callback.MqttCallback;\nimport io.github.netty.mqtt.client.connector.MqttConnector;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.msg.*;\nimport io.github.netty.mqtt.client.support.future.MqttFuture;\nimport io.github.netty.mqtt.client.support.future.MqttFutureWrapper;\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.mqtt.*;\n\nimport java.net.InetSocketAddress;\nimport java.util.Collection;\nimport java.util.List;\n\n\n/**\n * cglib拦截器帮助类\n * @author: xzc-coder\n */\npublic abstract class CglibTargetHelper {\n\n\n    /**\n     * 根据具体的对象创建包装对象\n     *\n     * @param target 目标对象\n     * @return 包装对象\n     */\n    public static Object createCglibTarget(Object target) {\n        Object cglibTarget;\n        if (target instanceof MqttClient) {\n            cglibTarget = new CglibTargetMqttClient();\n        } else if (target instanceof MqttConnector) {\n            cglibTarget = new CglibTargetMqttConnector();\n        } else if (target instanceof MqttDelegateHandler) {\n            cglibTarget = new CglibTargetMqttDelegateHandler();\n        } else {\n            cglibTarget = target;\n        }\n        return cglibTarget;\n    }\n\n\n    public static class CglibTargetMqttClient implements MqttClient {\n\n        @Override\n        public InetSocketAddress getLocalAddress() {\n            return null;\n        }\n\n        @Override\n        public InetSocketAddress getRemoteAddress() {\n            return null;\n        }\n\n        @Override\n        public String getClientId() {\n            return null;\n        }\n\n        @Override\n        public MqttConnectParameter getMqttConnectParameter() {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper connectFuture() {\n            return null;\n        }\n\n        @Override\n        public void connect() {\n\n        }\n\n        @Override\n        public void disconnect() {\n\n        }\n\n        @Override\n        public MqttFutureWrapper disconnectFuture() {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper disconnectFuture(MqttDisconnectMsg mqttDisconnectMsg) {\n            return null;\n        }\n\n        @Override\n        public void disconnect(MqttDisconnectMsg mqttDisconnectMsg) {\n\n        }\n\n        @Override\n        public MqttFutureWrapper publishFuture(MqttMsgInfo mqttMsgInfo) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper publishFuture(byte[] payload, String topic, MqttQoS qos, boolean retain) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper publishFuture(byte[] payload, String topic, MqttQoS qos) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper publishFuture(byte[] payload, String topic) {\n            return null;\n        }\n\n        @Override\n        public void publish(MqttMsgInfo mqttMsgInfo) {\n\n        }\n\n        @Override\n        public void publish(byte[] payload, String topic, MqttQoS qos, boolean retain) {\n\n        }\n\n        @Override\n        public void publish(byte[] payload, String topic, MqttQoS qos) {\n\n        }\n\n        @Override\n        public void publish(byte[] payload, String topic) {\n\n        }\n\n        @Override\n        public void subscribe(String topic, MqttQoS qos) {\n\n        }\n\n        @Override\n        public void subscribe(MqttSubInfo mqttSubInfo) {\n\n        }\n\n        @Override\n        public void subscribe(MqttSubInfo mqttSubInfo, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n\n        }\n\n        @Override\n        public void subscribes(List<MqttSubInfo> mqttSubInfoList) {\n\n        }\n\n        @Override\n        public void subscribes(List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n\n        }\n\n        @Override\n        public void subscribes(List<String> topicList, MqttQoS qos) {\n\n        }\n\n        @Override\n        public MqttFutureWrapper subscribesFuture(List<String> topicList, MqttQoS qos) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper subscribeFuture(String topic, MqttQoS qos) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper subscribeFuture(MqttSubInfo mqttSubInfo) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper subscribeFuture(MqttSubInfo mqttSubInfo, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper subscribesFuture(List<MqttSubInfo> mqttSubInfoList, Integer subscriptionIdentifier, MqttProperties.UserProperties mqttUserProperties) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper subscribesFuture(List<MqttSubInfo> mqttSubInfoList) {\n            return null;\n        }\n\n        @Override\n        public void unsubscribes(List<String> topicList, MqttProperties.UserProperties mqttUserProperties) {\n\n        }\n\n        @Override\n        public void unsubscribes(List<String> topicList) {\n\n        }\n\n        @Override\n        public void unsubscribe(String topic, MqttProperties.UserProperties mqttUserProperties) {\n\n        }\n\n        @Override\n        public void unsubscribe(String topic) {\n\n        }\n\n        @Override\n        public MqttFutureWrapper unsubscribeFuture(String topic, MqttProperties.UserProperties mqttUserProperties) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper unsubscribeFuture(String topic) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper unsubscribesFuture(List<String> topicList) {\n            return null;\n        }\n\n        @Override\n        public MqttFutureWrapper unsubscribesFuture(List<String> topicList, MqttProperties.UserProperties mqttUserProperties) {\n            return null;\n        }\n\n        @Override\n        public void addMqttCallback(MqttCallback mqttCallback) {\n\n        }\n\n        @Override\n        public void addMqttCallbacks(Collection<MqttCallback> mqttCallbacks) {\n\n        }\n\n        @Override\n        public boolean isOnline() {\n            return false;\n        }\n\n        @Override\n        public boolean isActive() {\n            return false;\n        }\n\n        @Override\n        public boolean isClose() {\n            return false;\n        }\n\n        @Override\n        public void close() {\n\n        }\n    }\n\n\n    public static class CglibTargetMqttConnector implements MqttConnector {\n\n        @Override\n        public MqttFuture<Channel> connect() {\n            return null;\n        }\n\n        @Override\n        public MqttDelegateHandler getMqttDelegateHandler() {\n            return null;\n        }\n\n        @Override\n        public MqttConfiguration getMqttConfiguration() {\n            return null;\n        }\n    }\n\n    public static class CglibTargetMqttDelegateHandler implements MqttDelegateHandler {\n\n\n        @Override\n        public void channelConnect(Channel channel) {\n\n        }\n\n        @Override\n        public void sendConnect(Channel channel) {\n\n        }\n\n        @Override\n        public void connack(Channel channel, MqttConnAckMessage mqttConnAckMessage) {\n\n        }\n\n        @Override\n        public void auth(Channel channel, MqttMessage mqttAuthMessage) {\n\n        }\n\n        @Override\n        public void sendAuth(Channel channel, byte reasonCode, MqttProperties mqttProperties) {\n\n        }\n\n        @Override\n        public void sendDisconnect(Channel channel, MqttFuture mqttFuture, MqttDisconnectMsg mqttDisconnectMsg) {\n\n        }\n\n        @Override\n        public void disconnect(Channel channel, MqttMessage mqttMessage) {\n\n        }\n\n        @Override\n        public void sendSubscribe(Channel channel, MqttSubMsg mqttSubMsg) {\n\n        }\n\n        @Override\n        public void suback(Channel channel, MqttSubAckMessage mqttSubAckMessage) {\n\n        }\n\n        @Override\n        public void sendUnsubscribe(Channel channel, MqttUnsubMsg mqttUnsubMsg) {\n\n        }\n\n        @Override\n        public void unsuback(Channel channel, MqttUnsubAckMessage mqttUnsubAckMessage) {\n\n        }\n\n        @Override\n        public void sendPingreq(Channel channel) {\n\n        }\n\n        @Override\n        public void pingresp(Channel channel, MqttMessage mqttPingRespMessage) {\n\n        }\n\n        @Override\n        public void sendPublish(Channel channel, MqttMsg mqttMsg, MqttFuture msgFuture) {\n\n        }\n\n        @Override\n        public void publish(Channel channel, MqttPublishMessage mqttPublishMessage) {\n\n        }\n\n        @Override\n        public void sendPuback(Channel channel, MqttMsg mqttMsg) {\n\n        }\n\n        @Override\n        public void puback(Channel channel, MqttPubAckMessage mqttPubAckMessage) {\n\n        }\n\n        @Override\n        public void sendPubrec(Channel channel, MqttMsg mqttMsg) {\n\n        }\n\n        @Override\n        public void pubrec(Channel channel, MqttMessage mqttMessage) {\n\n        }\n\n        @Override\n        public void sendPubrel(Channel channel, MqttMsg mqttMsg) {\n\n        }\n\n        @Override\n        public void pubrel(Channel channel, MqttMessage mqttMessage) {\n\n        }\n\n        @Override\n        public void sendPubcomp(Channel channel, MqttMsg mqttMsg) {\n\n        }\n\n        @Override\n        public void pubcomp(Channel channel, MqttMessage mqttMessage) {\n\n        }\n\n        @Override\n        public void exceptionCaught(Channel channel, Throwable cause) {\n\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/plugin/Interceptor.java",
    "content": "package io.github.netty.mqtt.client.plugin;\n\n/**\n * 拦截器接口\n * @author: xzc-coder\n */\npublic interface Interceptor {\n\n    /**\n     * 拦截某个方法\n     *\n     * @param invocation 方法调用器\n     * @return 方法返回值\n     * @throws Throwable 异常\n     */\n    Object intercept(Invocation invocation) throws Throwable;\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/plugin/InterceptorChain.java",
    "content": "package io.github.netty.mqtt.client.plugin;\n\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * 拦截器链，用于执行拦截调用\n * @author: xzc-coder\n */\npublic class InterceptorChain {\n\n    /**\n     * 拦截集合的迭代器\n     */\n    private Iterator<Interceptor> interceptorIterator;\n\n    public InterceptorChain(List<Interceptor> interceptors) {\n        if (interceptors != null) {\n            this.interceptorIterator = new LinkedList<>(interceptors).iterator();\n        }\n\n    }\n\n    /**\n     * 返回下一个拦截器\n     *\n     * @return 拦截器\n     */\n    Interceptor next() {\n        Interceptor interceptor = null;\n        if (this.interceptorIterator.hasNext()) {\n            interceptor = this.interceptorIterator.next();\n        }\n        return interceptor;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/plugin/Intercepts.java",
    "content": "package io.github.netty.mqtt.client.plugin;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 拦截器注解\n * @author: xzc-coder\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface Intercepts {\n\n    /**\n     * 拦截的类\n     *\n     * @return 拦截的类\n     */\n    Class<?>[] type();\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/plugin/Invocation.java",
    "content": "package io.github.netty.mqtt.client.plugin;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\n\nimport java.lang.reflect.Method;\n\n/**\n * 拦截器方法调用类\n * @author: xzc-coder\n */\npublic class Invocation {\n\n    /**\n     * 目标对象\n     */\n    private final Object target;\n    /**\n     * 当前调用的方法\n     */\n    private final Method method;\n    /**\n     * 参数\n     */\n    private final Object[] args;\n    /**\n     * 拦截链\n     */\n    private final InterceptorChain interceptorChain;\n\n    public Invocation(Object target, Method method, Object[] args, InterceptorChain interceptorChain) {\n        AssertUtils.notNull(target, \"target is null\");\n        AssertUtils.notNull(method, \"method is null\");\n        AssertUtils.notNull(interceptorChain, \"interceptorChain is null\");\n        this.target = target;\n        this.method = method;\n        this.args = args;\n        this.interceptorChain = interceptorChain;\n    }\n\n    public Object getTarget() {\n        return target;\n    }\n\n    public Method getMethod() {\n        return method;\n    }\n\n    public Object[] getArgs() {\n        return args;\n    }\n\n    /**\n     * 调用方法\n     *\n     * @return 方法返回值\n     * @throws Throwable 异常\n     */\n    public Object proceed() throws Throwable {\n        //获取下一个拦截器\n        Interceptor interceptor = this.interceptorChain.next();\n        if (interceptor == null) {\n            //说明拦截器调用完了\n            return method.invoke(target, args);\n        }\n        //进行拦截器拦截调用\n        return interceptor.intercept(new Invocation(target, method, args, interceptorChain));\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/plugin/JdkMethodInterceptor.java",
    "content": "package io.github.netty.mqtt.client.plugin;\n\n\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Method;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\n\n/**\n * JDK动态代理拦截器实现\n * @author: xzc-coder\n */\npublic class JdkMethodInterceptor extends BaseMethodInterceptor implements InvocationHandler {\n\n    public JdkMethodInterceptor(List<Interceptor> interceptors, Object target) {\n        super(interceptors, target);\n    }\n\n    @Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        //是否拦截该方法\n        boolean invoke = invokeInterceptMethod(method);\n        if (invoke) {\n            //创建方法调用器\n            Invocation invocation = new Invocation(this.target, method, args, new InterceptorChain(this.interceptors));\n            //执行方法\n            return invocation.proceed();\n        } else {\n            //不拦截则调用目标对象方法继续执行\n            return method.invoke(this.target, args);\n        }\n    }\n\n    @Override\n    protected void filterInterceptor(List<Interceptor> interceptors, Object target) {\n        //翻转拦截器\n        Collections.reverse(interceptors);\n        Iterator<Interceptor> iterator = interceptors.iterator();\n        //遍历拦截器\n        while (iterator.hasNext()) {\n            //获取拦截器上的注解Intercepts信息\n            Interceptor interceptor = iterator.next();\n            Intercepts intercepts = interceptor.getClass().getAnnotation(Intercepts.class);\n            //是否删除该拦截器（无效的拦截器会被删除）\n            boolean delete = true;\n            if (intercepts != null) {\n                //获取Intercepts上的class信息集合\n                Class<?>[] classes = intercepts.type();\n                //遍历\n                for (Class<?> annotationClass : classes) {\n                    //如果class不是接口则抛出异常,因为jdk只支持接口代理\n                    if (!annotationClass.isInterface()) {\n                        throw new IllegalArgumentException(\"jdk proxy type \" + annotationClass.getName() + \" must is interface.\");\n                    }\n                    //判断注解上的要拦截的类是否是目标对象的超类或相同类\n                    if (annotationClass.isAssignableFrom(target.getClass())) {\n                        //有一个符合条件则不删除该拦截器\n                        delete = false;\n                        this.interceptClassSet.add(annotationClass);\n                    }\n                }\n            }\n            if (delete) {\n                iterator.remove();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/retry/MqttRetrier.java",
    "content": "package io.github.netty.mqtt.client.retry;\n\nimport io.github.netty.mqtt.client.MqttConnectParameter;\nimport io.github.netty.mqtt.client.support.future.MqttFuture;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.channel.EventLoopGroup;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * MQTT消息重试器\n * @author: xzc-coder\n */\npublic class MqttRetrier {\n\n    /**\n     * 线程组\n     */\n    private final EventLoopGroup eventLoopGroup;\n\n    private final MqttConnectParameter mqttConnectParameter;\n\n    public MqttRetrier(MqttConnectParameter mqttConnectParameter,EventLoopGroup eventLoopGroup) {\n        AssertUtils.notNull(mqttConnectParameter, \"mqttConnectParameter is null\");\n        AssertUtils.notNull(eventLoopGroup, \"eventLoopGroup is null\");\n        this.mqttConnectParameter = mqttConnectParameter;\n        this.eventLoopGroup = eventLoopGroup;\n    }\n\n    /**\n     * 重试\n     * @param msgFuture Future，用来判断是否完成\n     * @param intervalMills 间隔毫秒\n     * @param task 要执行的任务\n     * @param nowExecute 是否立即执行\n     */\n    public void retry(MqttFuture msgFuture, long intervalMills, Runnable task, boolean nowExecute) {\n        if (!eventLoopGroup.isShutdown()) {\n            if (nowExecute) {\n                eventLoopGroup.execute(task);\n            }\n            eventLoopGroup.schedule(() -> {\n                //如果没有完成，则执行任务\n                if (!msgFuture.isDone()) {\n                    task.run();\n                    //下一次执行间隔\n                    long nextDelayMills = intervalMills + mqttConnectParameter.getRetryIntervalIncreaseMillis();\n                    if (nextDelayMills > mqttConnectParameter.getRetryIntervalMaxMillis()) {\n                        nextDelayMills = mqttConnectParameter.getRetryIntervalMaxMillis();\n                    }\n                    retry(msgFuture, nextDelayMills, task, false);\n                }\n            }, intervalMills, TimeUnit.MILLISECONDS);\n        }\n    }\n\n    /**\n     * 重试\n     * @param msgFuture Future，用来判断是否完成\n     * @param task 要执行的任务\n     * @param nowExecute 是否立即执行\n     */\n    public void retry(MqttFuture msgFuture,Runnable task, boolean nowExecute) {\n        this.retry(msgFuture,mqttConnectParameter.getRetryIntervalMillis(),task,nowExecute);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/store/FileMqttMsgStore.java",
    "content": "package io.github.netty.mqtt.client.store;\n\nimport io.github.netty.mqtt.client.constant.MqttMsgDirection;\nimport io.github.netty.mqtt.client.exception.MqttException;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\nimport io.github.netty.mqtt.client.support.util.MqttUtils;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.Set;\n\n/**\n * MQTT文件消息存储器\n * @author: xzc-coder\n */\npublic class FileMqttMsgStore implements MqttMsgStore {\n\n    private final File propertiesFile;\n    private final Properties properties;\n\n    /**\n     * MQTT 发送消息的前缀 后面接 -客户端ID-消息ID\n     */\n    private static final String MQTT_SEND_MSG_PREFIX = \"send\";\n    /**\n     * MQTT properties文件中的key的分隔符号\n     */\n    private static final String KEY_SEPARATOR = \"-\";\n    /**\n     * MQTT 接收消息的前缀 后面接 -客户端ID-消息ID\n     */\n    private static final String MQTT_RECEIVE_MSG_PREFIX = \"receive\";\n\n    public FileMqttMsgStore(File propertiesFile) {\n        AssertUtils.notNull(propertiesFile, \"propertiesFile is null\");\n        this.propertiesFile = propertiesFile;\n        this.properties = new Properties();\n        loadProperties();\n    }\n\n    @Override\n    public MqttMsg getMsg(MqttMsgDirection mqttMsgDirection, String clientId, int msgId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        MqttMsg mqttMsg;\n        String msgPropertyKey = getMsgPropertyKey(clientId, mqttMsgDirection, msgId);\n        mqttMsg = getMqttMsg(msgPropertyKey);\n        return mqttMsg;\n    }\n\n    @Override\n    public void putMsg(MqttMsgDirection mqttMsgDirection, String clientId, MqttMsg mqttMsg) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsg, \"mqttMsg is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        String msgKey = getMsgPropertyKey(clientId, mqttMsgDirection, mqttMsg.getMsgId());\n        properties.put(msgKey, MqttUtils.serializableMsgBase64(mqttMsg));\n        writeProperties();\n    }\n\n    @Override\n    public MqttMsg removeMsg(MqttMsgDirection mqttMsgDirection, String clientId, int msgId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        MqttMsg mqttMsg = null;\n        String msgKey = getMsgPropertyKey(clientId, mqttMsgDirection, msgId);\n        Object value = properties.remove(msgKey);\n        writeProperties();\n        if (value != null) {\n            mqttMsg = MqttUtils.deserializableMsgBase64((String)value);\n        }\n        return mqttMsg;\n    }\n\n    @Override\n    public List<MqttMsg> getMsgList(MqttMsgDirection mqttMsgDirection, String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        List<MqttMsg> mqttMsgList = new ArrayList<>();\n        Set<String> propertyNameSet = properties.stringPropertyNames();\n        if (EmptyUtils.isNotEmpty(propertyNameSet)) {\n            String clientKeyPrefix;\n            switch (mqttMsgDirection) {\n                case SEND:\n                    clientKeyPrefix = MQTT_SEND_MSG_PREFIX + KEY_SEPARATOR + clientId + KEY_SEPARATOR;\n                    break;\n                case RECEIVE:\n                    clientKeyPrefix = MQTT_RECEIVE_MSG_PREFIX + KEY_SEPARATOR + clientId + KEY_SEPARATOR;\n                    break;\n                default:\n                    throw new IllegalArgumentException(mqttMsgDirection.name() + \" is illegal\");\n            }\n            for (String propertyName : propertyNameSet) {\n                if (propertyName.startsWith(clientKeyPrefix)) {\n                    MqttMsg mqttMsg = getMqttMsg(propertyName);\n                    if (mqttMsg != null) {\n                        mqttMsgList.add(mqttMsg);\n                    }\n                }\n            }\n        }\n        return mqttMsgList;\n    }\n\n    @Override\n    public void clearMsg(MqttMsgDirection mqttMsgDirection, String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        String clientSendKeyPrefix = MQTT_SEND_MSG_PREFIX + KEY_SEPARATOR + clientId + KEY_SEPARATOR;\n        String clientReceiveKeyPrefix = MQTT_RECEIVE_MSG_PREFIX + KEY_SEPARATOR + clientId + KEY_SEPARATOR;\n        Set<String> propertyNameSet = properties.stringPropertyNames();\n        if (EmptyUtils.isNotEmpty(propertyNameSet)) {\n            for (String propertyName : propertyNameSet) {\n                if (propertyName.startsWith(clientSendKeyPrefix) || propertyName.startsWith(clientReceiveKeyPrefix)) {\n                    properties.remove(propertyName);\n                }\n            }\n            writeProperties();\n        }\n\n    }\n\n    private String getMsgPropertyKey(String clientId, MqttMsgDirection mqttMsgDirection, int msgId) {\n        String key;\n        switch (mqttMsgDirection) {\n            case SEND:\n                key = MQTT_SEND_MSG_PREFIX + KEY_SEPARATOR + clientId + KEY_SEPARATOR + msgId;\n                break;\n            case RECEIVE:\n                key = MQTT_RECEIVE_MSG_PREFIX + KEY_SEPARATOR + clientId + KEY_SEPARATOR + msgId;\n                break;\n            default:\n                throw new IllegalArgumentException(mqttMsgDirection.name() + \" is illegal\");\n        }\n        return key;\n    }\n\n    private MqttMsg getMqttMsg(String msgPropertyKey) {\n        Object value = properties.get(msgPropertyKey);\n        MqttMsg mqttMsg = null;\n        if (value != null) {\n            mqttMsg = MqttUtils.deserializableMsgBase64((String) value);\n        }\n        return mqttMsg;\n    }\n\n    private void writeProperties() {\n        try {\n            properties.store(new FileOutputStream(propertiesFile), null);\n        } catch (IOException e) {\n            throw new MqttException(e);\n        }\n    }\n\n    private void loadProperties() {\n        try {\n            properties.load(new FileInputStream(propertiesFile));\n        } catch (IOException e) {\n            throw new MqttException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/store/MemoryMqttMsgStore.java",
    "content": "package io.github.netty.mqtt.client.store;\n\nimport io.github.netty.mqtt.client.constant.MqttMsgDirection;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * MQTT内存消息储存器\n * @author: xzc-coder\n */\npublic class MemoryMqttMsgStore implements MqttMsgStore {\n    private final Map<String, MqttMsgMap> clientSendMsgMap = new ConcurrentHashMap<>();\n\n    private final Map<String, MqttMsgMap> clientReceiveMsgMap = new ConcurrentHashMap<>();\n\n\n    @Override\n    public MqttMsg getMsg(MqttMsgDirection mqttMsgDirection, String clientId, int msgId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        MqttMsg mqttMsg;\n        switch (mqttMsgDirection) {\n            case SEND:\n                mqttMsg = getSendMsg(clientId, msgId);\n                break;\n            case RECEIVE:\n                mqttMsg = getReceiveMsg(clientId, msgId);\n                break;\n            default:\n                throw new IllegalArgumentException(mqttMsgDirection.name() + \" is illegal\");\n        }\n        return mqttMsg;\n    }\n\n    private MqttMsg getReceiveMsg(String clientId, int msgId) {\n        MqttMsg mqttMsg = null;\n        MqttMsgMap mqttMsgMap = clientReceiveMsgMap.get(clientId);\n        if (mqttMsgMap != null) {\n            mqttMsg = mqttMsgMap.get(msgId);\n        }\n        return mqttMsg;\n    }\n\n    private MqttMsg getSendMsg(String clientId, int msgId) {\n        MqttMsg mqttMsg = null;\n        MqttMsgMap mqttMsgMap = clientSendMsgMap.get(clientId);\n        if (mqttMsgMap != null) {\n            mqttMsg = mqttMsgMap.get(msgId);\n        }\n        return mqttMsg;\n    }\n\n    @Override\n    public void putMsg(MqttMsgDirection mqttMsgDirection, String clientId, MqttMsg mqttMsg) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsg, \"mqttMsg is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        switch (mqttMsgDirection) {\n            case SEND:\n                putSendMsg(clientId, mqttMsg);\n                break;\n            case RECEIVE:\n                putReceiveMsg(clientId, mqttMsg);\n                break;\n            default:\n                throw new IllegalArgumentException(mqttMsgDirection.name() + \" is illegal\");\n        }\n    }\n\n\n    private void putSendMsg(String clientId, MqttMsg mqttMsg) {\n        MqttMsgMap mqttMsgMap = clientSendMsgMap.get(clientId);\n        if (mqttMsgMap == null) {\n            synchronized (clientId.intern()) {\n                mqttMsgMap = clientSendMsgMap.getOrDefault(clientId, new MqttMsgMap());\n                clientSendMsgMap.putIfAbsent(clientId, mqttMsgMap);\n            }\n        }\n        mqttMsgMap.put(mqttMsg);\n    }\n\n    private void putReceiveMsg(String clientId, MqttMsg mqttMsg) {\n        MqttMsgMap mqttMsgMap = clientReceiveMsgMap.get(clientId);\n        if (mqttMsgMap == null) {\n            synchronized (clientId.intern()) {\n                mqttMsgMap = clientReceiveMsgMap.getOrDefault(clientId, new MqttMsgMap());\n                clientReceiveMsgMap.putIfAbsent(clientId, mqttMsgMap);\n            }\n        }\n        mqttMsgMap.put(mqttMsg);\n    }\n\n    @Override\n    public MqttMsg removeMsg(MqttMsgDirection mqttMsgDirection, String clientId, int msgId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        MqttMsg mqttMsg;\n        switch (mqttMsgDirection) {\n            case SEND:\n                mqttMsg = removeSendMsg(clientId, msgId);\n                break;\n            case RECEIVE:\n                mqttMsg = removeReceiveMsg(clientId, msgId);\n                break;\n            default:\n                throw new IllegalArgumentException(mqttMsgDirection.name() + \" is illegal\");\n        }\n        return mqttMsg;\n    }\n\n    private MqttMsg removeReceiveMsg(String clientId, int msgId) {\n        MqttMsg mqttMsg = null;\n        MqttMsgMap mqttMsgMap = clientReceiveMsgMap.get(clientId);\n        if (mqttMsgMap != null) {\n            mqttMsg = mqttMsgMap.remove(msgId);\n        }\n        return mqttMsg;\n    }\n\n    private MqttMsg removeSendMsg(String clientId, int msgId) {\n        MqttMsg mqttMsg = null;\n        MqttMsgMap mqttMsgMap = clientSendMsgMap.get(clientId);\n        if (mqttMsgMap != null) {\n            mqttMsg = mqttMsgMap.remove(msgId);\n        }\n        return mqttMsg;\n    }\n\n    @Override\n    public List<MqttMsg> getMsgList(MqttMsgDirection mqttMsgDirection, String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        List<MqttMsg> mqttMsgList = new ArrayList<>();\n        List<MqttMsg> sentMsgList = getSendMsgList(clientId);\n        List<MqttMsg> receiveMsgList = getReceiveMsgList(clientId);\n        switch (mqttMsgDirection) {\n            case SEND:\n                mqttMsgList.addAll(sentMsgList);\n                break;\n            case RECEIVE:\n                mqttMsgList.addAll(receiveMsgList);\n                break;\n            default:\n                throw new IllegalArgumentException(mqttMsgDirection.name() + \" is illegal\");\n        }\n        return mqttMsgList;\n    }\n\n    private List<MqttMsg> getReceiveMsgList(String clientId) {\n        List<MqttMsg> receiveMsgList;\n        MqttMsgMap mqttMsgMap = clientReceiveMsgMap.get(clientId);\n        if (mqttMsgMap != null) {\n            receiveMsgList = mqttMsgMap.getList();\n        } else {\n            receiveMsgList = new ArrayList<>();\n        }\n        return receiveMsgList;\n    }\n\n    private List<MqttMsg> getSendMsgList(String clientId) {\n        List<MqttMsg> sentMsgList;\n        MqttMsgMap mqttMsgMap = clientSendMsgMap.get(clientId);\n        if (mqttMsgMap != null) {\n            sentMsgList = mqttMsgMap.getList();\n        } else {\n            sentMsgList = new ArrayList<>();\n        }\n        return sentMsgList;\n    }\n\n    @Override\n    public void clearMsg(MqttMsgDirection mqttMsgDirection, String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        switch (mqttMsgDirection) {\n            case SEND:\n                clientSendMsgMap.remove(clientId);\n                break;\n            case RECEIVE:\n                clientReceiveMsgMap.remove(clientId);\n                break;\n            default:\n                throw new IllegalArgumentException(mqttMsgDirection.name() + \" is illegal\");\n        }\n    }\n\n    /**\n     * MQTT的消息Map对象（封装Map好操作）\n     */\n    private static class MqttMsgMap {\n        /**\n         * 存储的MQTT消息Map，key是消息ID，value是MQTT消息\n         */\n        private final Map<Integer, MqttMsg> mqttMsgMap = new ConcurrentHashMap<>();\n\n        private MqttMsg get(int msgId) {\n            return mqttMsgMap.get(msgId);\n        }\n\n        private MqttMsg remove(int msgId) {\n            return mqttMsgMap.remove(msgId);\n        }\n\n        private void put(MqttMsg mqttMsg) {\n            mqttMsgMap.put(mqttMsg.getMsgId(), mqttMsg);\n        }\n\n        private List<MqttMsg> getList() {\n            return new ArrayList<>(mqttMsgMap.values());\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/store/MqttMsgIdCache.java",
    "content": "package io.github.netty.mqtt.client.store;\n\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Collectors;\n\n/**\n * MQTT的消息ID缓存器\n * @author: xzc-coder\n */\npublic class MqttMsgIdCache {\n\n\n    /**\n     * MQTT的消息ID的Map\n     */\n    private static final Map<String, MqttMsgIdInfo> MSG_ID_INFO_MAP = new ConcurrentHashMap<>();\n\n    private MqttMsgIdCache() {\n\n    }\n\n    /**\n     * 占用客户端的消息ID，需要在MQTT的客户端创建时占用（其他时候占用会产生不可预知问题），如果是不清理会话，则调用该方法让其占用一些未释放的消息ID，\n     * 该方法是非线程安全的，在消息存储器初始化时单线程占用即可\n     *\n     * @param clientId     客户端ID\n     * @param msgIdCollect 消息ID集合\n     */\n    public static void occupyMsgId(String clientId, Collection<Integer> msgIdCollect) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notEmpty(msgIdCollect, \"msgIdCollect is empty\");\n        msgIdCollect = msgIdCollect.stream()\n                .filter(msgId -> (msgId >= MqttConstant.MQTT_MIN_MSG_ID && msgId <= MqttConstant.MQTT_MAX_MSG_ID))\n                .sorted(Comparator.naturalOrder())\n                .collect(Collectors.toSet());\n        if(msgIdCollect.size() >= MqttConstant.MQTT_MAX_MSG_ID_NUMBER) {\n            throw new IllegalArgumentException(\"msgIdCollect size to long\");\n        }\n        MqttMsgIdInfo mqttMsgIdInfo = getMqttMsgIdInfo(clientId);\n        mqttMsgIdInfo.putMsgIds(msgIdCollect);\n    }\n\n    /**\n     * 获取客户端一个可用的消息ID\n     *\n     * @param clientId 客户端ID\n     * @return 消息ID\n     */\n    public static int nextMsgId(String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        MqttMsgIdInfo mqttMsgIdInfo = getMqttMsgIdInfo(clientId);\n        return mqttMsgIdInfo.getNextId();\n    }\n\n    /**\n     * 释放客户端的一个消息ID\n     *\n     * @param clientId 客户端ID\n     * @param msgId    消息ID\n     */\n    public static void releaseMsgId(String clientId, int msgId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        MqttMsgIdInfo mqttMsgIdInfo = MSG_ID_INFO_MAP.get(clientId);\n        if (mqttMsgIdInfo != null) {\n            mqttMsgIdInfo.releaseMsgId(msgId);\n        }\n    }\n\n    /**\n     * 清理客户端的消息ID\n     *\n     * @param clientId 客户端ID\n     */\n    public static void clearMsgId(String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        MSG_ID_INFO_MAP.remove(clientId);\n    }\n\n    /**\n     * 获取客户端对应的MqttMsgIdInfo\n     *\n     * @param clientId 客户端ID\n     * @return MqttMsgIdInfo\n     */\n    private static MqttMsgIdInfo getMqttMsgIdInfo(String clientId) {\n        MqttMsgIdInfo mqttMsgIdInfo = MSG_ID_INFO_MAP.get(clientId);\n        if (mqttMsgIdInfo == null) {\n            synchronized (clientId.intern()) {\n                mqttMsgIdInfo = MSG_ID_INFO_MAP.getOrDefault(clientId, new MqttMsgIdInfo());\n                MSG_ID_INFO_MAP.putIfAbsent(clientId, mqttMsgIdInfo);\n            }\n        }\n        return mqttMsgIdInfo;\n    }\n\n    /**\n     * MQTT的消息ID信息类，一个客户端对应一个，存储和生成相关的消息ID\n     */\n    private static class MqttMsgIdInfo {\n        /**\n         * 被使用的消息ID的Set\n         */\n        private final Set<Integer> useMsgIdSet = ConcurrentHashMap.newKeySet();\n        /**\n         * 下一个可用的消息ID，从0开始到65535\n         */\n        private final AtomicInteger nextMsgId = new AtomicInteger(MqttConstant.MQTT_MIN_MSG_ID);\n\n        /**\n         * 获取一个的消息ID\n         *\n         * @return 可用的消息ID\n         */\n        private int getNextId() {\n            //cas获取值，保证并发安全\n            for (; ; ) {\n                //当前可用消息ID\n                int msgId = nextMsgId.get();\n                //下一个值\n                int next = msgId;\n                do {\n                    next++;\n                    //如果下一个值大于最大值，则从最小值开始\n                    next = (next > MqttConstant.MQTT_MAX_MSG_ID ? MqttConstant.MQTT_MIN_MSG_ID : next);\n                } while (useMsgIdSet.contains(next));\n                //cas设值成功，则表明操作成功\n                if (nextMsgId.compareAndSet(msgId, next)) {\n                    //添加到占用列表\n                    useMsgIdSet.add(msgId);\n                    return msgId;\n                }\n            }\n        }\n\n        /**\n         * 释放消息ID\n         *\n         * @param msgId\n         */\n        private void releaseMsgId(int msgId) {\n            useMsgIdSet.remove(msgId);\n        }\n\n        /**\n         * 添加消息ID\n         *\n         * @param msgIdCollect 消息ID集合\n         */\n        private void putMsgIds(Collection<Integer> msgIdCollect) {\n            for (Integer msgId : msgIdCollect) {\n                if (msgId != null) {\n                    useMsgIdSet.add(msgId);\n                    if (msgId.equals(nextMsgId.get())) {\n                        nextMsgId.incrementAndGet();\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/store/MqttMsgStore.java",
    "content": "package io.github.netty.mqtt.client.store;\n\nimport io.github.netty.mqtt.client.constant.MqttMsgDirection;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\n\nimport java.util.List;\n\n/**\n * MQTT消息储存器接口\n * @author: xzc-coder\n */\npublic interface MqttMsgStore {\n\n    /**\n     * 获取一个MQTT消息\n     *\n     * @param mqttMsgDirection 消息的方向\n     * @param clientId         客户端ID\n     * @param msgId            消息ID\n     * @return MQTT消息\n     */\n    MqttMsg getMsg(MqttMsgDirection mqttMsgDirection, String clientId, int msgId);\n\n    /**\n     * 存储一个MQTT消息\n     *\n     * @param mqttMsgDirection 方向\n     * @param clientId         客户端ID\n     * @param mqttMsg          MQTT消息\n     */\n    void putMsg(MqttMsgDirection mqttMsgDirection, String clientId, MqttMsg mqttMsg);\n\n    /**\n     * 移除一个MQTT消息\n     *\n     * @param mqttMsgDirection 方向\n     * @param clientId         客户端ID\n     * @param msgId            消息ID\n     * @return MQTT消息\n     */\n    MqttMsg removeMsg(MqttMsgDirection mqttMsgDirection, String clientId, int msgId);\n\n    /**\n     * 拿到客户端的MQTT消息列表\n     *\n     * @param mqttMsgDirection 方向\n     * @param clientId         客户端ID\n     * @return MQTT消息列表\n     */\n    List<MqttMsg> getMsgList(MqttMsgDirection mqttMsgDirection, String clientId);\n\n    /**\n     * 清理客户端的MQTT消息\n     *\n     * @param mqttMsgDirection 方向\n     * @param clientId         客户端ID\n     */\n    void clearMsg(MqttMsgDirection mqttMsgDirection, String clientId);\n\n    /**\n     * 清理客户端的MQTT消息\n     *\n     * @param clientId 客户端ID\n     */\n    default void clearMsg(String clientId) {\n        clearMsg(MqttMsgDirection.SEND, clientId);\n        clearMsg(MqttMsgDirection.RECEIVE, clientId);\n    }\n\n    /**\n     * 关闭消息存储器\n     */\n    default void close() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/store/RedisMqttMsgStore.java",
    "content": "package io.github.netty.mqtt.client.store;\n\nimport io.github.netty.mqtt.client.constant.MqttMsgDirection;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\nimport io.github.netty.mqtt.client.support.util.MqttUtils;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisPool;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * MQTT的Redis消息存储器\n * @author: zizai\n **/\npublic class RedisMqttMsgStore implements MqttMsgStore {\n\n\n    /**\n     * 获取redis发送消息的key，后面跟客户端ID\n     */\n    private static final String MQTT_SEND_MSG_REDIS_PREFIX = \"mqtt-client:send-message:\";\n    /**\n     * 获取redis接受消息的key，后面跟客户端ID\n     */\n    private static final String MQTT_RECEIVE_MSG_REDIS_PREFIX = \"mqtt-client:receive-message:\";\n\n    private final JedisPool jedisPool;\n\n\n    public RedisMqttMsgStore(JedisPool jedisPool) {\n        AssertUtils.notNull(jedisPool, \"jedisPool is null\");\n        this.jedisPool = jedisPool;\n    }\n\n\n    @Override\n    public MqttMsg getMsg(MqttMsgDirection mqttMsgDirection, String clientId, int msgId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        MqttMsg mqttMsg;\n        try (Jedis jedis = jedisPool.getResource()) {\n            byte[] key = getKey(mqttMsgDirection, clientId);\n            byte[] field = String.valueOf(msgId).getBytes(StandardCharsets.UTF_8);\n            mqttMsg = MqttUtils.deserializableMsg(jedis.hget(key, field));\n            return mqttMsg;\n        }\n    }\n\n    @Override\n    public void putMsg(MqttMsgDirection mqttMsgDirection, String clientId, MqttMsg mqttMsg) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsg, \"mqttMsg is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        try (Jedis jedis = jedisPool.getResource()) {\n            byte[] key = getKey(mqttMsgDirection, clientId);\n            byte[] field = String.valueOf(mqttMsg.getMsgId()).getBytes(StandardCharsets.UTF_8);\n            jedis.hset(key, field, MqttUtils.serializableMsg(mqttMsg));\n        }\n    }\n\n    @Override\n    public MqttMsg removeMsg(MqttMsgDirection mqttMsgDirection, String clientId, int msgId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        MqttMsg mqttMsg;\n        try (Jedis jedis = jedisPool.getResource()) {\n            byte[] key = getKey(mqttMsgDirection, clientId);\n            byte[] field = String.valueOf(msgId).getBytes(StandardCharsets.UTF_8);\n            mqttMsg = MqttUtils.deserializableMsg(jedis.hget(key, field));\n            jedis.hdel(key, field);\n            return mqttMsg;\n        }\n    }\n\n    @Override\n    public List<MqttMsg> getMsgList(MqttMsgDirection mqttMsgDirection, String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        List<MqttMsg> mqttMsgList = new ArrayList<>();\n        try (Jedis jedis = jedisPool.getResource()) {\n            byte[] key = getKey(mqttMsgDirection, clientId);\n            Map<byte[], byte[]> msgMap = jedis.hgetAll(key);\n            if (EmptyUtils.isNotEmpty(msgMap)) {\n                for (byte[] msgBytes : msgMap.values()) {\n                    MqttMsg mqttMsg = MqttUtils.deserializableMsg(msgBytes);\n                    mqttMsgList.add(mqttMsg);\n                }\n            }\n        }\n        return mqttMsgList;\n    }\n\n\n    @Override\n    public void clearMsg(MqttMsgDirection mqttMsgDirection, String clientId) {\n        AssertUtils.notNull(clientId, \"clientId is null\");\n        AssertUtils.notNull(mqttMsgDirection, \"mqttMsgStoreType is null\");\n        byte[] key = getKey(mqttMsgDirection, clientId);\n        try (Jedis jedis = jedisPool.getResource()) {\n            jedis.del(key);\n        }\n    }\n\n    @Override\n    public void close() {\n        if (jedisPool != null) {\n            jedisPool.close();\n        }\n    }\n\n\n    private byte[] getKey(MqttMsgDirection mqttMsgDirection, String clientId) {\n        byte[] key;\n        switch (mqttMsgDirection) {\n            case SEND:\n                key = (MQTT_SEND_MSG_REDIS_PREFIX + clientId).getBytes(StandardCharsets.UTF_8);\n                break;\n            case RECEIVE:\n                key = (MQTT_RECEIVE_MSG_REDIS_PREFIX + clientId).getBytes(StandardCharsets.UTF_8);\n                break;\n            default:\n                throw new IllegalArgumentException(mqttMsgDirection.name() + \" is illegal\");\n        }\n        return key;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/future/DefaultMqttFuture.java",
    "content": "package io.github.netty.mqtt.client.support.future;\n\n\nimport io.github.netty.mqtt.client.support.util.LogUtils;\n\nimport java.util.Date;\nimport java.util.Set;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.concurrent.locks.Condition;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * 默认的MqttFuture实现\n * @author: xzc-coder\n */\npublic class DefaultMqttFuture<T> extends MqttFuture<T> {\n\n    /**\n     * 成功无返回值标志\n     */\n    private static final Object SUCCESS = new Object();\n\n    /**\n     * 被阻塞的线程\n     */\n    private final Set<Thread> waitThreads = ConcurrentHashMap.newKeySet();\n\n    /**\n     * 结果\n     */\n    private final AtomicReference<Object> result = new AtomicReference<>();\n\n    /**\n     * 锁\n     */\n    private final Lock lock = new ReentrantLock();\n\n    /**\n     * 条件\n     */\n    private final Condition condition = this.lock.newCondition();\n\n    public DefaultMqttFuture(MqttFutureKey mqttFutureKey) {\n        super(mqttFutureKey);\n    }\n\n    public DefaultMqttFuture(MqttFutureKey mqttFutureKey, Object parameter) {\n        super(mqttFutureKey, parameter);\n    }\n\n    public DefaultMqttFuture(String clientId, Object key) {\n        super(clientId, key);\n    }\n\n\n    public DefaultMqttFuture(String clientId, Object key, Object parameter) {\n        super(clientId, key, parameter);\n    }\n\n    @Override\n    public void awaitComplete() throws InterruptedException {\n        this.doAwaitCompleteResult(null, true);\n    }\n\n    @Override\n    public boolean awaitComplete(long timeout) throws InterruptedException {\n        return this.doAwaitCompleteResult(timeout, true);\n    }\n\n    @Override\n    public boolean awaitCompleteUninterruptibly() {\n        try {\n            return this.doAwaitCompleteResult(null, false);\n        } catch (InterruptedException e) {\n            //应该不存在这种情况\n            throw new InternalError();\n        }\n    }\n\n    @Override\n    public boolean awaitCompleteUninterruptibly(long timeout) {\n        try {\n            return doAwaitCompleteResult(timeout, false);\n        } catch (InterruptedException e) {\n            //应该不存在这种情况\n            throw new InternalError();\n        }\n    }\n\n    private boolean doAwaitCompleteResult(Long timeout, boolean interruptable) throws InterruptedException {\n        this.doAwaitComplete(timeout, interruptable);\n        return this.isDone();\n    }\n\n    private void doSyncResult(Long timeout,boolean interruptable) throws InterruptedException, TimeoutException {\n        this.doAwaitComplete(timeout, interruptable);\n        if(!this.isDone()) {\n            throw new TimeoutException();\n        }else {\n            if (!this.isSuccess()) {\n                rethrowIfFailed();\n            }\n        }\n    }\n\n    /**\n     * 等待完成\n     *\n     * @param timeout       超时时间\n     * @param interruptable 是否可打断\n     * @throws InterruptedException 打断异常\n     */\n    private void doAwaitComplete(Long timeout, boolean interruptable) throws InterruptedException {\n        //如果已经完成，则直接结束\n        if (this.isDone()) {\n            return;\n        }\n        //设置超时时间\n        Long deadline = null;\n        if (timeout != null) {\n            if (timeout <= 0) {\n                return;\n            }\n            deadline = System.currentTimeMillis() + timeout;\n        }\n        //可以被打断，并且进来之前已经被打断 则抛出异常\n        if (interruptable && Thread.interrupted()) {\n            throw new InterruptedException();\n        }\n\n        //中间过程有没有被打断\n        boolean interrupted = false;\n        this.lock.lock();\n        try {\n            while (!this.isDone()) {\n                try {\n                    //阻塞等待\n                    this.waitThreads.add(Thread.currentThread());\n                    if (deadline != null) {\n                        //超时等待\n                        this.condition.awaitUntil(new Date(deadline));\n                        //如果到了超时时间，则结束\n                        if (System.currentTimeMillis() >= deadline) {\n                            break;\n                        }\n                    } else {\n                        //等待\n                        this.condition.await();\n                    }\n                } catch (InterruptedException interruptedException) {\n                    //如果允许被打断，则抛出异常\n                    if (interruptable) {\n                        throw interruptedException;\n                    } else {\n                        //不允许被打断 则标记中间过程被打断\n                        interrupted = true;\n                    }\n                } finally {\n                    //移除当前线程等待\n                    this.removeWaiter(Thread.currentThread());\n                }\n            }\n            //如果不允许被打断并且中间过程被打断，则重新打断一次\n            if (!interruptable && interrupted) {\n                Thread.currentThread().interrupt();\n            }\n        } finally {\n            this.lock.unlock();\n        }\n    }\n\n    /**\n     * 移除一个等待\n     *\n     * @param currentThread 线程\n     */\n    private void removeWaiter(Thread currentThread) {\n        this.waitThreads.remove(currentThread);\n    }\n\n\n    @Override\n    protected boolean doSetSuccess(T result) {\n        Object value = (result == null) ? SUCCESS : result;\n        return this.setValue(value);\n    }\n\n    @Override\n    public T getResult() {\n        return getRealResult();\n    }\n\n    @Override\n    public MqttFuture sync() throws InterruptedException, TimeoutException {\n        this.doSyncResult(null,true);\n        return this;\n    }\n\n    @Override\n    public MqttFuture sync(long timeout) throws InterruptedException, TimeoutException {\n        this.doSyncResult(timeout,true);\n        return this;\n    }\n\n    @Override\n    public MqttFuture syncUninterruptibly() {\n        try {\n            this.doSyncResult(null,false);\n        } catch (InterruptedException | TimeoutException e) {\n            //忽略 应该不存在这种情况\n        }\n        return this;\n    }\n\n    @Override\n    public MqttFuture syncUninterruptibly(long timeout) throws TimeoutException {\n        try {\n            this.doSyncResult(timeout,false);\n        } catch (InterruptedException e) {\n            //忽略 应该不存在这种情况\n        }\n        return this;\n    }\n\n    @Override\n    protected boolean doSetFailure(Throwable cause) {\n        if (cause == null) {\n            throw new NullPointerException(\"this Throwable cause is null\");\n        }\n        CauseHolder causeHolder = new CauseHolder(cause);\n        return this.setValue(causeHolder);\n    }\n\n    @Override\n    public boolean isDone() {\n        return this.result.get() != null;\n    }\n\n    @Override\n    public boolean isSuccess() {\n        return this.result.get() != null && !(this.result.get() instanceof CauseHolder);\n    }\n\n    @Override\n    public Throwable getCause() {\n        Throwable cause = null;\n        if (this.result.get() != null && this.result.get() instanceof CauseHolder) {\n            CauseHolder causeHolder = (CauseHolder) this.result.get();\n            cause = causeHolder.cause;\n        }\n        return cause;\n    }\n\n    @Override\n    public void addListener(MqttFutureListener<T> listener) {\n        this.listeners.add(listener);\n        if (this.isDone()) {\n            notifyListener(listener);\n        }\n    }\n\n    /**\n     * 唤醒监听器\n     *\n     * @param listener 监听器\n     */\n    private void notifyListener(MqttFutureListener<T> listener) {\n        try {\n            boolean success = this.removeListener(listener);\n            if (success) {\n                listener.operationComplete(this);\n            }\n        } catch (Throwable throwable) {\n            LogUtils.error(DefaultMqttFuture.class,\"exception occurred in listener call,cause: \" + throwable.getMessage());\n        }\n    }\n\n    @Override\n    public boolean removeListener(MqttFutureListener<T> listener) {\n        return this.listeners.remove(listener);\n    }\n\n    @Override\n    public boolean cancel() throws Exception {\n        CauseHolder causeHolder = new CauseHolder(new CancellationException());\n        return setValue(causeHolder);\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return result.get() instanceof CauseHolder && ((CauseHolder) this.result.get()).cause instanceof CancellationException;\n    }\n\n    /**\n     * 设值\n     *\n     * @param value 值\n     * @return 是否成功\n     */\n    private boolean setValue(Object value) {\n        //cas设值成功\n        if (this.result.compareAndSet(null, value)) {\n            notifyAllThreadAndListener();\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * 唤醒所有的等待线程\n     */\n    private void notifyAllThreadAndListener() {\n        this.lock.lock();\n        try {\n            if (this.waitThreads.size() > 0) {\n                this.condition.signalAll();\n            }\n        } finally {\n            this.lock.unlock();\n        }\n\n        for (MqttFutureListener<T> listener : this.listeners) {\n            this.notifyListener(listener);\n        }\n    }\n\n    /**\n     * 拿到真实的值\n     *\n     * @return 值\n     */\n    private T getRealResult() {\n        //如果是成功的空值，则返回null\n        if (this.result.get() == SUCCESS) {\n            return null;\n        }\n        return (T) this.result.get();\n    }\n\n\n    /**\n     * 异常原因\n     */\n    private static final class CauseHolder {\n        /**\n         * 原因\n         */\n        private final Throwable cause;\n\n        CauseHolder(Throwable cause) {\n            this.cause = cause;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/future/MqttFuture.java",
    "content": "package io.github.netty.mqtt.client.support.future;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\nimport io.netty.util.internal.PlatformDependent;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArraySet;\nimport java.util.concurrent.TimeoutException;\n\n/**\n * MqttFuture基类\n * @param <T> 响应结果类\n * @author: xzc-coder\n */\npublic abstract class MqttFuture<T> {\n\n    /**\n     * Future的Map\n     */\n    private static final Map<MqttFutureKey, MqttFuture> FUTURES = new ConcurrentHashMap();\n\n    /**\n     * future的ID，不能同时存在相同的ID\n     */\n    protected final MqttFutureKey futureKey;\n\n    /**\n     * 参数\n     */\n    protected final Object parameter;\n\n    /**\n     * 监听器\n     */\n    protected final Set<MqttFutureListener<T>> listeners = new CopyOnWriteArraySet<>();\n\n\n    public MqttFuture(MqttFutureKey mqttFutureKey) {\n        this(mqttFutureKey, null);\n    }\n\n    public MqttFuture(MqttFutureKey mqttFutureKey, Object parameter) {\n        AssertUtils.notNull(mqttFutureKey, \"mqttFutureKey is null\");\n        if (FUTURES.containsKey(mqttFutureKey)) {\n            throw new IllegalStateException(\"client: \" + mqttFutureKey.getClientId() + \" ,key: \" + mqttFutureKey.getKey() + \" ,already exists\");\n        }\n        FUTURES.put(mqttFutureKey, this);\n        this.futureKey = mqttFutureKey;\n        this.parameter = parameter;\n    }\n\n\n    public MqttFuture(String clientId, Object key) {\n        this(clientId, key, null);\n    }\n\n    public MqttFuture(String clientId, Object key, Object parameter) {\n        this(new MqttFutureKey(clientId, key), parameter);\n    }\n\n\n    public static MqttFuture getFuture(MqttFutureKey mqttFutureKey) {\n        return FUTURES.get(mqttFutureKey);\n    }\n\n    public static MqttFuture getFuture(String clientId, Object key) {\n        return FUTURES.get(new MqttFutureKey(clientId, key));\n    }\n\n    public MqttFutureKey getFutureKey() {\n        return futureKey;\n    }\n\n    /**\n     * 获取参数\n     *\n     * @return 参数\n     */\n    public Object getParameter() {\n        return parameter;\n    }\n\n    /**\n     * 移除一个Future\n     *\n     * @param clientId 客户端ID\n     * @param key Future的key\n     * @return 被移除的Future\n     */\n    public static MqttFuture removeFuture(String clientId, String key) {\n        return FUTURES.remove(new MqttFutureKey(clientId, key));\n    }\n\n    /**\n     * 阻塞等待至响应\n     *\n     * @throws InterruptedException 打断异常\n     */\n    public abstract void awaitComplete() throws InterruptedException;\n\n    /**\n     * 阻塞等待至超时\n     *\n     * @param timeout 超时时间 单位毫秒\n     * @return 是否完成\n     * @throws InterruptedException 打断异常\n     */\n    public abstract boolean awaitComplete(long timeout) throws InterruptedException;\n\n    /**\n     * 阻塞等待至超时，并且忽略打断异常\n     *\n     * @return 是否完成\n     */\n    public abstract boolean awaitCompleteUninterruptibly();\n\n    /**\n     * 阻塞等待至超时时间，并且忽略打断异常\n     *\n     * @param timeout 超时时间 单位毫秒\n     * @return 是否完成\n     */\n    public abstract boolean awaitCompleteUninterruptibly(long timeout);\n\n    /**\n     * 设置响应结果并且唤醒之前等待的线程\n     *\n     * @param result 响应结果\n     * @return 是否设置成功，如果先被超时唤醒，则返回 false，否则返回 true，不管结果如何，都会被唤醒。\n     */\n    public boolean setSuccess(T result) {\n        try {\n            return doSetSuccess(result);\n        } finally {\n            FUTURES.remove(this.futureKey);\n        }\n    }\n\n    /**\n     * 设值成功值\n     *\n     * @param result 值\n     * @return 是否成功\n     */\n    protected abstract boolean doSetSuccess(T result);\n\n    /**\n     * 返回成功的结果\n     *\n     * @return 成功的结果\n     */\n    public abstract T getResult();\n\n    /**\n     * 设置执行失败并且唤醒之前等待的线程\n     *\n     * @param cause 异常原因\n     * @return 操作是否成功\n     */\n    public boolean setFailure(Throwable cause) {\n        try {\n            return doSetFailure(cause);\n        } finally {\n            FUTURES.remove(this.futureKey);\n        }\n    }\n\n    /**\n     * 设置成功或者失败\n     *\n     * @param success 是否成功\n     * @param result  成功时的结果\n     * @param cause   失败时的异常信息\n     * @return 操作是否成功\n     */\n    public boolean set(boolean success, T result, Throwable cause) {\n        boolean operateSuccess = false;\n        if (success) {\n            operateSuccess = setSuccess(result);\n        } else {\n            operateSuccess = setFailure(cause);\n        }\n        return operateSuccess;\n    }\n\n    /**\n     * 阻塞等待至完成（如果失败则会抛出异常）\n     *\n     * @return MqttFuture\n     * @throws InterruptedException 打断异常\n     * @throws TimeoutException 超时异常\n     */\n    public abstract MqttFuture sync() throws InterruptedException, TimeoutException;\n\n    /**\n     * 阻塞等待至完成（如果失败则会抛出异常）\n     *\n     * @param timeout 超时毫秒\n     * @return MqttFuture\n     * @throws InterruptedException 打断异常\n     * @throws TimeoutException 超时异常\n     */\n    public abstract MqttFuture sync(long timeout) throws InterruptedException, TimeoutException;\n\n\n    /**\n     * 阻塞等待至完成（如果失败则会抛出异常），忽略打断异常\n     *\n     * @return MqttFuture\n     */\n    public abstract MqttFuture syncUninterruptibly();\n\n    /**\n     * 阻塞等待至完成（如果失败则会抛出异常），忽略打断异常\n     *\n     * @param timeout 超时的毫秒\n     * @return MqttFuture\n     * @throws TimeoutException 超时异常\n     */\n    public abstract MqttFuture syncUninterruptibly(long timeout) throws TimeoutException;\n\n    /**\n     * 设失败值\n     *\n     * @param cause 异常原因\n     * @return 是否成功\n     */\n    protected abstract boolean doSetFailure(Throwable cause);\n\n    /**\n     * 是否完成\n     *\n     * @return 是否被唤醒\n     */\n    public abstract boolean isDone();\n\n    /**\n     * 是否执行成功\n     *\n     * @return 操作是否成功\n     */\n    public abstract boolean isSuccess();\n\n    /**\n     * 获取失败异常\n     *\n     * @return 异常\n     */\n    public abstract Throwable getCause();\n\n\n    /**\n     * 添加一个监听器\n     *\n     * @param listener 要添加的监听器\n     */\n    public abstract void addListener(MqttFutureListener<T> listener);\n\n    /**\n     * 移除一个监听器\n     *\n     * @param listener 要移除的监听器\n     * @return 如果包含此listener则返回true\n     */\n    public abstract boolean removeListener(MqttFutureListener<T> listener);\n\n    /**\n     /**\n     * 取消任务\n     *\n     * @return 取消操作是否成功\n     * @throws Exception 异常\n     */\n    public abstract boolean cancel() throws Exception;\n\n    /**\n     * 是否取消成功\n     *\n     * @return 是否取消成功\n     */\n    public abstract boolean isCancelled();\n\n\n    protected void rethrowIfFailed() {\n        Throwable cause = getCause();\n        if (cause == null) {\n            return;\n        }\n        PlatformDependent.throwException(cause);\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/future/MqttFutureKey.java",
    "content": "package io.github.netty.mqtt.client.support.future;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\n\nimport java.util.Objects;\n\n/**\n * MqttFuture的Key\n * @author: xzc-coder\n */\npublic class MqttFutureKey {\n\n    /**\n     * 客户端ID\n     */\n    private final String clientId;\n    /**\n     * Future的key\n     */\n    protected final Object key;\n\n    public MqttFutureKey(String clientId, Object key) {\n        AssertUtils.notNull(clientId,\"clientId is null\");\n        AssertUtils.notNull(key,\"key is null\");\n        this.clientId = clientId;\n        this.key = key;\n    }\n\n    public String getClientId() {\n        return clientId;\n    }\n\n    public Object getKey() {\n        return key;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof MqttFutureKey)) {\n            return false;\n        }\n        MqttFutureKey that = (MqttFutureKey) o;\n        return Objects.equals(clientId, that.clientId) && Objects.equals(key, that.key);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(clientId, key);\n    }\n\n\n    @Override\n    public String toString() {\n        return \"MqttFutureKey{\" +\n                \"clientId='\" + clientId + '\\'' +\n                \", key=\" + key +\n                '}';\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/future/MqttFutureListener.java",
    "content": "package io.github.netty.mqtt.client.support.future;\n\n/**\n * MQTTFuture的监听器\n * @author: xzc-coder\n */\npublic interface MqttFutureListener<T> {\n\n    /**\n     * 操作完成回调\n     *\n     * @param mqttFuture Future\n     * @throws Exception 异常\n     */\n    void operationComplete(MqttFuture<T> mqttFuture) throws Throwable;\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/future/MqttFutureWrapper.java",
    "content": "package io.github.netty.mqtt.client.support.future;\n\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\n\nimport java.util.concurrent.TimeoutException;\n\n/**\n * MqttFuture的包装类\n *\n * @author: xzc-coder\n */\npublic class MqttFutureWrapper {\n\n    private final MqttFuture mqttFuture;\n\n    public MqttFutureWrapper(MqttFuture mqttFuture) {\n        AssertUtils.notNull(mqttFuture, \"mqttFuture is null\");\n        this.mqttFuture = mqttFuture;\n    }\n\n    /**\n     * 阻塞等待至响应\n     *\n     * @throws InterruptedException 打断异常\n     */\n    public void awaitComplete() throws InterruptedException {\n        mqttFuture.awaitComplete();\n    }\n\n    /**\n     * 阻塞等待至超时\n     *\n     * @param timeout 超时时间 单位毫秒\n     * @return 是否完成\n     * @throws InterruptedException 打断异常\n     */\n    public boolean awaitComplete(long timeout) throws InterruptedException {\n        return mqttFuture.awaitComplete(timeout);\n    }\n\n    /**\n     * 阻塞等待至超时，并且忽略打断异常\n     *\n     * @return 是否完成\n     */\n    public boolean awaitCompleteUninterruptibly() {\n        return mqttFuture.awaitCompleteUninterruptibly();\n    }\n\n    /**\n     * 阻塞等待至超时时间，并且忽略打断异常\n     *\n     * @param timeout 超时时间 单位毫秒\n     * @return 是否完成\n     */\n    public boolean awaitCompleteUninterruptibly(long timeout) {\n        return mqttFuture.awaitCompleteUninterruptibly(timeout);\n    }\n\n    /**\n     * 阻塞等待至超时时间\n     *\n     * @return MqttFuture\n     * @throws InterruptedException 打断异常\n     * @throws TimeoutException     超时异常\n     */\n    public MqttFuture sync() throws InterruptedException, TimeoutException {\n        return mqttFuture.sync();\n    }\n\n    /**\n     * @param timeout 超时时间 单位毫秒\n     * @return MqttFuture\n     * @throws InterruptedException 打断异常\n     * @throws TimeoutException 超时异常\n     */\n    public MqttFuture sync(long timeout) throws InterruptedException, TimeoutException {\n        return mqttFuture.sync(timeout);\n    }\n\n\n    /**\n     * 阻塞等待至完成（如果失败则会抛出异常），忽略打断异常\n     *\n     * @return MqttFuture\n     */\n    public MqttFuture syncUninterruptibly() {\n        return mqttFuture.syncUninterruptibly();\n    }\n\n    /**\n     * 阻塞等待至完成（如果失败则会抛出异常），忽略打断异常\n     *\n     * @param timeout 超时时间 单位毫秒\n     * @return MqttFuture\n     * @throws TimeoutException 超时异常\n     */\n    public MqttFuture syncUninterruptibly(long timeout) throws TimeoutException {\n        return mqttFuture.syncUninterruptibly(timeout);\n    }\n\n\n    /**\n     * 是否完成\n     *\n     * @return 是否被唤醒\n     */\n    public boolean isDone() {\n        return mqttFuture.isDone();\n    }\n\n    /**\n     * 是否执行成功\n     *\n     * @return 操作是否成功\n     */\n    public boolean isSuccess() {\n        return mqttFuture.isSuccess();\n    }\n\n    /**\n     * 获取失败异常\n     *\n     * @return 异常\n     */\n    public Throwable getCause() {\n        return mqttFuture.getCause();\n    }\n\n\n    /**\n     * 添加一个监听器\n     *\n     * @param listener 要添加的监听器\n     */\n    public void addListener(MqttFutureListener listener) {\n        this.mqttFuture.addListener(listener);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/proxy/CglibProxyFactory.java",
    "content": "package io.github.netty.mqtt.client.support.proxy;\n\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.plugin.CglibMethodInterceptor;\nimport io.github.netty.mqtt.client.plugin.CglibTargetHelper;\nimport io.github.netty.mqtt.client.plugin.Interceptor;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\nimport net.sf.cglib.proxy.Enhancer;\n\nimport java.util.List;\n\n/**\n * cglib代理工厂\n * @author: xzc-coder\n */\npublic class CglibProxyFactory implements ProxyFactory {\n    @Override\n    public Object getProxy(Object target, List<Interceptor> interceptors) {\n        Object obj = target;\n        if (target == null || EmptyUtils.isEmpty(interceptors)) {\n            return obj;\n        }else {\n            //创建一个Enhancer\n            Enhancer enhancer = new Enhancer();\n            //设置父类的class\n            enhancer.setSuperclass(CglibTargetHelper.createCglibTarget(target).getClass());\n            //设置回调时拦截器\n            enhancer.setCallback(new CglibMethodInterceptor(interceptors, target));\n            //创建代理对象\n            obj = enhancer.create();\n        }\n        return obj;\n    }\n\n    @Override\n    public String getProxyType() {\n        return MqttConstant.PROXY_TYPE_CGLIB;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/proxy/JdkProxyFactory.java",
    "content": "package io.github.netty.mqtt.client.support.proxy;\n\n\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.plugin.Interceptor;\nimport io.github.netty.mqtt.client.plugin.JdkMethodInterceptor;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\nimport io.github.netty.mqtt.client.support.util.ReflectionUtils;\n\nimport java.lang.reflect.Proxy;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * JDK动态代理工厂\n * @author: xzc-coder\n */\npublic class JdkProxyFactory implements ProxyFactory {\n\n    /**\n     * 对象接口缓存\n     */\n    private static final Map<Class, Class[]> interfacesMap = new ConcurrentHashMap<>();\n\n    @Override\n    public Object getProxy(Object target, List<Interceptor> interceptors) {\n        if (target == null || EmptyUtils.isEmpty(interceptors)) {\n            return target;\n        } else {\n            //创建代理对象\n            return Proxy.newProxyInstance(target.getClass().getClassLoader(), getInterfaces(target.getClass()), new JdkMethodInterceptor(interceptors, target));\n        }\n    }\n\n    @Override\n    public String getProxyType() {\n        return MqttConstant.PROXY_TYPE_JDK;\n    }\n\n    /**\n     * 获取类的所有接口\n     *\n     * @param clazz 类信息\n     * @return 所有接口\n     */\n    private Class<?>[] getInterfaces(Class<?> clazz) {\n        //从缓存获取\n        Class[] interfaces = interfacesMap.get(clazz);\n        if (interfaces == null) {\n            //反射工具类获取所有接口\n            interfaces = ReflectionUtils.getAllInterfaces(clazz);\n            //添加到缓存中\n            Class[] result = interfacesMap.putIfAbsent(clazz, interfaces);\n            if (result != null) {\n                interfaces = result;\n            }\n        }\n        return interfaces;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/proxy/ProxyFactory.java",
    "content": "package io.github.netty.mqtt.client.support.proxy;\n\n\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.plugin.Interceptor;\nimport io.github.netty.mqtt.client.support.util.AssertUtils;\n\nimport java.util.List;\n\n/**\n * 代理工厂接口\n * @author: xzc-coder\n */\npublic interface ProxyFactory {\n\n    /**\n     * 获取一个代理对象\n     *\n     * @param target       目标对象\n     * @param interceptors 拦截器集合\n     * @return 代理对象\n     */\n    Object getProxy(Object target, List<Interceptor> interceptors);\n\n    /**\n     * 获取代理类别\n     *\n     * @return 代理类别\n     */\n    String getProxyType();\n\n    /**\n     * 判断对象是否是代理对象\n     *\n     * @param object 对象\n     * @return 是否是代理对象\n     */\n    static boolean isProxyObject(Object object) {\n        AssertUtils.notNull(object, \"object is null\");\n        return object.getClass().getName().contains(MqttConstant.CGLIB_CONTAIN_CONTENT) || object.getClass().getName().contains(MqttConstant.JDK_PROXY_CONTAIN_CONTENT);\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/util/AssertUtils.java",
    "content": "package io.github.netty.mqtt.client.support.util;\n\nimport java.util.Collection;\nimport java.util.Map;\n\n/**\n * 断言工具类\n * @author: xzc-coder\n */\npublic class AssertUtils {\n\n    private AssertUtils() {\n    }\n\n\n    public static void notEmpty(CharSequence charSequence, String message) {\n        if (EmptyUtils.isEmpty(charSequence)) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n\n    public static void notBlank(CharSequence charSequence, String message) {\n        if (EmptyUtils.isBlank(charSequence)) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n\n    public static void notEmpty(Collection collection, String message) {\n        if (EmptyUtils.isEmpty(collection)) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n\n    public static void notEmpty(Map map, String message) {\n        if (EmptyUtils.isEmpty(map)) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n\n    public static void notEmpty(Object[] array, String message) {\n        if (EmptyUtils.isEmpty(array)) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n\n    public static void notNull(Object obj, String message) {\n        if (obj == null) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n\n    public static void notNull(Object obj, RuntimeException exeception) {\n        if (obj == null) {\n            throw exeception;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/util/CRC16Utils.java",
    "content": "package io.github.netty.mqtt.client.support.util;\n\n/**\n * CRC16校验工具类，查表法（使用CRC16_IBM）\n * @author: xzc-coder\n */\npublic class CRC16Utils {\n\n\n    private static final int[] table = {0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741,\n            0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40,\n            0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40,\n            0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341,\n            0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740,\n            0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41,\n            0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41,\n            0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340,\n            0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740,\n            0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41,\n            0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41,\n            0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340,\n            0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741,\n            0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40,\n            0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40,\n            0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341,\n            0x4100, 0x81C1, 0x8081, 0x4040};\n\n    private CRC16Utils() {\n    }\n\n    /**\n     * 获取CRC16的校验码\n     *\n     * @param bytes 二进制\n     * @return 2个字节的校验码\n     */\n    public static byte[] getCRC16Bytes(byte[] bytes) {\n        int crc = 0x0000;\n        for (byte b : bytes) {\n            crc = (crc >>> 8) ^ table[(crc ^ b) & 0xff];\n        }\n        return new byte[]{(byte) (0xff & crc), (byte) ((0xff00 & crc) >> 8)};\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/util/EmptyUtils.java",
    "content": "package io.github.netty.mqtt.client.support.util;\n\nimport java.util.Collection;\nimport java.util.Map;\n\n/**\n * 空判断工具类\n * @author: xzc-coder\n */\npublic class EmptyUtils {\n\n    private EmptyUtils() {\n    }\n\n    public static boolean isEmpty(CharSequence charSequence) {\n        return charSequence == null || charSequence.length() == 0;\n    }\n\n    public static boolean isNotEmpty(CharSequence charSequence) {\n        return !isEmpty(charSequence);\n    }\n\n    public static boolean isBlank(CharSequence charSequence) {\n        if (isEmpty(charSequence)) {\n            return true;\n        }\n        for (int i = 0; i < charSequence.length(); i++) {\n            if (!Character.isWhitespace(charSequence.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public static boolean isNotBlank(CharSequence charSequence) {\n        return !isBlank(charSequence);\n    }\n\n    public static boolean isEmpty(Map map) {\n        return (map == null || map.isEmpty());\n    }\n\n    public static boolean isNotEmpty(Map map) {\n        return !isEmpty(map);\n    }\n\n    public static boolean isEmpty(Collection collection) {\n        return (collection == null || collection.isEmpty());\n    }\n\n    public static boolean isNotEmpty(Collection collection) {\n        return !isEmpty(collection);\n    }\n\n    public static boolean isEmpty(Object[] array) {\n        return (array == null || array.length == 0);\n    }\n\n    public static boolean isNotEmpty(Object[] array) {\n        return !isEmpty(array);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/util/LogUtils.java",
    "content": "package io.github.netty.mqtt.client.support.util;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * 日志工具类\n * @author: xzc-coder\n */\npublic class LogUtils {\n\n    /**\n     * 是否有Log相关的框架\n     */\n    private volatile static boolean hasLoggingFramework;\n\n    /**\n     * Log框架类的集合，通过判断是否存在这几个类来判断是否存在Log框架\n     */\n    private static final List<String> LOGGING_FRAMEWORKS = Arrays.asList(\n            \"org.apache.logging.log4j.core.Logger\",\n            \"ch.qos.logback.classic.Logger\",\n            \"org.apache.log4j.Logger\"\n    );\n\n    static {\n        hasLoggingFramework = isLoggingFrameworkAvailable();\n    }\n\n    private static final String DATE_FORMAT = \"yyyy-MM-dd HH:mm:ss.SSS\";\n    private static final String DEBUG = \"DEBUG\";\n    private static final String INFO = \"INFO\";\n    private static final String WARN = \"WARN\";\n    private static final String ERROR = \"ERROR\";\n    private static final String SPACE = \" \";\n\n    /**\n     * 判断是否有Log框架\n     *\n     * @return 是否有Log框架\n     */\n    private static boolean isLoggingFrameworkAvailable() {\n        boolean result = false;\n        for (String className : LOGGING_FRAMEWORKS) {\n            boolean loggingFrameworkAvailable = isLoggingFrameworkAvailable(className);\n            if (loggingFrameworkAvailable) {\n                result = true;\n                break;\n            }\n        }\n        return result;\n    }\n\n    private static boolean isLoggingFrameworkAvailable(String className) {\n        try {\n            Class.forName(className);\n            return true;\n        } catch (ClassNotFoundException e) {\n            return false;\n        }\n    }\n\n    public static boolean isLoggingFramework() {\n        return hasLoggingFramework;\n    }\n\n    public static void debug(Class clazz, String message) {\n        if (hasLoggingFramework) {\n            Logger logger = LoggerFactory.getLogger(clazz);\n            logger.debug(message);\n        } else {\n            System.out.println(getPrintContent(clazz, message, DEBUG));\n        }\n    }\n\n\n    public static void info(Class clazz, String message) {\n        if (hasLoggingFramework) {\n            Logger logger = LoggerFactory.getLogger(clazz);\n            logger.info(message);\n        } else {\n            System.out.println(getPrintContent(clazz, message, INFO));\n        }\n    }\n\n\n    public static void warn(Class clazz, String message) {\n        if (hasLoggingFramework) {\n            Logger logger = LoggerFactory.getLogger(clazz);\n            logger.warn(message);\n        } else {\n            System.out.println(getPrintContent(clazz, message, WARN));\n        }\n    }\n\n    public static void error(Class clazz, String message) {\n        if (hasLoggingFramework) {\n            Logger logger = LoggerFactory.getLogger(clazz);\n            logger.error(message);\n        } else {\n            System.out.println(getPrintContent(clazz, message, ERROR));\n        }\n    }\n\n    public static void error(Class clazz, String message, Throwable throwable) {\n        if (hasLoggingFramework) {\n            Logger logger = LoggerFactory.getLogger(clazz);\n            logger.error(message, throwable);\n        } else {\n            System.out.println(getPrintContent(clazz, message, ERROR));\n            throwable.printStackTrace();\n        }\n    }\n\n\n    private static String getDate() {\n        DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);\n        return dateFormat.format(new Date());\n    }\n\n    private static String getThreadName() {\n        return \"[\" + Thread.currentThread().getName() + \"]\";\n    }\n\n    private static String getPrintContent(Class clazz, String message, String level) {\n        StringBuilder content = new StringBuilder();\n        content.append(getDate())\n                .append(SPACE)\n                .append(getThreadName())\n                .append(SPACE)\n                .append(level)\n                .append(SPACE)\n                .append(clazz.getName())\n                .append(SPACE)\n                .append(\"-\")\n                .append(SPACE)\n                .append(message);\n        return content.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/util/MqttUtils.java",
    "content": "package io.github.netty.mqtt.client.support.util;\n\nimport io.github.netty.mqtt.client.MqttConnectParameter;\nimport io.github.netty.mqtt.client.constant.MqttConstant;\nimport io.github.netty.mqtt.client.constant.MqttMsgDirection;\nimport io.github.netty.mqtt.client.constant.MqttMsgState;\nimport io.github.netty.mqtt.client.constant.MqttVersion;\nimport io.github.netty.mqtt.client.exception.MqttException;\nimport io.github.netty.mqtt.client.msg.MqttDisconnectMsg;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.github.netty.mqtt.client.msg.MqttMsgInfo;\nimport io.github.netty.mqtt.client.msg.MqttWillMsg;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\n\nimport java.io.UnsupportedEncodingException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\n\n/**\n * MQTT工具类\n * @author: xzc-coder\n */\npublic class MqttUtils {\n\n    /**\n     * Mqtt 发布消息中相关的字符属性\n     */\n    private static final Set<Integer> MQTT_PROPERTY_STRING_ID_SET = new HashSet<>();\n    /**\n     * Mqtt 发布消息中相关的数值属性\n     */\n    private static final Set<Integer> MQTT_PROPERTY_INTEGER_ID_SET = new HashSet<>();\n    /**\n     * Mqtt 发布消息中相关的二进制属性\n     */\n    private static final Set<Integer> MQTT_PROPERTY_BINARY_ID_SET = new HashSet<>();\n\n    static {\n        //字符串\n        MQTT_PROPERTY_STRING_ID_SET.add(MqttProperties.MqttPropertyType.CONTENT_TYPE.value());\n        MQTT_PROPERTY_STRING_ID_SET.add(MqttProperties.MqttPropertyType.RESPONSE_TOPIC.value());\n        MQTT_PROPERTY_STRING_ID_SET.add(MqttProperties.MqttPropertyType.REASON_STRING.value());\n        MQTT_PROPERTY_STRING_ID_SET.add(MqttProperties.MqttPropertyType.USER_PROPERTY.value());\n        //二进制\n        MQTT_PROPERTY_BINARY_ID_SET.add(MqttProperties.MqttPropertyType.CORRELATION_DATA.value());\n        //整型\n        MQTT_PROPERTY_INTEGER_ID_SET.add(MqttProperties.MqttPropertyType.PAYLOAD_FORMAT_INDICATOR.value());\n        MQTT_PROPERTY_INTEGER_ID_SET.add(MqttProperties.MqttPropertyType.TOPIC_ALIAS.value());\n        MQTT_PROPERTY_INTEGER_ID_SET.add(MqttProperties.MqttPropertyType.PUBLICATION_EXPIRY_INTERVAL.value());\n        MQTT_PROPERTY_INTEGER_ID_SET.add(MqttProperties.MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value());\n    }\n\n    private MqttUtils() {\n    }\n\n    /**\n     * 获取一个连接时用的 MqttProperties\n     *\n     * @param mqttConnectParameter MQTT连接参数\n     * @return MqttProperties\n     */\n    public static MqttProperties getConnectMqttProperties(MqttConnectParameter mqttConnectParameter) {\n        AssertUtils.notNull(mqttConnectParameter, \"mqttConnectParameter is null\");\n        MqttVersion mqttVersion = mqttConnectParameter.getMqttVersion();\n        MqttProperties mqttProperties;\n        if (mqttVersion == MqttVersion.MQTT_5_0_0) {\n            mqttProperties = new MqttProperties();\n            Integer sessionExpiryIntervalSeconds = mqttConnectParameter.getSessionExpiryIntervalSeconds();\n            String authenticationMethod = mqttConnectParameter.getAuthenticationMethod();\n            byte[] authenticationData = mqttConnectParameter.getAuthenticationData();\n            Integer requestProblemInformation = mqttConnectParameter.getRequestProblemInformation();\n            String responseInformation = mqttConnectParameter.getResponseInformation();\n            Integer receiveMaximum = mqttConnectParameter.getReceiveMaximum();\n            Integer topicAliasMaximum = mqttConnectParameter.getTopicAliasMaximum();\n            Integer maximumPacketSize = mqttConnectParameter.getMaximumPacketSize();\n            MqttProperties.UserProperties mqttUserProperties = mqttConnectParameter.getMqttUserProperties();\n            if (sessionExpiryIntervalSeconds != null && sessionExpiryIntervalSeconds >= 0) {\n                mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SESSION_EXPIRY_INTERVAL.value(), sessionExpiryIntervalSeconds));\n            }\n            if (authenticationMethod != null) {\n                mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_METHOD.value(), authenticationMethod));\n            }\n            if (authenticationData != null && authenticationData.length > 0) {\n                mqttProperties.add(new MqttProperties.BinaryProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_DATA.value(), authenticationData));\n            }\n            if (requestProblemInformation != null) {\n                mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.REQUEST_PROBLEM_INFORMATION.value(), requestProblemInformation));\n            }\n            if (responseInformation != null) {\n                mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.RESPONSE_INFORMATION.value(), responseInformation));\n            }\n            if (receiveMaximum != null && receiveMaximum > 0) {\n                mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.RECEIVE_MAXIMUM.value(), receiveMaximum));\n            }\n            if (topicAliasMaximum != null && topicAliasMaximum > 0) {\n                mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS_MAXIMUM.value(), topicAliasMaximum));\n            }\n            if (maximumPacketSize != null && maximumPacketSize > 0) {\n                mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.MAXIMUM_PACKET_SIZE.value(), maximumPacketSize));\n            }\n            addUserProperty(mqttProperties, mqttUserProperties);\n        } else {\n            mqttProperties = MqttProperties.NO_PROPERTIES;\n        }\n        return mqttProperties;\n    }\n\n    /**\n     * 获取断开连接的MqttProperties\n     *\n     * @param mqttDisconnectMsg MQTT断开消息\n     * @return MqttProperties\n     */\n    public static MqttProperties getDisconnectMqttProperties(MqttDisconnectMsg mqttDisconnectMsg) {\n        AssertUtils.notNull(mqttDisconnectMsg, \"mqttDisconnectMsg is null\");\n        MqttProperties mqttProperties = new MqttProperties();\n        String reasonString = mqttDisconnectMsg.getReasonString();\n        Integer sessionExpiryIntervalSeconds = mqttDisconnectMsg.getSessionExpiryIntervalSeconds();\n        MqttProperties.UserProperties mqttUserProperties = mqttDisconnectMsg.getMqttUserProperties();\n        if (EmptyUtils.isNotEmpty(reasonString)) {\n            mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.REASON_STRING.value(), reasonString));\n        }\n        if (sessionExpiryIntervalSeconds != null && sessionExpiryIntervalSeconds > 0) {\n            mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SESSION_EXPIRY_INTERVAL.value(), sessionExpiryIntervalSeconds));\n        }\n        addUserProperty(mqttProperties, mqttUserProperties);\n        return mqttProperties;\n\n    }\n\n    /**\n     * 获取遗嘱的MqttProperties\n     *\n     * @param mqttVersion MQTT版本\n     * @param mqttWillMsg 遗嘱消息\n     * @return MqttProperties\n     */\n    public static MqttProperties getWillMqttProperties(MqttVersion mqttVersion, MqttWillMsg mqttWillMsg) {\n        AssertUtils.notNull(mqttVersion, \"mqttVersion is null\");\n        AssertUtils.notNull(mqttWillMsg, \"mqttWillMsg is null\");\n        MqttProperties mqttProperties;\n        if (mqttVersion == MqttVersion.MQTT_5_0_0) {\n            mqttProperties = new MqttProperties();\n            Integer willDelayIntervalSeconds = mqttWillMsg.getWillDelayIntervalSeconds();\n            Integer payloadFormatIndicator = mqttWillMsg.getPayloadFormatIndicator();\n            Integer messageExpiryIntervalSeconds = mqttWillMsg.getMessageExpiryIntervalSeconds();\n            String contentType = mqttWillMsg.getContentType();\n            String responseTopic = mqttWillMsg.getResponseTopic();\n            byte[] correlationData = mqttWillMsg.getCorrelationData();\n            MqttProperties.UserProperties mqttUserProperties = mqttWillMsg.getMqttUserProperties();\n            if (willDelayIntervalSeconds != null && willDelayIntervalSeconds > 0) {\n                mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.WILL_DELAY_INTERVAL.value(), willDelayIntervalSeconds));\n            }\n            if (payloadFormatIndicator != null) {\n                mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.PAYLOAD_FORMAT_INDICATOR.value(), payloadFormatIndicator));\n            }\n            if (messageExpiryIntervalSeconds != null && messageExpiryIntervalSeconds > 0) {\n                mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.PUBLICATION_EXPIRY_INTERVAL.value(), messageExpiryIntervalSeconds));\n            }\n            if (EmptyUtils.isNotEmpty(contentType)) {\n                mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.CONTENT_TYPE.value(), contentType));\n            }\n\n            if (EmptyUtils.isNotEmpty(responseTopic)) {\n                mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.RESPONSE_TOPIC.value(), responseTopic));\n            }\n\n            if (correlationData != null && correlationData.length > 0) {\n                mqttProperties.add(new MqttProperties.BinaryProperty(MqttProperties.MqttPropertyType.CORRELATION_DATA.value(), correlationData));\n            }\n            addUserProperty(mqttProperties, mqttUserProperties);\n        } else {\n            mqttProperties = MqttProperties.NO_PROPERTIES;\n        }\n        return mqttProperties;\n    }\n\n    private static void addUserProperty(MqttProperties mqttProperties, MqttProperties.UserProperties mqttUserProperties) {\n        if (mqttUserProperties != null && !mqttUserProperties.value().isEmpty()) {\n            mqttProperties.add(mqttUserProperties);\n        }\n    }\n\n    /**\n     * 获取认证报文的MqttProperties\n     *\n     * @param authenticationMethod 认证方法\n     * @param authenticationData   认证数据\n     * @param reasonString         原因字符串\n     * @param mqttUserProperties   MQTT用户属性\n     * @return MqttProperties\n     */\n    public static MqttProperties getAuthMqttProperties(String authenticationMethod, byte[] authenticationData, String reasonString, MqttProperties.UserProperties mqttUserProperties) {\n        AssertUtils.notNull(authenticationMethod, \"authenticationMethod is null\");\n        MqttProperties mqttProperties = new MqttProperties();\n        mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_METHOD.value(), authenticationMethod));\n        if (authenticationData != null && authenticationData.length > 0) {\n            mqttProperties.add(new MqttProperties.BinaryProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_DATA.value(), authenticationData));\n        }\n        if (reasonString != null) {\n            mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.REASON_STRING.value(), reasonString));\n        }\n        addUserProperty(mqttProperties, mqttUserProperties);\n        return mqttProperties;\n    }\n\n    /**\n     * 从MqttProperties中获取原因字符串\n     *\n     * @param mqttProperties MqttProperties\n     * @return 原因字符串\n     */\n    public static String getReasonString(MqttProperties mqttProperties) {\n        String reasonString = null;\n        if (mqttProperties != null) {\n            MqttProperties.StringProperty reasonStringProperty = (MqttProperties.StringProperty) mqttProperties.getProperty(MqttProperties.MqttPropertyType.REASON_STRING.value());\n            if (reasonStringProperty != null) {\n                reasonString = reasonStringProperty.value();\n            }\n        }\n        return reasonString;\n    }\n\n    /**\n     * 获取活跃时间（秒）\n     *\n     * @param clientKeepAlive 客户端设置的活跃时间\n     * @param mqttProperties  broker返回的MqttProperties中的活跃时间\n     * @return 活跃时间\n     */\n    public static int getKeepAlive(int clientKeepAlive, MqttProperties mqttProperties) {\n        int keepAlive = clientKeepAlive;\n        if (mqttProperties != null) {\n            MqttProperties.IntegerProperty mqttProperty = (MqttProperties.IntegerProperty) mqttProperties.getProperty(MqttProperties.MqttPropertyType.SERVER_KEEP_ALIVE.value());\n            if (mqttProperty != null) {\n                Integer value = mqttProperty.value();\n                if (value != null) {\n                    keepAlive = value;\n                }\n            }\n        }\n        return keepAlive;\n    }\n\n    /**\n     * 获取MQTT中的某个用户属性\n     *\n     * @param mqttProperties MqttProperties\n     * @param key            key\n     * @return 用户属性集合\n     */\n    public static List<String> getUserMqttPropertyValues(MqttProperties mqttProperties, String key) {\n        List<String> userMqttPropertyValues = new ArrayList<>();\n        if (mqttProperties != null && EmptyUtils.isNotEmpty(key)) {\n            MqttProperties.UserProperties mqttUserProperties = (MqttProperties.UserProperties) mqttProperties.getProperty(MqttProperties.MqttPropertyType.USER_PROPERTY.value());\n            if (mqttUserProperties != null) {\n                List<MqttProperties.StringPair> stringPairList = mqttUserProperties.value();\n                if (EmptyUtils.isNotEmpty(stringPairList)) {\n                    for (MqttProperties.StringPair stringPair : stringPairList) {\n                        if (key.equals(stringPair.key)) {\n                            userMqttPropertyValues.add(stringPair.value);\n                        }\n                    }\n                }\n            }\n        }\n        return userMqttPropertyValues;\n    }\n\n    /**\n     * 获取MQTT中的某个用户属性值\n     *\n     * @param mqttProperties MqttProperties\n     * @param key            key\n     * @return 单个值\n     */\n    public static String getUserMqttPropertyValue(MqttProperties mqttProperties, String key) {\n        String value = null;\n        List<String> userMqttPropertyValues = getUserMqttPropertyValues(mqttProperties, key);\n        if (EmptyUtils.isNotEmpty(userMqttPropertyValues)) {\n            value = userMqttPropertyValues.get(0);\n        }\n        return value;\n    }\n\n    /**\n     * 获取MQTT中的某个用户属性值\n     *\n     * @param mqttProperties MqttProperties\n     * @param key            key\n     * @param defaultValue   获取不到时的默认值\n     * @return 单个值\n     */\n    public static String getUserMqttPropertyValue(MqttProperties mqttProperties, String key, String defaultValue) {\n        String value = null;\n        List<String> userMqttPropertyValues = getUserMqttPropertyValues(mqttProperties, key);\n        if (EmptyUtils.isNotEmpty(userMqttPropertyValues)) {\n            value = userMqttPropertyValues.get(0);\n            if (EmptyUtils.isEmpty(value)) {\n                value = defaultValue;\n            }\n        }\n        return value;\n    }\n\n    /**\n     * 获取整型的MqttProperty值\n     *\n     * @param mqttProperties   MqttProperties\n     * @param mqttPropertyType MQTT属性类型\n     * @return 整型值\n     */\n    public static Integer getIntegerMqttPropertyValue(MqttProperties mqttProperties, MqttProperties.MqttPropertyType mqttPropertyType) {\n        Integer value = null;\n        if (mqttProperties != null && mqttPropertyType != null) {\n            MqttProperties.MqttProperty mqttProperty = mqttProperties.getProperty(mqttPropertyType.value());\n            if (mqttProperty instanceof MqttProperties.IntegerProperty) {\n                value = ((MqttProperties.IntegerProperty) mqttProperty).value();\n            }\n        }\n        return value;\n    }\n\n    /**\n     * 获取整型的MqttProperty值\n     *\n     * @param mqttProperties   MqttProperties\n     * @param mqttPropertyType MQTT属性类型\n     * @param defaultValue     获取不到时的默认值\n     * @return 整型值\n     */\n    public static Integer getIntegerMqttPropertyValue(MqttProperties mqttProperties, MqttProperties.MqttPropertyType mqttPropertyType, int defaultValue) {\n        Integer value;\n        value = getIntegerMqttPropertyValue(mqttProperties, mqttPropertyType);\n        if (value == null) {\n            value = defaultValue;\n        }\n        return value;\n    }\n\n    /**\n     * 获取字符串的MqttProperty值\n     *\n     * @param mqttProperties   MqttProperties\n     * @param mqttPropertyType MQTT属性类型\n     * @return 字符串\n     */\n    public static String getStringMqttPropertyValue(MqttProperties mqttProperties, MqttProperties.MqttPropertyType mqttPropertyType) {\n        String value = null;\n        if (mqttProperties != null && mqttPropertyType != null) {\n            MqttProperties.MqttProperty mqttProperty = mqttProperties.getProperty(mqttPropertyType.value());\n            if (mqttProperty instanceof MqttProperties.StringProperty) {\n                value = ((MqttProperties.StringProperty) mqttProperty).value();\n            }\n        }\n        return value;\n    }\n\n\n    /**\n     * 获取字符串的MqttProperty值\n     *\n     * @param mqttProperties   MqttProperties\n     * @param mqttPropertyType MQTT属性类型\n     * @param defaultValue     获取不到时的默认值\n     * @return 字符串\n     */\n    public static String getStringMqttPropertyValue(MqttProperties mqttProperties, MqttProperties.MqttPropertyType mqttPropertyType, String defaultValue) {\n        String value;\n        value = getStringMqttPropertyValue(mqttProperties, mqttPropertyType);\n        if (EmptyUtils.isEmpty(value)) {\n            value = defaultValue;\n        }\n        return value;\n    }\n\n    /**\n     * 获取二进制的MqttProperty值\n     *\n     * @param mqttProperties   MqttProperties\n     * @param mqttPropertyType MQTT属性类型\n     * @return 二进制\n     */\n    public static byte[] getBinaryMqttPropertyValue(MqttProperties mqttProperties, MqttProperties.MqttPropertyType mqttPropertyType) {\n        byte[] value = null;\n        if (mqttProperties != null && mqttPropertyType != null) {\n            MqttProperties.MqttProperty mqttProperty = mqttProperties.getProperty(mqttPropertyType.value());\n            if (mqttProperty instanceof MqttProperties.BinaryProperty) {\n                value = ((MqttProperties.BinaryProperty) mqttProperty).value();\n            }\n        }\n        return value;\n    }\n\n\n    /**\n     * 获取二进制的MqttProperty值\n     *\n     * @param mqttProperties   MqttProperties\n     * @param mqttPropertyType MQTT属性类型\n     * @param defaultValue     获取不到时的默认值\n     * @return 二进制\n     */\n    public static byte[] getBinaryMqttPropertyValue(MqttProperties mqttProperties, MqttProperties.MqttPropertyType mqttPropertyType, byte[] defaultValue) {\n        byte[] value;\n        value = getBinaryMqttPropertyValue(mqttProperties, mqttPropertyType);\n        if (value == null || value.length == 0) {\n            value = defaultValue;\n        }\n        return value;\n    }\n\n    /**\n     * 获取活跃时间（秒）\n     *\n     * @param channel                    Channel\n     * @param clientKeepAliveTimeSeconds 客户端的活跃时间\n     * @return 活跃时间（\n     */\n    public static int getKeepAliveTimeSeconds(Channel channel, int clientKeepAliveTimeSeconds) {\n        Integer keepAliveTimeSeconds = null;\n        if (channel != null) {\n            keepAliveTimeSeconds = channel.attr(MqttConstant.KEEP_ALIVE_TIME_ATTRIBUTE_KEY).get();\n        }\n        if (keepAliveTimeSeconds == null) {\n            keepAliveTimeSeconds = clientKeepAliveTimeSeconds;\n        }\n        return keepAliveTimeSeconds;\n    }\n\n    /**\n     * 获取发布消息的MqttProperties\n     *\n     * @param mqttMsgInfo MQTT消息信息\n     * @return MqttProperties\n     */\n    public static MqttProperties getPublishMqttProperties(MqttMsgInfo mqttMsgInfo) {\n        AssertUtils.notNull(mqttMsgInfo, \"mqttMsgInfo is null\");\n        MqttProperties mqttProperties = new MqttProperties();\n        Integer payloadFormatIndicator = mqttMsgInfo.getPayloadFormatIndicator();\n        Integer messageExpiryIntervalSeconds = mqttMsgInfo.getMessageExpiryIntervalSeconds();\n        Integer topicAlias = mqttMsgInfo.getTopicAlias();\n        String responseTopic = mqttMsgInfo.getResponseTopic();\n        byte[] correlationData = mqttMsgInfo.getCorrelationData();\n        Integer subscriptionIdentifier = mqttMsgInfo.getSubscriptionIdentifier();\n        String contentType = mqttMsgInfo.getContentType();\n        MqttProperties.UserProperties mqttUserProperties = mqttMsgInfo.getMqttUserProperties();\n        if (payloadFormatIndicator != null) {\n            mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.PAYLOAD_FORMAT_INDICATOR.value(), payloadFormatIndicator));\n        }\n        if (messageExpiryIntervalSeconds != null) {\n            mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.PUBLICATION_EXPIRY_INTERVAL.value(), messageExpiryIntervalSeconds));\n        }\n        if (topicAlias != null) {\n            mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS.value(), topicAlias));\n        }\n        if (responseTopic != null) {\n            mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.RESPONSE_TOPIC.value(), responseTopic));\n        }\n        if (correlationData != null) {\n            mqttProperties.add(new MqttProperties.BinaryProperty(MqttProperties.MqttPropertyType.CORRELATION_DATA.value(), correlationData));\n        }\n        if (subscriptionIdentifier != null) {\n            mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value(), subscriptionIdentifier));\n        }\n        if (contentType != null) {\n            mqttProperties.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.CONTENT_TYPE.value(), contentType));\n        }\n        addUserProperty(mqttProperties, mqttUserProperties);\n        return mqttProperties;\n    }\n\n    /**\n     * 序列化MqttMsg，\n     * 按顺序为：2字节MsgId-2字节主题长度-主题内容-1字节的qos-1字节是否保留消息-1字节是否重复消息-1字节消息状态-4字节的载荷长度-载荷-1字节消息方向-1字节原因码-4字节MqttProperties长度-MqttProperties\n     *\n     * @param mqttMsg MQTT消息\n     * @return 二进制\n     */\n    public static byte[] serializableMsg(MqttMsg mqttMsg) {\n        AssertUtils.notNull(mqttMsg, \"mqttMsg is null\");\n        ByteBuf byteBuf = Unpooled.buffer();\n        int msgId = mqttMsg.getMsgId();\n        String topic = mqttMsg.getTopic();\n        byte[] topicBytes = topic.getBytes(StandardCharsets.UTF_8);\n        int topicLength = topicBytes.length;\n        int qos = mqttMsg.getQos().value();\n        boolean retain = mqttMsg.isRetain();\n        boolean dup = mqttMsg.isDup();\n        int msgState = mqttMsg.getMsgState().getState();\n        byte[] payload = mqttMsg.getPayload();\n        int payloadLength = payload.length;\n        int mqttMsgDirection = mqttMsg.getMqttMsgDirection().getDirection();\n        byte reasonCode = mqttMsg.getReasonCode();\n        MqttProperties mqttProperties = mqttMsg.getMqttProperties();\n        boolean existMqttProperties = mqttProperties != null && !mqttProperties.listAll().isEmpty();\n        byte[] mqttPropertiesBytes = new byte[0];\n        if (existMqttProperties) {\n            //解析MqttProperties\n            mqttPropertiesBytes = mqttPropertiesToBytes(mqttProperties);\n        }\n        long createTimestamp = mqttMsg.getCreateTimestamp();\n        byteBuf.writeShort(msgId);\n        byteBuf.writeShort(topicLength);\n        byteBuf.writeBytes(topicBytes);\n        byteBuf.writeByte(qos);\n        byteBuf.writeBoolean(retain);\n        byteBuf.writeBoolean(dup);\n        byteBuf.writeByte(msgState);\n        byteBuf.writeInt(payloadLength);\n        byteBuf.writeBytes(payload);\n        byteBuf.writeByte(mqttMsgDirection);\n        byteBuf.writeByte(reasonCode);\n        byteBuf.writeInt(mqttPropertiesBytes.length);\n        byteBuf.writeBytes(mqttPropertiesBytes);\n        byteBuf.writeLong(createTimestamp);\n        //2个字节的crc校验码\n        byte[] data = ByteBufUtil.getBytes(byteBuf);\n        byte[] crc16Bytes = CRC16Utils.getCRC16Bytes(data);\n        byteBuf.writeBytes(crc16Bytes);\n        return ByteBufUtil.getBytes(byteBuf);\n    }\n\n    /**\n     * 反序列化二进制为MQTT消息\n     *\n     * @param bytes 二进制\n     * @return MQTT消息\n     */\n    public static MqttMsg deserializableMsg(byte[] bytes) {\n        AssertUtils.notNull(bytes, \"bytes is null\");\n        ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);\n        //先进行CRC校验，直接读的话浪费性能和时间\n        crcCheck(byteBuf);\n        //读取数据\n        int mgsId = byteBuf.readUnsignedShort();\n        int topicLength = byteBuf.readUnsignedShort();\n        byte[] topicBytes = new byte[topicLength];\n        byteBuf.readBytes(topicBytes);\n        String topic = new String(topicBytes, StandardCharsets.UTF_8);\n        byte qosByte = byteBuf.readByte();\n        MqttQoS qos = MqttQoS.valueOf(qosByte);\n        boolean retain = byteBuf.readBoolean();\n        boolean dup = byteBuf.readBoolean();\n        MqttMsgState msgState = MqttMsgState.findMqttMsgState(byteBuf.readByte());\n        int payloadLength = byteBuf.readInt();\n        byte[] payload = new byte[payloadLength];\n        byteBuf.readBytes(payload);\n        MqttMsgDirection mqttMsgDirection = MqttMsgDirection.findMqttMsgDirection(byteBuf.readByte());\n        byte reasonCode = byteBuf.readByte();\n        int mqttPropertiesLength = byteBuf.readInt();\n        byte[] mqttPropertiesBytes = new byte[mqttPropertiesLength];\n        byteBuf.readBytes(mqttPropertiesBytes);\n        MqttProperties mqttProperties = bytesToMqttProperties(mqttPropertiesBytes);\n        long createTimestamp = byteBuf.readLong();\n        MqttMsg mqttMsg = new MqttMsg(mgsId, payload, topic, qos, retain, dup, msgState, mqttMsgDirection);\n        mqttMsg.setReasonCode(reasonCode);\n        mqttMsg.setMqttProperties(mqttProperties);\n        mqttMsg.setCreateTimestamp(createTimestamp);\n        return mqttMsg;\n    }\n\n    /**\n     * 序列化MQTT消息为Base64字符串\n     *\n     * @param mqttMsg MQTT消息\n     * @return Base64字符串\n     */\n    public static String serializableMsgBase64(MqttMsg mqttMsg) {\n        byte[] bytes = serializableMsg(mqttMsg);\n        return Base64.getEncoder().encodeToString(bytes);\n    }\n\n\n    /**\n     * 反序列化Base64字符串为MQTT消息\n     *\n     * @param base64 base64字符串\n     * @return MQTT消息\n     */\n    public static MqttMsg deserializableMsgBase64(String base64) {\n        return deserializableMsg(Base64.getDecoder().decode(base64));\n    }\n\n\n    /**\n     * MqttProperties转为二进制\n     *\n     * @param mqttProperties MqttProperties\n     * @return 二进制\n     */\n    private static byte[] mqttPropertiesToBytes(MqttProperties mqttProperties) {\n        ByteBuf byteBuf = Unpooled.buffer();\n        if (mqttProperties != null && !mqttProperties.listAll().isEmpty()) {\n            Collection<? extends MqttProperties.MqttProperty> mqttPropertyList = mqttProperties.listAll();\n            //遍历所有MqttProperty\n            for (MqttProperties.MqttProperty mqttProperty : mqttPropertyList) {\n                int propertyId = mqttProperty.propertyId();\n                //整型MqttProperty\n                if (MQTT_PROPERTY_INTEGER_ID_SET.contains(propertyId)) {\n                    MqttProperties.IntegerProperty mqttIntegerProperty = (MqttProperties.IntegerProperty) mqttProperty;\n                    byteBuf.writeByte(propertyId);\n                    byteBuf.writeInt(mqttIntegerProperty.value());\n                } else if (MQTT_PROPERTY_STRING_ID_SET.contains(propertyId)) {\n                    //字符型MqttProperty\n                    byteBuf.writeByte(propertyId);\n                    if (propertyId == MqttProperties.MqttPropertyType.USER_PROPERTY.value()) {\n                        //如果是用户属性，则需要单独处理\n                        ByteBuf userPropertyByteBuf = ByteBufAllocator.DEFAULT.heapBuffer();\n                        MqttProperties.UserProperties userProperties = (MqttProperties.UserProperties) mqttProperty;\n                        List<MqttProperties.StringPair> stringPairList = userProperties.value();\n                        if (!stringPairList.isEmpty()) {\n                            for (MqttProperties.StringPair stringPair : stringPairList) {\n                                byte[] keyBytes = stringPair.key.getBytes(StandardCharsets.UTF_8);\n                                byte[] valueBytes = stringPair.value.getBytes(StandardCharsets.UTF_8);\n                                //写 key 长度\n                                userPropertyByteBuf.writeInt(keyBytes.length);\n                                //写 key 值\n                                userPropertyByteBuf.writeBytes(keyBytes);\n                                //写 value长度\n                                userPropertyByteBuf.writeInt(valueBytes.length);\n                                //写 value 值\n                                userPropertyByteBuf.writeBytes(valueBytes);\n                            }\n                        }\n                        int userPropertiesBytesLength = userPropertyByteBuf.readableBytes();\n                        //写用户属性总长度\n                        byteBuf.writeInt(userPropertiesBytesLength);\n                        byteBuf.writeBytes(userPropertyByteBuf);\n                    } else {\n                        //不是用户属性直接处理\n                        MqttProperties.StringProperty mqttStringProperty = (MqttProperties.StringProperty) mqttProperty;\n                        byte[] data = mqttStringProperty.value().getBytes(StandardCharsets.UTF_8);\n                        //多写4个字节的字符串长度\n                        byteBuf.writeInt(data.length);\n                        byteBuf.writeBytes(data);\n                    }\n                } else if (MQTT_PROPERTY_BINARY_ID_SET.contains(propertyId)) {\n                    //二进制类型MqttProperty\n                    MqttProperties.BinaryProperty mqttBinaryProperty = (MqttProperties.BinaryProperty) mqttProperty;\n                    byte[] data = mqttBinaryProperty.value();\n                    byteBuf.writeByte(propertyId);\n                    //多写4字节的数据长度\n                    byteBuf.writeInt(data.length);\n                    byteBuf.writeBytes(data);\n                }\n            }\n        }\n        return ByteBufUtil.getBytes(byteBuf);\n    }\n\n    /**\n     * 二进制转为MqttProperties\n     *\n     * @param bytes 二进制\n     * @return MqttProperties\n     */\n    private static MqttProperties bytesToMqttProperties(byte[] bytes) {\n        MqttProperties mqttProperties = null;\n        if (bytes != null && bytes.length > 0) {\n            mqttProperties = new MqttProperties();\n            //包装byte数组，不需要读写，直接使用byte数组的引用\n            ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);\n            while (byteBuf.isReadable()) {\n                int propertyId = byteBuf.readByte();\n                //整型MqttProperty\n                if (MQTT_PROPERTY_INTEGER_ID_SET.contains(propertyId)) {\n                    //整形直接读4个字节\n                    int value = byteBuf.readInt();\n                    mqttProperties.add(new MqttProperties.IntegerProperty(propertyId, value));\n                } else if (MQTT_PROPERTY_STRING_ID_SET.contains(propertyId)) {\n                    //字符型MqttProperty\n                    if (propertyId == MqttProperties.MqttPropertyType.USER_PROPERTY.value()) {\n                        //用户属性的话单独处理\n                        int userPropertiesBytesLength = byteBuf.readInt();\n                        MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n                        while (userPropertiesBytesLength > 0) {\n                            //用户属性的key长度\n                            int keyLength = byteBuf.readInt();\n                            byte[] keyBytes = new byte[keyLength];\n                            byteBuf.readBytes(keyBytes);\n                            String key = new String(keyBytes, StandardCharsets.UTF_8);\n                            //用户属性的value长度\n                            int valueLength = byteBuf.readInt();\n                            byte[] valueBytes = new byte[valueLength];\n                            byteBuf.readBytes(valueBytes);\n                            String value = new String(valueBytes, StandardCharsets.UTF_8);\n                            mqttUserProperties.add(key, value);\n                            userPropertiesBytesLength = userPropertiesBytesLength - 8 - keyLength - valueLength;\n                        }\n                        mqttProperties.add(mqttUserProperties);\n                    } else {\n                        //其他的则直接处理\n                        int length = byteBuf.readInt();\n                        byte[] data = new byte[length];\n                        byteBuf.readBytes(data);\n                        mqttProperties.add(new MqttProperties.StringProperty(propertyId, new String(data, StandardCharsets.UTF_8)));\n                    }\n                } else if (MQTT_PROPERTY_BINARY_ID_SET.contains(propertyId)) {\n                    //二进制类型MqttProperty\n                    int length = byteBuf.readInt();\n                    byte[] data = new byte[length];\n                    byteBuf.readBytes(data);\n                    mqttProperties.add(new MqttProperties.BinaryProperty(propertyId, data));\n                } else {\n                    throw new MqttException(\"MqttMsg.MqttProperties parse exception,the propertyId \" + propertyId + \" does not exist\");\n                }\n            }\n        }\n        return mqttProperties;\n    }\n\n    /**\n     * 主题校验（参考 org.eclipse.paho.mqttv5.common.util.MqttTopicValidator#validate）\n     *\n     * @param topic 主题\n     * @param wildcardAllowed 是否允许包含通配符\n     */\n    public static void topicCheck(String topic,boolean wildcardAllowed) {\n        AssertUtils.notNull(topic,\"topic is null\");\n        int topicLen = 0;\n        try {\n            topicLen = topic.getBytes(MqttConstant.MQTT_DEFAULT_CHARACTER).length;\n        } catch (UnsupportedEncodingException e) {\n            throw new MqttException(e);\n        }\n        //校验主题长度\n        if (topicLen < MqttConstant.MQTT_MIN_TOPIC_LEN || topicLen > MqttConstant.MQTT_MAX_TOPIC_LEN) {\n            throw new MqttException(String.format(\"invalid topic length, should be in range[%d, %d]!\",\n                    MqttConstant.MQTT_MIN_TOPIC_LEN, MqttConstant.MQTT_MAX_TOPIC_LEN));\n        }\n\n        //如果允许包含通配符\n        if (wildcardAllowed) {\n            //如果主题只存在 # 或者 + 通配符时，则不需要继续校验\n            if (equalsAny(topic, new String[] { MqttConstant.MQTT_MULTI_LEVEL_WILDCARD, MqttConstant.MQTT_SINGLE_LEVEL_WILDCARD })) {\n                return;\n            }\n            //多级通配符校验：不允许包含多个#;不允许包含有#的同时不以/# 结尾\n            if (countMatches(topic, MqttConstant.MQTT_MULTI_LEVEL_WILDCARD) > 1\n                    || (topic.contains(MqttConstant.MQTT_MULTI_LEVEL_WILDCARD)\n                    && !topic.endsWith(MqttConstant.MQTT_TOPIC_LEVEL_SEPARATOR + MqttConstant.MQTT_MULTI_LEVEL_WILDCARD))) {\n                throw new MqttException(\n                        \"invalid usage of multi-level wildcard in topic string: \" + topic);\n            }\n\n            //单级通配符校验\n            validateSingleLevelWildcard(topic);\n        }else {\n            //不允许包含通配符\n            if (containsAny(topic, (MqttConstant.MQTT_MULTI_LEVEL_WILDCARD + MqttConstant.MQTT_SINGLE_LEVEL_WILDCARD).toCharArray())) {\n                throw new MqttException(\"the topic name MUST NOT contain any wildcard characters (#+)\");\n            }\n        }\n    }\n\n\n    /**\n     * 校验单级通配符主题\n     *\n     * @param topic 主题\n     */\n    private static void validateSingleLevelWildcard(String topic) {\n        char singleLevelWildcardChar = MqttConstant.MQTT_SINGLE_LEVEL_WILDCARD.charAt(0);\n        char topicLevelSeparatorChar = MqttConstant.MQTT_TOPIC_LEVEL_SEPARATOR.charAt(0);\n\n        char[] chars = topic.toCharArray();\n        int length = chars.length;\n        char prev = MqttConstant.NUL, next = MqttConstant.NUL;\n        for (int i = 0; i < length; i++) {\n            prev = (i - 1 >= 0) ? chars[i - 1] : MqttConstant.NUL;\n            next = (i + 1 < length) ? chars[i + 1] : MqttConstant.NUL;\n            if (chars[i] == singleLevelWildcardChar) {\n                //单级通配符的上一个和下一个字符只能是 /或者空字符\n                if (prev != topicLevelSeparatorChar && prev != MqttConstant.NUL || next != topicLevelSeparatorChar && next != MqttConstant.NUL) {\n                    throw new MqttException(\n                            String.format(\"invalid usage of single-level wildcard in topic string '%s'!\",\n                                    topic));\n\n                }\n            }\n        }\n    }\n\n    /**\n     * 判断字符是否等于字符数组中的任意一个字符\n     *\n     * @param cs 字符\n     * @param strs 匹配的字符数组\n     * @return true：等于 false：不等于\n     */\n    private static boolean equalsAny(CharSequence cs, CharSequence[] strs) {\n        boolean eq = false;\n        if (cs == null) {\n            eq = (strs == null);\n        }\n\n        if (strs != null) {\n            for (CharSequence str : strs) {\n                eq = eq || str.equals(cs);\n            }\n        }\n\n        return eq;\n    }\n\n    /**\n     * 判断字符是否包含字符数组中的任意一个字符\n     *\n     * @param cs 字符\n     * @param searchChars 包含的字符数组\n     * @return true：包含 false：不包含\n     */\n    private static boolean containsAny(CharSequence cs, char[] searchChars) {\n        int csLength = cs.length();\n        int searchLength = searchChars.length;\n        int csLast = csLength - 1;\n        int searchLast = searchLength - 1;\n        for (int i = 0; i < csLength; i++) {\n            char ch = cs.charAt(i);\n            for (int j = 0; j < searchLength; j++) {\n                if (searchChars[j] == ch) {\n                    if (Character.isHighSurrogate(ch)) {\n                        if (j == searchLast) {\n                            return true;\n                        }\n                        if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) {\n                            return true;\n                        }\n                    }\n                    else {\n                        return true;\n                    }\n                }\n            }\n        }\n        return false;\n    }\n\n    /**\n     * 获取子字符串在字符串中出现的次数\n     *\n     * @param str 字符串\n     * @param sub 子字符串\n     * @return 出现的次数\n     */\n    private static int countMatches(CharSequence str, CharSequence sub) {\n        if (EmptyUtils.isEmpty(str) || EmptyUtils.isEmpty(sub)) {\n            return 0;\n        }\n        int count = 0;\n        int idx = 0;\n        while ((idx = indexOf(str, sub, idx)) != -1) {\n            count++;\n            idx += sub.length();\n        }\n        return count;\n    }\n\n\n    private static int indexOf(CharSequence cs, CharSequence searchChar, int start) {\n        return cs.toString().indexOf(searchChar.toString(), start);\n    }\n\n    /**\n     * crc校验\n     *\n     * @param byteBuf ByteBuf\n     */\n    private static void crcCheck(ByteBuf byteBuf) {\n        //标记一下当前位置\n        byteBuf.markReaderIndex();\n        int crcStartIndex = byteBuf.readableBytes() - 2;\n        //读取数据计算crc\n        byte[] data = new byte[crcStartIndex];\n        byteBuf.readBytes(data);\n        byte[] crc16 = CRC16Utils.getCRC16Bytes(data);\n        byte[] crc16BytesCheck = new byte[2];\n        //读取crc\n        byteBuf.readBytes(crc16BytesCheck);\n        //判断是否一致\n        boolean pass = Arrays.equals(crc16, crc16BytesCheck);\n        //重置\n        byteBuf.resetReaderIndex();\n        if (!pass) {\n            throw new MqttException(\"bytes parse exception,crc check failed,crc value:\" + Arrays.toString(crc16) + \",expect value:\" + Arrays.toString(crc16BytesCheck));\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/github/netty/mqtt/client/support/util/ReflectionUtils.java",
    "content": "package io.github.netty.mqtt.client.support.util;\n\nimport java.lang.reflect.Constructor;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * 反射工具类\n * @author: xzc-coder\n */\npublic class ReflectionUtils {\n\n    private ReflectionUtils() {\n    }\n\n\n    /**\n     * 获取类的所有接口\n     *\n     * @param clazz 类信息\n     * @return 所有接口\n     */\n    public static Class[] getAllInterfaces(Class clazz) {\n        List<Class> interfaceList = new ArrayList<>();\n        if (clazz != null) {\n            interfaceList.addAll(Arrays.asList(clazz.getInterfaces()));\n            Class superclass = clazz.getSuperclass();\n            //递归调用\n            Class[] superInterfaces = getAllInterfaces(superclass);\n            interfaceList.addAll(Arrays.asList(superInterfaces));\n        }\n        return interfaceList.toArray(new Class[interfaceList.size()]);\n    }\n\n    /**\n     * 创建一个实例\n     *\n     * @param className 全类名\n     * @param args 构建参数\n     * @return 实例对象\n     */\n    public static Object createInstance(String className, Object... args) {\n        try {\n            Class<?> clazz = Class.forName(className);\n            Class<?>[] parameterClass = getParameterClass(args);\n            Constructor constructor = clazz.getConstructor(parameterClass);\n            return constructor.newInstance(args);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 获取参数的Class数组\n     * @param args 参数\n     * @return Class数组\n     */\n    public static Class<?>[] getParameterClass(Object... args) {\n        if (args == null) {\n            return null;\n        }\n        Class<?>[] parameterClass = new Class[args.length];\n        for (int i = 0; i < args.length; i++) {\n            parameterClass[i] = args[i].getClass();\n        }\n        return parameterClass;\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/github/netty/mqtt/client/ConnectTest.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.callback.MqttCallback;\nimport io.github.netty.mqtt.client.callback.MqttConnectCallbackResult;\nimport io.github.netty.mqtt.client.constant.MqttVersion;\nimport io.github.netty.mqtt.client.msg.MqttDisconnectMsg;\nimport io.github.netty.mqtt.client.support.future.MqttFutureWrapper;\nimport io.github.netty.mqtt.client.util.PropertiesUtils;\nimport org.junit.AfterClass;\nimport org.junit.Assert;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\nimport java.io.IOException;\nimport java.math.BigDecimal;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * 连接相关的测试用例\n * @author: xzc-coder\n */\n@RunWith(JUnit4.class)\npublic class ConnectTest {\n\n    private static MqttClientFactory mqttClientFactory = new DefaultMqttClientFactory();\n\n    @BeforeClass\n    public static void beforeClass() throws IOException {\n        PropertiesUtils.loadTestProperties();\n    }\n\n    @AfterClass\n    public static void afterClass() {\n        mqttClientFactory.close();\n    }\n\n\n    @Test\n    public void testMqttConnect() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        try {\n            mqttClient.connect();\n        } catch (Exception e) {\n            Assert.fail(\"clientId connect failed,cause : \" + e.getMessage());\n        }\n        Assert.assertTrue(mqttClient.isOnline());\n    }\n\n\n    @Test\n    public void testMqtt5Connect() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        //mqtt5的参数\n        mqttConnectParameter.setReceiveMaximum(10240);\n        mqttConnectParameter.setTopicAliasMaximum(2000);\n        mqttConnectParameter.addMqttUserProperty(\"name\", \"xzc-coder\");\n        mqttConnectParameter.addMqttUserProperty(\"sex\", \"male\");\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        try {\n            mqttClient.connect();\n        } catch (Exception e) {\n            Assert.fail(\"clientId connect failed,cause : \" + e.getMessage());\n        }\n        Assert.assertTrue(mqttClient.isOnline());\n    }\n\n    @Test\n    public void testMqttConnectListener() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        MqttFutureWrapper mqttFutureWrapper = mqttClient.connectFuture();\n        mqttFutureWrapper.addListener(mqttFuture -> {\n            Assert.assertTrue(mqttFuture.isSuccess());\n            Assert.assertTrue(mqttClient.isOnline());\n        });\n        mqttFutureWrapper.syncUninterruptibly();\n    }\n\n    @Test\n    public void testMqtt5ConnectListener() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        //mqtt5的参数\n        mqttConnectParameter.setReceiveMaximum(10240);\n        mqttConnectParameter.setTopicAliasMaximum(2000);\n        mqttConnectParameter.addMqttUserProperty(\"name\", \"xzc-coder\");\n        mqttConnectParameter.addMqttUserProperty(\"sex\", \"male\");\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        MqttFutureWrapper mqttFutureWrapper = mqttClient.connectFuture();\n        mqttFutureWrapper.addListener(mqttFuture -> {\n            Assert.assertTrue(mqttFuture.isSuccess());\n            Assert.assertTrue(mqttClient.isOnline());\n        });\n        mqttFutureWrapper.syncUninterruptibly();\n    }\n\n    @Test\n    public void testMqttConnectCallback() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        //添加回调\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void connectCompleteCallback(MqttConnectCallbackResult mqttConnectCallbackResult) {\n                Assert.assertTrue(mqttClient.isOnline());\n            }\n        });\n        mqttClient.connect();\n    }\n\n\n    @Test\n    public void testMqtt5ConnectCallback() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        //mqtt5的参数\n        mqttConnectParameter.setReceiveMaximum(10240);\n        mqttConnectParameter.setTopicAliasMaximum(2000);\n        mqttConnectParameter.addMqttUserProperty(\"name\", \"xzc-coder\");\n        mqttConnectParameter.addMqttUserProperty(\"sex\", \"male\");\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        //添加回调\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void connectCompleteCallback(MqttConnectCallbackResult mqttConnectCallbackResult) {\n                Assert.assertTrue(mqttClient.isOnline());\n            }\n        });\n        mqttClient.connect();\n    }\n\n    @Test\n    public void testMqttDisconnect() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        mqttClient.connect();\n        Assert.assertTrue(mqttClient.isOnline());\n        mqttClient.disconnect();\n        Assert.assertFalse(mqttClient.isActive());\n    }\n\n\n    @Test\n    public void testMqtt5Disconnect() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        mqttClient.connect();\n        Assert.assertTrue(mqttClient.isOnline());\n        //使用MQTT5参数断开连接\n        MqttDisconnectMsg mqttDisconnectMsg = new MqttDisconnectMsg();\n        mqttDisconnectMsg.setReasonCode((byte) 0);\n        mqttDisconnectMsg.setSessionExpiryIntervalSeconds(1);\n        mqttClient.disconnect(mqttDisconnectMsg);\n        Assert.assertFalse(mqttClient.isActive());\n    }\n\n    @Test\n    public void testReconnect() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        //设置自动重连\n        mqttConnectParameter.setAutoReconnect(true);\n        int keepAliveSeconds = 1;\n        //心跳是活跃时间的3倍，则会超时被断开\n        BigDecimal keepAliveTimeCoefficient = new BigDecimal(\"3\");\n        mqttConnectParameter.setKeepAliveTimeSeconds(keepAliveSeconds);\n        mqttConnectParameter.setKeepAliveTimeCoefficient(keepAliveTimeCoefficient);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        AtomicInteger connectCount = new AtomicInteger(0);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        int reconnectTimeoutSeconds = keepAliveSeconds * 5;\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void connectCompleteCallback(MqttConnectCallbackResult mqttConnectCallbackResult) {\n                //连接一次则加1\n                int count = connectCount.incrementAndGet();\n                if(count > 1) {\n                    //有过重连则唤醒\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        countDownLatch.await(reconnectTimeoutSeconds, TimeUnit.SECONDS);\n        Assert.assertTrue(connectCount.get() > 1);\n    }\n\n    @Test\n    public void testMqttClose() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        Assert.assertFalse(mqttClient.isClose());\n        mqttClient.close();\n        Assert.assertTrue(mqttClient.isClose());\n    }\n\n    @Test\n    public void testMqtt5Close() {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        Assert.assertFalse(mqttClient.isClose());\n        mqttClient.close();\n        Assert.assertTrue(mqttClient.isClose());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/github/netty/mqtt/client/FutureTest.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.support.future.DefaultMqttFuture;\nimport io.github.netty.mqtt.client.support.future.MqttFuture;\nimport io.github.netty.mqtt.client.support.future.MqttFutureKey;\nimport io.github.netty.mqtt.client.util.PropertiesUtils;\nimport org.junit.Assert;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\nimport java.io.IOException;\n\n/**\n * Future相关的测试用例\n * @author: xzc-coder\n */\n@RunWith(JUnit4.class)\npublic class FutureTest {\n\n    @BeforeClass\n    public static void beforeClass() throws IOException {\n        PropertiesUtils.loadTestProperties();\n    }\n\n    @Test\n    public void testFutureSuccess() {\n        Object key = new Object();\n        MqttFutureKey futureKey = new MqttFutureKey(PropertiesUtils.getClientId(), key);\n        MqttFuture mqttFuture = new DefaultMqttFuture(futureKey);\n        //异步唤醒\n        new Thread(() -> {\n            try {\n                //模拟业务，休眠100毫秒\n                Thread.sleep(100);\n            } catch (InterruptedException e) {\n                //ignore\n            }\n            //唤醒\n            mqttFuture.setSuccess(true);\n        }).start();\n        mqttFuture.awaitCompleteUninterruptibly();\n        Assert.assertTrue((Boolean) mqttFuture.getResult() && mqttFuture.isSuccess());\n    }\n\n\n    @Test\n    public void testFutureFail() {\n        Object key = new Object();\n        MqttFutureKey futureKey = new MqttFutureKey(PropertiesUtils.getClientId(), key);\n        MqttFuture mqttFuture = new DefaultMqttFuture(futureKey);\n        //异步唤醒\n        new Thread(() -> {\n            try {\n                //模拟业务，休眠100毫秒\n                Thread.sleep(100);\n            } catch (InterruptedException e) {\n                //ignore\n            }\n            //唤醒\n            mqttFuture.setFailure(new RuntimeException(\"test future fail\"));\n        }).start();\n        mqttFuture.awaitCompleteUninterruptibly();\n        Object cause = mqttFuture.getCause();\n        Assert.assertTrue((cause instanceof Exception) && !mqttFuture.isSuccess());\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/github/netty/mqtt/client/PluginTest.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.connector.MqttConnector;\nimport io.github.netty.mqtt.client.constant.MqttVersion;\nimport io.github.netty.mqtt.client.handler.MqttDelegateHandler;\nimport io.github.netty.mqtt.client.plugin.Interceptor;\nimport io.github.netty.mqtt.client.plugin.Intercepts;\nimport io.github.netty.mqtt.client.plugin.Invocation;\nimport io.github.netty.mqtt.client.support.proxy.CglibProxyFactory;\nimport io.github.netty.mqtt.client.support.util.LogUtils;\nimport io.github.netty.mqtt.client.util.PropertiesUtils;\nimport org.junit.*;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n\n/**\n * 插件相关的测试用例\n * @author: xzc-coder\n */\n@RunWith(JUnit4.class)\npublic class PluginTest {\n\n    private static final MqttClientFactory mqttClientFactory = new DefaultMqttClientFactory();\n\n    private static final AtomicBoolean isExecuteMqttClientIntercept = new AtomicBoolean(false);\n\n    private static final AtomicBoolean isExecuteMqttConnectorIntercept = new AtomicBoolean(false);\n\n    private static final AtomicBoolean isExecuteMqttDelegateHandlerIntercept = new AtomicBoolean(false);\n\n\n    @BeforeClass\n    public static void beforeClass() throws IOException {\n        PropertiesUtils.loadTestProperties();\n    }\n\n    @After\n    public void reset() {\n        isExecuteMqttClientIntercept.set(false);\n        isExecuteMqttConnectorIntercept.set(false);\n        isExecuteMqttDelegateHandlerIntercept.set(false);\n    }\n\n    @AfterClass\n    public static void afterClass() {\n        mqttClientFactory.close();\n    }\n\n    @Test\n    public void testMqttClientPlugin() {\n        mqttClientFactory.addInterceptor(new MqttClientInterceptor());\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        mqttClient.connectFuture();\n        mqttClient.disconnect();\n        Assert.assertTrue(isExecuteMqttClientIntercept.get());\n    }\n\n\n    @Test\n    public void testMqttConnectorPlugin() {\n        mqttClientFactory.addInterceptor(new MqttConnectorInterceptor());\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        mqttClient.connectFuture();\n        mqttClient.disconnect();\n        Assert.assertTrue(isExecuteMqttConnectorIntercept.get());\n    }\n\n\n    @Test\n    public void testMqttDelegateHandlerPlugin() {\n        mqttClientFactory.addInterceptor(new MqttDelegateHandlerInterceptor());\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        mqttClient.connect();\n        mqttClient.disconnect();\n        Assert.assertTrue(isExecuteMqttDelegateHandlerIntercept.get());\n    }\n\n    @Test\n    public void testMultiplePlugin() {\n        mqttClientFactory.addInterceptor(new MqttClientInterceptor());\n        mqttClientFactory.addInterceptor(new MqttConnectorInterceptor());\n        mqttClientFactory.addInterceptor(new MqttDelegateHandlerInterceptor());\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        mqttClient.connect();\n        mqttClient.disconnect();\n        Assert.assertTrue(isExecuteMqttClientIntercept.get() && isExecuteMqttConnectorIntercept.get() && isExecuteMqttDelegateHandlerIntercept.get());\n    }\n\n    @Test\n    public void testCombinationPlugin() {\n        mqttClientFactory.addInterceptor(new CombinationInterceptor());\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        mqttClient.connect();\n        mqttClient.disconnect();\n        Assert.assertTrue(isExecuteMqttClientIntercept.get() && isExecuteMqttConnectorIntercept.get() && isExecuteMqttDelegateHandlerIntercept.get());\n    }\n\n    @Test\n    public void testCglibPlugin() {\n        mqttClientFactory.setProxyFactory(new CglibProxyFactory());\n        mqttClientFactory.addInterceptor(new CombinationInterceptor());\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        mqttClient.connect();\n        mqttClient.disconnect();\n        Assert.assertTrue(isExecuteMqttClientIntercept.get() && isExecuteMqttConnectorIntercept.get() && isExecuteMqttDelegateHandlerIntercept.get());\n    }\n\n\n    @Intercepts(type = {MqttClient.class})\n    private static class MqttClientInterceptor implements Interceptor {\n        @Override\n        public Object intercept(Invocation invocation) throws Throwable {\n            Object[] args = invocation.getArgs();\n            Method method = invocation.getMethod();\n            Object result = invocation.proceed();\n            LogUtils.info(MqttClientInterceptor.class, \"方法：\" + method.getName() + \"被拦截，参数：\" + Arrays.toString(args) + \",返回值：\" + result);\n            isExecuteMqttClientIntercept.set(true);\n            return result;\n        }\n    }\n\n\n    @Intercepts(type = {MqttConnector.class})\n    private static class MqttConnectorInterceptor implements Interceptor {\n        @Override\n        public Object intercept(Invocation invocation) throws Throwable {\n            Object[] args = invocation.getArgs();\n            Method method = invocation.getMethod();\n            Object result = invocation.proceed();\n            LogUtils.info(MqttConnectorInterceptor.class, \"方法：\" + method.getName() + \"被拦截，参数：\" + Arrays.toString(args) + \",返回值：\" + result);\n            isExecuteMqttConnectorIntercept.set(true);\n            return result;\n        }\n    }\n\n\n    @Intercepts(type = {MqttDelegateHandler.class})\n    private static class MqttDelegateHandlerInterceptor implements Interceptor {\n        @Override\n        public Object intercept(Invocation invocation) throws Throwable {\n            Object[] args = invocation.getArgs();\n            Method method = invocation.getMethod();\n            Object result = invocation.proceed();\n            LogUtils.info(MqttDelegateHandlerInterceptor.class, \"方法：\" + method.getName() + \"被拦截，参数：\" + Arrays.toString(args) + \",返回值：\" + result);\n            isExecuteMqttDelegateHandlerIntercept.set(true);\n            return result;\n        }\n    }\n\n    @Intercepts(type = {MqttClient.class, MqttConnector.class, MqttDelegateHandler.class})\n    private static class CombinationInterceptor implements Interceptor {\n\n        @Override\n        public Object intercept(Invocation invocation) throws Throwable {\n            Object[] args = invocation.getArgs();\n            Method method = invocation.getMethod();\n            Object result = invocation.proceed();\n            Object target = invocation.getTarget();\n            if (target instanceof MqttClient) {\n                isExecuteMqttClientIntercept.set(true);\n            } else if (target instanceof MqttConnector) {\n                isExecuteMqttConnectorIntercept.set(true);\n            } else if (target instanceof MqttDelegateHandler) {\n                isExecuteMqttDelegateHandlerIntercept.set(true);\n            }\n            LogUtils.info(MqttDelegateHandlerInterceptor.class, \"方法：\" + method.getName() + \"被拦截，参数：\" + Arrays.toString(args) + \",返回值：\" + result);\n            return result;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "src/test/java/io/github/netty/mqtt/client/SendReceiveMessageTest.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.callback.MqttCallback;\nimport io.github.netty.mqtt.client.callback.MqttReceiveCallbackResult;\nimport io.github.netty.mqtt.client.callback.MqttSendCallbackResult;\nimport io.github.netty.mqtt.client.constant.MqttVersion;\nimport io.github.netty.mqtt.client.msg.MqttMsgInfo;\nimport io.github.netty.mqtt.client.util.PropertiesUtils;\nimport io.netty.handler.codec.mqtt.MqttQoS;\nimport org.junit.AfterClass;\nimport org.junit.Assert;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * 收发消息相关的测试用例\n * @author: xzc-coder\n */\n@RunWith(JUnit4.class)\npublic class SendReceiveMessageTest {\n\n    private static MqttClientFactory mqttClientFactory;\n\n    private static final String TEST_TOPIC = \"testSubscribe\";\n\n    private static final int WAIT_TIMEOUT_SECONDS = 2;\n\n    @BeforeClass\n    public static void beforeClass() throws IOException {\n        mqttClientFactory = new DefaultMqttClientFactory();\n        PropertiesUtils.loadTestProperties();\n    }\n\n    @AfterClass\n    public static void afterClass() {\n        mqttClientFactory.close();\n    }\n\n    @Test\n    public void testMqttSendReceiveMessageQos0() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttQoS mqttQoS = MqttQoS.AT_MOST_ONCE;\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        AtomicBoolean sendMsgSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveMsgSuccess = new AtomicBoolean(false);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void messageSendCallback(MqttSendCallbackResult mqttSendCallbackResult) {\n                String topic = mqttSendCallbackResult.getTopic();\n                MqttQoS qos = mqttSendCallbackResult.getQos();\n                byte[] payload = mqttSendCallbackResult.getPayload();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos)) {\n                    sendMsgSuccess.set(true);\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                byte[] payload = receiveCallbackResult.getPayload();\n                String topic = receiveCallbackResult.getTopic();\n                MqttQoS qos = receiveCallbackResult.getQos();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos)) {\n                    receiveMsgSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(TEST_TOPIC, mqttQoS);\n        //发送消息\n        mqttClient.publish(bytes, TEST_TOPIC, mqttQoS);\n        //阻塞等待唤醒\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        Assert.assertTrue(sendMsgSuccess.get() && receiveMsgSuccess.get());\n    }\n\n\n    @Test\n    public void testMqttSendReceiveMessageQos1() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttQoS mqttQoS = MqttQoS.AT_LEAST_ONCE;\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        AtomicBoolean sendMsgSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveMsgSuccess = new AtomicBoolean(false);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void messageSendCallback(MqttSendCallbackResult mqttSendCallbackResult) {\n                String topic = mqttSendCallbackResult.getTopic();\n                MqttQoS qos = mqttSendCallbackResult.getQos();\n                byte[] payload = mqttSendCallbackResult.getPayload();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos)) {\n                    sendMsgSuccess.set(true);\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                byte[] payload = receiveCallbackResult.getPayload();\n                String topic = receiveCallbackResult.getTopic();\n                MqttQoS qos = receiveCallbackResult.getQos();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos)) {\n                    receiveMsgSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(TEST_TOPIC, mqttQoS);\n        //发送消息\n        mqttClient.publish(bytes, TEST_TOPIC, mqttQoS);\n        //阻塞等待唤醒\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        Assert.assertTrue(sendMsgSuccess.get() && receiveMsgSuccess.get());\n    }\n\n    @Test\n    public void testMqttSendReceiveMessageQos2() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttQoS mqttQoS = MqttQoS.AT_MOST_ONCE;\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        AtomicBoolean sendMsgSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveMsgSuccess = new AtomicBoolean(false);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void messageSendCallback(MqttSendCallbackResult mqttSendCallbackResult) {\n                String topic = mqttSendCallbackResult.getTopic();\n                MqttQoS qos = mqttSendCallbackResult.getQos();\n                byte[] payload = mqttSendCallbackResult.getPayload();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos)) {\n                    sendMsgSuccess.set(true);\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                byte[] payload = receiveCallbackResult.getPayload();\n                String topic = receiveCallbackResult.getTopic();\n                MqttQoS qos = receiveCallbackResult.getQos();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos)) {\n                    receiveMsgSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(TEST_TOPIC, mqttQoS);\n        //发送消息\n        mqttClient.publish(bytes, TEST_TOPIC, mqttQoS);\n        //阻塞等待唤醒\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        Assert.assertTrue(sendMsgSuccess.get() && receiveMsgSuccess.get());\n    }\n\n\n    @Test\n    public void testMqtt5SendReceiveMessageQos0() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        final String nameKey = \"name\";\n        final String nameValue = \"xzc-coder\";\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttQoS mqttQoS = MqttQoS.AT_MOST_ONCE;\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        AtomicBoolean sendMsgSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveMsgSuccess = new AtomicBoolean(false);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void messageSendCallback(MqttSendCallbackResult mqttSendCallbackResult) {\n                String topic = mqttSendCallbackResult.getTopic();\n                MqttQoS qos = mqttSendCallbackResult.getQos();\n                byte[] payload = mqttSendCallbackResult.getPayload();\n                String name = mqttSendCallbackResult.getUserMqttPropertyValue(nameKey);\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos) && nameValue.equals(name)) {\n                    sendMsgSuccess.set(true);\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                byte[] payload = receiveCallbackResult.getPayload();\n                String topic = receiveCallbackResult.getTopic();\n                MqttQoS qos = receiveCallbackResult.getQos();\n                String name = receiveCallbackResult.getUserMqttPropertyValue(nameKey);\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos) && nameValue.equals(name)) {\n                    receiveMsgSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(TEST_TOPIC, mqttQoS);\n        //发送消息\n        MqttMsgInfo mqttMsgInfo = new MqttMsgInfo(TEST_TOPIC, bytes, mqttQoS);\n        mqttMsgInfo.addMqttUserProperty(nameKey, nameValue);\n        mqttClient.publish(mqttMsgInfo);\n        //阻塞等待唤醒\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        Assert.assertTrue(sendMsgSuccess.get() && receiveMsgSuccess.get());\n    }\n\n\n    @Test\n    public void testMqtt5SendReceiveMessageQos1() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        final String nameKey = \"name\";\n        final String nameValue = \"xzc-coder\";\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttQoS mqttQoS = MqttQoS.AT_LEAST_ONCE;\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        AtomicBoolean sendMsgSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveMsgSuccess = new AtomicBoolean(false);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void messageSendCallback(MqttSendCallbackResult mqttSendCallbackResult) {\n                String topic = mqttSendCallbackResult.getTopic();\n                MqttQoS qos = mqttSendCallbackResult.getQos();\n                byte[] payload = mqttSendCallbackResult.getPayload();\n                String name = mqttSendCallbackResult.getUserMqttPropertyValue(nameKey);\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos) && nameValue.equals(name)) {\n                    sendMsgSuccess.set(true);\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                byte[] payload = receiveCallbackResult.getPayload();\n                String topic = receiveCallbackResult.getTopic();\n                MqttQoS qos = receiveCallbackResult.getQos();\n                String name = receiveCallbackResult.getUserMqttPropertyValue(nameKey);\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos) && nameValue.equals(name)) {\n                    receiveMsgSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(TEST_TOPIC, mqttQoS);\n        //发送消息\n        MqttMsgInfo mqttMsgInfo = new MqttMsgInfo(TEST_TOPIC, bytes, mqttQoS);\n        mqttMsgInfo.addMqttUserProperty(nameKey, nameValue);\n        mqttClient.publish(mqttMsgInfo);\n        //阻塞等待唤醒\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        Assert.assertTrue(sendMsgSuccess.get() && receiveMsgSuccess.get());\n    }\n\n    @Test\n    public void testMqtt5SendReceiveMessageQos2() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        final String nameKey = \"name\";\n        final String nameValue = \"xzc-coder\";\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttQoS mqttQoS = MqttQoS.AT_LEAST_ONCE;\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        AtomicBoolean sendMsgSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveMsgSuccess = new AtomicBoolean(false);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void messageSendCallback(MqttSendCallbackResult mqttSendCallbackResult) {\n                String topic = mqttSendCallbackResult.getTopic();\n                MqttQoS qos = mqttSendCallbackResult.getQos();\n                byte[] payload = mqttSendCallbackResult.getPayload();\n                String name = mqttSendCallbackResult.getUserMqttPropertyValue(nameKey);\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos) && nameValue.equals(name)) {\n                    sendMsgSuccess.set(true);\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                byte[] payload = receiveCallbackResult.getPayload();\n                String topic = receiveCallbackResult.getTopic();\n                MqttQoS qos = receiveCallbackResult.getQos();\n                String name = receiveCallbackResult.getUserMqttPropertyValue(nameKey);\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic) && mqttQoS.equals(qos) && nameValue.equals(name)) {\n                    receiveMsgSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(TEST_TOPIC, mqttQoS);\n        //发送消息\n        MqttMsgInfo mqttMsgInfo = new MqttMsgInfo(TEST_TOPIC, bytes, mqttQoS);\n        mqttMsgInfo.addMqttUserProperty(nameKey, nameValue);\n        mqttClient.publish(mqttMsgInfo);\n        //阻塞等待唤醒\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        Assert.assertTrue(sendMsgSuccess.get() && receiveMsgSuccess.get());\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/github/netty/mqtt/client/SerializableTest.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.constant.MqttMsgDirection;\nimport io.github.netty.mqtt.client.constant.MqttMsgState;\nimport io.github.netty.mqtt.client.msg.MqttMsg;\nimport io.github.netty.mqtt.client.support.util.MqttUtils;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\n/**\n * 收发消息相关的测试用例\n * @author: xzc-coder\n */\n@RunWith(JUnit4.class)\npublic class SerializableTest {\n\n    @Test\n    public void serializableMqttByteTest() {\n        MqttMsg mqttMsg = new MqttMsg(1, \"test\");\n        mqttMsg.setMsgState(MqttMsgState.PUBLISH);\n        mqttMsg.setMqttMsgDirection(MqttMsgDirection.SEND);\n        mqttMsg.setDup(true);\n        mqttMsg.setRetain(true);\n        mqttMsg.setQos(MqttQoS.AT_LEAST_ONCE);\n        mqttMsg.setPayload(new byte[]{1, 2, 3});\n        byte[] bytes = MqttUtils.serializableMsg(mqttMsg);\n        MqttMsg tempMqttMsg = MqttUtils.deserializableMsg(bytes);\n        Assert.assertEquals(mqttMsg, tempMqttMsg);\n    }\n\n    @Test\n    public void serializableMqttBase64Test() {\n        MqttMsg mqttMsg = new MqttMsg(1, \"test\");\n        mqttMsg.setMsgState(MqttMsgState.PUBLISH);\n        mqttMsg.setMqttMsgDirection(MqttMsgDirection.SEND);\n        mqttMsg.setDup(true);\n        mqttMsg.setRetain(true);\n        mqttMsg.setQos(MqttQoS.AT_LEAST_ONCE);\n        mqttMsg.setPayload(new byte[]{1, 2, 3});\n        String base64 = MqttUtils.serializableMsgBase64(mqttMsg);\n        MqttMsg tempMqttMsg = MqttUtils.deserializableMsgBase64(base64);\n        Assert.assertEquals(mqttMsg, tempMqttMsg);\n    }\n\n\n    @Test\n    public void serializableByteTest5() {\n        MqttMsg mqttMsg = new MqttMsg(1, \"test\");\n        mqttMsg.setMsgState(MqttMsgState.PUBLISH);\n        mqttMsg.setMqttMsgDirection(MqttMsgDirection.SEND);\n        mqttMsg.setDup(true);\n        mqttMsg.setRetain(true);\n        mqttMsg.setQos(MqttQoS.AT_LEAST_ONCE);\n        mqttMsg.setPayload(new byte[]{1, 2, 3});\n        //mqtt5的参数\n        mqttMsg.setReasonCode((byte) 1);\n        MqttProperties mqttProperties = new MqttProperties();\n        //只支持发布消息的属性\n        mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS.value(), 20));\n        mqttMsg.setMqttProperties(mqttProperties);\n        byte[] bytes = MqttUtils.serializableMsg(mqttMsg);\n        MqttMsg tempMqttMsg = MqttUtils.deserializableMsg(bytes);\n        //因为MqttProperties没有重写equals，所以不能使用MqttMsg的equals\n        Assert.assertNotNull(tempMqttMsg);\n        MqttProperties.MqttProperty mqttProperty = mqttMsg.getMqttProperties().getProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS.value());\n        MqttProperties.MqttProperty tempMqttProperty = tempMqttMsg.getMqttProperties().getProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS.value());\n        Assert.assertEquals(mqttProperty, tempMqttProperty);\n    }\n\n    @Test\n    public void serializableBase64Test5() {\n        MqttMsg mqttMsg = new MqttMsg(1, \"test\");\n        mqttMsg.setMsgState(MqttMsgState.PUBLISH);\n        mqttMsg.setMqttMsgDirection(MqttMsgDirection.SEND);\n        mqttMsg.setDup(true);\n        mqttMsg.setRetain(true);\n        mqttMsg.setQos(MqttQoS.AT_LEAST_ONCE);\n        mqttMsg.setPayload(new byte[]{1, 2, 3});\n        //mqtt5的参数\n        mqttMsg.setReasonCode((byte) 1);\n        MqttProperties mqttProperties = new MqttProperties();\n        //只支持发布消息的属性\n        mqttProperties.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS.value(), 20));\n        mqttMsg.setMqttProperties(mqttProperties);\n        String base64 = MqttUtils.serializableMsgBase64(mqttMsg);\n        MqttMsg tempMqttMsg = MqttUtils.deserializableMsgBase64(base64);\n        //因为MqttProperties没有重写equals，所以不能使用MqttMsg的equals\n        Assert.assertNotNull(tempMqttMsg);\n        MqttProperties.MqttProperty mqttProperty = mqttMsg.getMqttProperties().getProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS.value());\n        MqttProperties.MqttProperty tempMqttProperty = tempMqttMsg.getMqttProperties().getProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS.value());\n        Assert.assertEquals(mqttProperty, tempMqttProperty);\n    }\n\n\n}\n"
  },
  {
    "path": "src/test/java/io/github/netty/mqtt/client/SslTest.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.constant.MqttVersion;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\nimport io.github.netty.mqtt.client.util.PropertiesUtils;\nimport org.junit.*;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * SSL相关的测试用例\n * @author: xzc-coder\n */\n@RunWith(JUnit4.class)\npublic class SslTest {\n\n    private static MqttClientFactory mqttClientFactory = new DefaultMqttClientFactory();\n\n    @BeforeClass\n    public static void beforeClass() throws IOException {\n        PropertiesUtils.loadTestProperties();\n    }\n\n    @AfterClass\n    public static void afterClass() {\n        mqttClientFactory.close();\n    }\n\n    @Test\n    public void testSingleSsl() {\n        String host = PropertiesUtils.getHost();\n        int serverSslPort = PropertiesUtils.getServerSslPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(serverSslPort);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        mqttConnectParameter.setSsl(true);\n        //如果是权威CA颁发的证书则不需要导入证书\n        File rootCertificateFile = getRootCertificateFile();\n        if (rootCertificateFile != null) {\n            mqttConnectParameter.setRootCertificateFile(rootCertificateFile);\n        }\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        try {\n            mqttClient.connect();\n        } catch (Exception e) {\n            Assert.fail(\"clientId connect failed,cause : \" + e.getMessage());\n        }\n        Assert.assertTrue(mqttClient.isOnline());\n    }\n\n    /**\n     * 该测试需要搭建Broker，并且生成对应的证书及导入到Broker中，过于麻烦，\n     * 需要进行该测试的自行搭建测试\n     */\n    @Test\n    @Ignore\n    public void testTwoWaySsl() {\n        String host = PropertiesUtils.getHost();\n        int serverSslPort = PropertiesUtils.getServerSslPort();\n        String clientId = PropertiesUtils.getClientId();\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(serverSslPort);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        mqttConnectParameter.setSsl(true);\n        //如果是权威CA颁发的证书则不需要导入证书\n        File rootCertificateFile = getRootCertificateFile();\n        if (rootCertificateFile != null) {\n            mqttConnectParameter.setRootCertificateFile(rootCertificateFile);\n        }\n        //双向认证需要的，客户端的证书\n        File clientCertificateFile = getClientCertificateFile();\n        if (clientCertificateFile != null) {\n            mqttConnectParameter.setClientCertificateFile(clientCertificateFile);\n        }\n        //双向认证需要的，客户端的私钥\n        File clientPrivateKeyFile = getClientPrivateKeyFile();\n        if (clientPrivateKeyFile != null) {\n            mqttConnectParameter.setClientPrivateKeyFile(clientPrivateKeyFile);\n        }\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        try {\n            mqttClient.connect();\n        } catch (Exception e) {\n            Assert.fail(\"clientId connect failed,cause : \" + e.getMessage());\n        }\n        Assert.assertTrue(mqttClient.isOnline());\n    }\n\n    /**\n     * 获取Broker的根证书文件\n     *\n     * @return Broker的根证书文件\n     */\n    private static File getRootCertificateFile() {\n        String rootCertificateFileName = PropertiesUtils.getRootCertificateFileName();\n        if (EmptyUtils.isNotBlank(rootCertificateFileName)) {\n            String path = SslTest.class.getClassLoader().getResource(\"\").getPath();\n            path = path + File.separator + rootCertificateFileName;\n            return new File(path);\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * 获取客户端的证书文件\n     *\n     * @return 客户端的证书文件\n     */\n    public static File getClientCertificateFile() {\n        String clientCertificateFileName = PropertiesUtils.getClientCertificateFileName();\n        if (EmptyUtils.isNotBlank(clientCertificateFileName)) {\n            String path = SslTest.class.getClassLoader().getResource(\"\").getPath();\n            path = path + File.separator + clientCertificateFileName;\n            return new File(path);\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * 获取客户端的私钥文件\n     *\n     * @return 客户端的私钥\n     */\n    public File getClientPrivateKeyFile() {\n        String clientPrivateKeyFileName = PropertiesUtils.getClientPrivateKeyFileName();\n        if (EmptyUtils.isNotBlank(clientPrivateKeyFileName)) {\n            String path = SslTest.class.getClassLoader().getResource(\"\").getPath();\n            path = path + File.separator + clientPrivateKeyFileName;\n            return new File(path);\n        } else {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/github/netty/mqtt/client/SubscribeTest.java",
    "content": "package io.github.netty.mqtt.client;\n\nimport io.github.netty.mqtt.client.callback.*;\nimport io.github.netty.mqtt.client.constant.MqttVersion;\nimport io.github.netty.mqtt.client.msg.MqttSubInfo;\nimport io.github.netty.mqtt.client.support.util.EmptyUtils;\nimport io.github.netty.mqtt.client.util.PropertiesUtils;\nimport io.github.netty.mqtt.client.callback.*;\nimport io.netty.handler.codec.mqtt.MqttProperties;\nimport io.netty.handler.codec.mqtt.MqttQoS;\nimport org.junit.AfterClass;\nimport org.junit.Assert;\nimport org.junit.BeforeClass;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * 订阅相关的测试用例\n * @author: xzc-coder\n */\n@RunWith(JUnit4.class)\npublic class SubscribeTest {\n\n    private static final MqttClientFactory mqttClientFactory = new DefaultMqttClientFactory();\n    /**\n     * 临时客户端，用来发消息\n     */\n    private static MqttClient tempMqttClient;\n\n    private static final String TEST_TOPIC = \"testSubscribe\";\n\n    private static final int WAIT_TIMEOUT_SECONDS = 2;\n\n    @BeforeClass\n    public static void beforeClass() throws IOException {\n        PropertiesUtils.loadTestProperties();\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = System.currentTimeMillis() + \"-test\";\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        tempMqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        tempMqttClient.connect();\n    }\n\n    @AfterClass\n    public static void afterClass() {\n        mqttClientFactory.close();\n    }\n\n    @Test\n    public void testSubscribe() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        AtomicBoolean subscribeSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveSubscribeSuccess = new AtomicBoolean(false);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void subscribeCallback(MqttSubscribeCallbackResult mqttSubscribeCallbackResult) {\n                if (EmptyUtils.isNotEmpty(mqttSubscribeCallbackResult.getSubscribeCallbackInfoList())) {\n                    MqttSubscribeCallbackInfo mqttSubscribeCallbackInfo = mqttSubscribeCallbackResult.getSubscribeCallbackInfoList().get(0);\n                    String subscribeTopic = mqttSubscribeCallbackInfo.getSubscribeTopic();\n                    boolean subscribed = mqttSubscribeCallbackInfo.isSubscribed();\n                    subscribeSuccess.set((subscribed && TEST_TOPIC.equals(subscribeTopic)));\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                byte[] payload = receiveCallbackResult.getPayload();\n                String topic = receiveCallbackResult.getTopic();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic)) {\n                    receiveSubscribeSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(TEST_TOPIC, MqttQoS.EXACTLY_ONCE);\n        //发送消息\n        tempMqttClient.publish(bytes, TEST_TOPIC, MqttQoS.EXACTLY_ONCE);\n        //阻塞等待唤醒\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        Assert.assertTrue(subscribeSuccess.get() && receiveSubscribeSuccess.get());\n    }\n\n    @Test\n    public void testSubscribe5() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n        mqttUserProperties.add(\"name\", \"xzc-coder\");\n        AtomicBoolean subscribeSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveSubscribeSuccess = new AtomicBoolean(false);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void subscribeCallback(MqttSubscribeCallbackResult mqttSubscribeCallbackResult) {\n                if (EmptyUtils.isNotEmpty(mqttSubscribeCallbackResult.getSubscribeCallbackInfoList())) {\n                    MqttSubscribeCallbackInfo mqttSubscribeCallbackInfo = mqttSubscribeCallbackResult.getSubscribeCallbackInfoList().get(0);\n                    String subscribeTopic = mqttSubscribeCallbackInfo.getSubscribeTopic();\n                    boolean subscribed = mqttSubscribeCallbackInfo.isSubscribed();\n                    subscribeSuccess.set((subscribed && TEST_TOPIC.equals(subscribeTopic)));\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                byte[] payload = receiveCallbackResult.getPayload();\n                String topic = receiveCallbackResult.getTopic();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic)) {\n                    receiveSubscribeSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(new MqttSubInfo(TEST_TOPIC, MqttQoS.EXACTLY_ONCE), 1, mqttUserProperties);\n        //发送消息\n        tempMqttClient.publish(bytes, TEST_TOPIC, MqttQoS.EXACTLY_ONCE);\n        //阻塞等待唤醒\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        Assert.assertTrue(subscribeSuccess.get() && receiveSubscribeSuccess.get());\n    }\n\n    @Test\n    public void testUnsubscribe() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_3_1_1);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        AtomicBoolean subscribeSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveSubscribeSuccess = new AtomicBoolean(false);\n        AtomicBoolean unsubscribeSuccess = new AtomicBoolean(false);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void subscribeCallback(MqttSubscribeCallbackResult mqttSubscribeCallbackResult) {\n                if (EmptyUtils.isNotEmpty(mqttSubscribeCallbackResult.getSubscribeCallbackInfoList())) {\n                    MqttSubscribeCallbackInfo mqttSubscribeCallbackInfo = mqttSubscribeCallbackResult.getSubscribeCallbackInfoList().get(0);\n                    String subscribeTopic = mqttSubscribeCallbackInfo.getSubscribeTopic();\n                    boolean subscribed = mqttSubscribeCallbackInfo.isSubscribed();\n                    subscribeSuccess.set((subscribed && TEST_TOPIC.equals(subscribeTopic)));\n                }\n            }\n\n            @Override\n            public void unsubscribeCallback(MqttUnSubscribeCallbackResult mqttUnSubscribeCallbackResult) {\n                if (EmptyUtils.isNotEmpty(mqttUnSubscribeCallbackResult.getUnsubscribeInfoList())) {\n                    MqttUnSubscribeCallbackInfo unSubscribeCallbackInfo = mqttUnSubscribeCallbackResult.getUnsubscribeInfoList().get(0);\n                    boolean unSubscribed = unSubscribeCallbackInfo.isUnSubscribed();\n                    String topic = unSubscribeCallbackInfo.getTopic();\n                    unsubscribeSuccess.set((unSubscribed && TEST_TOPIC.equals(topic)));\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                byte[] payload = receiveCallbackResult.getPayload();\n                String topic = receiveCallbackResult.getTopic();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic)) {\n                    receiveSubscribeSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(TEST_TOPIC, MqttQoS.EXACTLY_ONCE);\n        //取消订阅\n        mqttClient.unsubscribe(TEST_TOPIC);\n        //发送消息\n        tempMqttClient.publish(bytes, TEST_TOPIC, MqttQoS.EXACTLY_ONCE);\n        //阻塞等待唤醒，取消订阅此处会超时唤醒以证明取消订阅后接受不到消息\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        //取消订阅后接受消息应该为false\n        Assert.assertTrue(subscribeSuccess.get() && !receiveSubscribeSuccess.get() && unsubscribeSuccess.get());\n    }\n\n\n    @Test\n    public void testUnsubscribe5() throws InterruptedException {\n        String host = PropertiesUtils.getHost();\n        int port = PropertiesUtils.getPort();\n        String clientId = PropertiesUtils.getClientId();\n        byte[] bytes = new byte[]{1, 1, 1, 1};\n        MqttConnectParameter mqttConnectParameter = new MqttConnectParameter(clientId);\n        mqttConnectParameter.setHost(host);\n        mqttConnectParameter.setPort(port);\n        mqttConnectParameter.setMqttVersion(MqttVersion.MQTT_5_0_0);\n        MqttClient mqttClient = mqttClientFactory.createMqttClient(mqttConnectParameter);\n        AtomicBoolean subscribeSuccess = new AtomicBoolean(false);\n        AtomicBoolean receiveSubscribeSuccess = new AtomicBoolean(false);\n        AtomicBoolean unsubscribeSuccess = new AtomicBoolean(false);\n        CountDownLatch countDownLatch = new CountDownLatch(1);\n        mqttClient.addMqttCallback(new MqttCallback() {\n            @Override\n            public void subscribeCallback(MqttSubscribeCallbackResult mqttSubscribeCallbackResult) {\n                if (EmptyUtils.isNotEmpty(mqttSubscribeCallbackResult.getSubscribeCallbackInfoList())) {\n                    MqttSubscribeCallbackInfo mqttSubscribeCallbackInfo = mqttSubscribeCallbackResult.getSubscribeCallbackInfoList().get(0);\n                    String subscribeTopic = mqttSubscribeCallbackInfo.getSubscribeTopic();\n                    boolean subscribed = mqttSubscribeCallbackInfo.isSubscribed();\n                    subscribeSuccess.set((subscribed && TEST_TOPIC.equals(subscribeTopic)));\n                }\n            }\n\n            @Override\n            public void unsubscribeCallback(MqttUnSubscribeCallbackResult mqttUnSubscribeCallbackResult) {\n                if (EmptyUtils.isNotEmpty(mqttUnSubscribeCallbackResult.getUnsubscribeInfoList())) {\n                    MqttUnSubscribeCallbackInfo unSubscribeCallbackInfo = mqttUnSubscribeCallbackResult.getUnsubscribeInfoList().get(0);\n                    boolean unSubscribed = unSubscribeCallbackInfo.isUnSubscribed();\n                    String topic = unSubscribeCallbackInfo.getTopic();\n                    unsubscribeSuccess.set((unSubscribed && TEST_TOPIC.equals(topic)));\n                }\n            }\n\n            @Override\n            public void messageReceiveCallback(MqttReceiveCallbackResult receiveCallbackResult) {\n                String topic = receiveCallbackResult.getTopic();\n                byte[] payload = receiveCallbackResult.getPayload();\n                if (Arrays.equals(payload, bytes) && TEST_TOPIC.equals(topic)) {\n                    receiveSubscribeSuccess.set(true);\n                    countDownLatch.countDown();\n                }\n            }\n        });\n        mqttClient.connect();\n        mqttClient.subscribe(TEST_TOPIC, MqttQoS.EXACTLY_ONCE);\n        //取消订阅 添加MQTT5信息\n        MqttProperties.UserProperties mqttUserProperties = new MqttProperties.UserProperties();\n        mqttUserProperties.add(\"name\", \"xzc-coder\");\n        mqttClient.unsubscribe(TEST_TOPIC, mqttUserProperties);\n        //发送消息\n        tempMqttClient.publishFuture(bytes, TEST_TOPIC, MqttQoS.EXACTLY_ONCE);\n        //阻塞等待唤醒，取消订阅此处会超时唤醒以证明取消订阅后接受不到消息\n        countDownLatch.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);\n        //取消订阅后接受消息应该为false\n        Assert.assertTrue(subscribeSuccess.get() && !receiveSubscribeSuccess.get() && unsubscribeSuccess.get());\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/github/netty/mqtt/client/util/PropertiesUtils.java",
    "content": "package io.github.netty.mqtt.client.util;\n\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.util.Properties;\n\n/**\n * 测试用的Properties工具类\n * @author: xzc-coder\n */\npublic class PropertiesUtils {\n\n    private static final String TEST_NAME = \"test.properties\";\n\n    private static final String HOST_KEY = \"host\";\n\n    private static final String PORT_KEY = \"port\";\n\n    private static final String CLIENT_ID_KEY = \"clientId\";\n\n    private static final String SERVER_SSL_PORT_KEY = \"serverSslPort\";\n\n    private static final String ROOT_CERTIFICATE_FILE_NAME_KEY = \"rootCertificateFileName\";\n\n\n    private static final String CLIENT_CERTIFICATE_FILE_NAME_KEY = \"clientCertificateFileName\";\n\n    private static final String CLIENT_PRIVATE_KEY_FILE_NAME_KEY = \"clientPrivateKeyFileName\";\n\n    private static final Properties properties = new Properties();\n\n    private PropertiesUtils() {\n    }\n\n    public static String getHost() {\n        return properties.getProperty(HOST_KEY);\n    }\n\n    public static int getPort() {\n        return Integer.parseInt(properties.getProperty(PORT_KEY));\n    }\n\n    public static String getClientId() {\n        return properties.getProperty(CLIENT_ID_KEY);\n    }\n\n    public static int getServerSslPort() {\n        return Integer.parseInt(properties.getProperty(SERVER_SSL_PORT_KEY));\n    }\n\n    public static String getRootCertificateFileName() {\n        return properties.getProperty(ROOT_CERTIFICATE_FILE_NAME_KEY);\n    }\n\n    public static String getClientCertificateFileName() {\n        return properties.getProperty(CLIENT_CERTIFICATE_FILE_NAME_KEY);\n    }\n\n    public static String getClientPrivateKeyFileName() {\n        return properties.getProperty(CLIENT_PRIVATE_KEY_FILE_NAME_KEY);\n    }\n\n    public static void loadTestProperties() throws IOException {\n        String path = PropertiesUtils.class.getClassLoader().getResource(\"\").getPath();\n        path = path + File.separator + TEST_NAME;\n        properties.load(Files.newInputStream(new File(path).toPath()));\n    }\n\n}\n"
  },
  {
    "path": "src/test/resources/broker.emqx.io-ca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\nb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\nq2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\ntCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\nvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\nBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\nNeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\nFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\npLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\nMrY=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/test.properties",
    "content": "host=broker.emqx.io\nport=1883\nclientId=netty-mqtt-client-test\nserverSslPort=8883\nrootCertificateFileName=broker.emqx.io-ca.crt\n\n\n"
  }
]