Compare commits

...
46 Commits
Author SHA1 Message Date
heavyrian2012 d4c6b1f616 update release note 2022-10-25 19:28:42 +08:00
imndx ae3eb9c2df linux 多端登录处理错误 2022-10-25 15:03:12 +08:00
heavyrian2012 771a4d751f version 0.97 and release note 2022-10-24 10:54:11 +08:00
heavyrian2012 2bed06cf43 更新升级说明 2022-10-24 10:49:30 +08:00
heavyrian2012 50f0330884 解决版本信息错误问题 2022-10-24 10:25:01 +08:00
heavyrian2012 acd21faa9c 优化野火IM服务安装目录 2022-10-24 09:53:44 +08:00
heavyrian2012 6e570c74ae 更新docker说明 2022-10-23 20:55:30 +08:00
heavyrian2012 893b58cef4 update dockerfile 2022-10-23 20:26:09 +08:00
heavyrian2012 bd2b037edb 删除旧的好友欢迎语的多语言处理方式 2022-10-23 19:22:54 +08:00
heavyrian2012 6acb1c968a 添加h2db路径的配置 2022-10-23 09:48:13 +08:00
heavyrian2012 6048748300 更新readme 2022-10-22 11:07:58 +08:00
heavyrian2012 9be49c3d41 update readme 2022-10-22 09:47:00 +08:00
heavyrian2012 d9eccd39de update readme 2022-10-22 08:52:35 +08:00
heavyrian2012 ffaffc8fc1 update systemctl service file 2022-10-21 19:46:10 +08:00
heavyrian2012 5e1f4fe6c9 update systectl service readme 2022-10-21 19:36:43 +08:00
heavyrian2012 b5d884f9f6 优化deb和rpm包 2022-10-21 19:12:11 +08:00
heavyrian2012 c0840f3a73 添加RPM打包方式 2022-10-21 16:38:58 +08:00
heavyrian2012 34bd59f195 打包输出deb包 2022-10-21 11:07:30 +08:00
heavyrian2012 8e1d56b2ca 添加service运行方式 2022-10-21 09:31:39 +08:00
heavyrian2012 a4eb64edd5 添加配置注释说明 2022-10-20 15:47:59 +08:00
heavyrian2012 b141990661 应用平台的部分错误日志改成error级别 2022-10-20 15:44:47 +08:00
imndx 0f9df0659f 名片消息推送 2022-10-18 22:07:19 +08:00
heavyrian2012 a2d4bf7d43 http回调失败用error打印日志 2022-10-12 17:08:35 +08:00
heavyrain.lee 898c4f2419 Merge pull request #379 from wildfirechat/dependabot/maven/broker/org.apache.httpcomponents-httpclient-4.5.13
Bump httpclient from 4.4.1 to 4.5.13 in /broker
2022-10-12 15:54:59 +08:00
heavyrain.lee e1a8a040df Merge pull request #381 from wildfirechat/dependabot/maven/sdk/org.apache.httpcomponents-httpclient-4.5.13
Bump httpclient from 4.5.3 to 4.5.13 in /sdk
2022-10-12 15:54:14 +08:00
heavyrain.lee adb4f082f1 Merge pull request #364 from wildfirechat/dependabot/maven/sdk/commons-io-commons-io-2.7
Bump commons-io from 2.5 to 2.7 in /sdk
2022-10-12 15:52:40 +08:00
heavyrain.lee 2724c79a79 Merge pull request #363 from wildfirechat/dependabot/maven/broker/commons-io-commons-io-2.7
Bump commons-io from 2.5 to 2.7 in /broker
2022-10-12 15:52:12 +08:00
heavyrain.lee ba01c90171 Merge pull request #544 from ren-jq101/oscs_fix_cd3626oau51of7vbl0vg
fix(sec): upgrade commons-codec:commons-codec to 1.13
2022-10-12 15:49:19 +08:00
ren-jq101 b8967ccaff update commons-codec:commons-codec 1.10 to 1.13 2022-10-12 14:39:29 +08:00
heavyrian2012 0e6e23da13 更新版本号和release note 2022-10-06 13:39:04 +08:00
heavyrian2012 88652af565 添加会议录制接口 2022-10-06 11:41:51 +08:00
heavyrian2012 af58bf91a4 单聊消息可以定向发送,只发送给其中一个用户 2022-09-29 08:36:01 +08:00
heavyrian2012 9d86539d01 update issue template 2022-09-23 22:25:48 +08:00
heavyrian2012 2559fa7e48 文章消息添加摘要信息 2022-09-20 11:03:14 +08:00
heavyrian2012 d8cdcfc09d udpate version and release not 2022-09-20 10:46:07 +08:00
heavyrian2012 209b1950b6 公众号消息添加摘要字段 2022-09-20 10:41:45 +08:00
heavyrian2012 7ea1a2b146 升级netty版本为4.1.68.Final 2022-09-18 22:05:22 +08:00
heavyrian2012 279530ade2 update github issue template 2022-09-08 21:28:42 +08:00
heavyrian2012 5a0fd95fc7 update readme 2022-09-08 13:34:18 +08:00
heavyrian2012 ec72ab6baf fix build error 2022-09-06 16:14:20 +08:00
heavyrian2012 cd6c7d2da9 添加按照用户会话删除消息接口 2022-09-06 12:54:24 +08:00
heavyrian2012 39371797d2 update readme 2022-09-05 16:24:13 +08:00
dependabot[bot] 20f71f5a21 Bump httpclient from 4.5.3 to 4.5.13 in /sdk
Bumps httpclient from 4.5.3 to 4.5.13.

---
updated-dependencies:
- dependency-name: org.apache.httpcomponents:httpclient
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-06-04 02:11:35 +00:00
dependabot[bot] 326f76afd8 Bump httpclient from 4.4.1 to 4.5.13 in /broker
Bumps httpclient from 4.4.1 to 4.5.13.

---
updated-dependencies:
- dependency-name: org.apache.httpcomponents:httpclient
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-06-04 01:50:00 +00:00
dependabot[bot] 9f36d47ca8 Bump commons-io from 2.5 to 2.7 in /sdk
Bumps commons-io from 2.5 to 2.7.

Signed-off-by: dependabot[bot] <[email protected]>
2021-04-26 19:30:47 +00:00
dependabot[bot] 57cdd57a30 Bump commons-io from 2.5 to 2.7 in /broker
Bumps commons-io from 2.5 to 2.7.

Signed-off-by: dependabot[bot] <[email protected]>
2021-04-26 18:54:43 +00:00
93 changed files with 3320 additions and 859 deletions
+7 -1
View File
@@ -11,7 +11,13 @@
你认为正确的表现应该是什么样的。 你认为正确的表现应该是什么样的。
#### 补充条件 #### 补充条件
是否是毕现的,还是偶现的?是否只有在特殊的网络/设备/平台上出现,还是所有的都出现。还有您用的版本是什么时候的,是不是最新版 是否是必现的,还是偶现的?是否只有在特殊的网络/设备/平台上出现,还是所有的都出现。还有您用的版本是什么时候的,是不是最新版
#### demo对比结果 #### demo对比结果
请用demo对比测试,demo上是什么状态。 请用demo对比测试,demo上是什么状态。
#### 检索已有问题
除了可以检索已有issue外,还可以去野火论坛查找已有问题,论坛地址为 https://bbs.wildfirechat.cn
#### star 我们
给我们点个star,可以及时接收到我们回复信息,另外也是对我们支持人员辛苦工作的认可,激励我们更好更快地为大家服务。
+58 -36
View File
@@ -4,10 +4,11 @@
## 功能特性 ## 功能特性
* 极致地硬件利用率,IM服务最低128M内存即可运行,上不封顶。 * 极致地硬件利用率,IM服务最低128M内存即可运行,上不封顶。
* 协议先进,采用MQTT+Protobuf组合,流量和性能极致优化。 * 协议先进,采用MQTT+Protobuf组合,流量和性能极致优化。
* 性能强大,专业版IM服务支持百万在线和集群部署。 * 性能强大,专业版IM服务支持百万在线和集群部署,业内顶尖性能水平,详情参考野火性能报告。
* 部署运维简单,依赖服务少,稍加配置一键启动。 * 部署运维简单,依赖服务少,稍加配置一键启动。
* 安全加密。网络连接AES加密。客户端数据库SqlCipher加密。安全无小事。 * 安全加密。网络连接AES加密。客户端数据库SqlCipher加密。安全无小事。
* 全平台客户端,四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。 * 全平台客户端,支持Android、iOS、Web、小程序、UniApp、Flutter、Windows、Mac OS、Linux等所有常见平台。
* 四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。
* 支持国产化。支持国产化操作系统、国产化芯片和国产操作系统。支持国密加密。 * 支持国产化。支持国产化操作系统、国产化芯片和国产操作系统。支持国密加密。
* 客户端使用微信[mars](https://github.com/tencent/mars)连接库,野火IM可能是最适应中国网络国情的即时通讯服务。 * 客户端使用微信[mars](https://github.com/tencent/mars)连接库,野火IM可能是最适应中国网络国情的即时通讯服务。
* 支持加速点加速,即可用于全球应用,也可用于政企内外双网复杂的网络环境。 * 支持加速点加速,即可用于全球应用,也可用于政企内外双网复杂的网络环境。
@@ -15,38 +16,45 @@
* 音视频多种解决方案,丰俭由人,可自由选择。 * 音视频多种解决方案,丰俭由人,可自由选择。
* 高级音视频功能强大,支持9人以上的群组视频通话,支持1080P视频,支持会议模式,支持百人以上会议,支持服务器端录制。 * 高级音视频功能强大,支持9人以上的群组视频通话,支持1080P视频,支持会议模式,支持百人以上会议,支持服务器端录制。
* 全私有部署,可不依赖任何第三方服务,完全内网部署。 * 全私有部署,可不依赖任何第三方服务,完全内网部署。
* 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。Demo成熟完善,基本可以做到开箱即用,也可把SDK嵌入其它应用。 * 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。代码开源率高,二次开发简单。
* 结构设计合理,客户端分功能库、UI库和应用三层。应用成熟完善,开箱即用,也可把SDK嵌入其它应用。
* 拥有应用开放平台,可以开发和创建自建应用,扩展您的工作台。 * 拥有应用开放平台,可以开发和创建自建应用,扩展您的工作台。
* API丰富,方便与其它服务系统的对接。 * API丰富,方便与其它服务系统的对接。
* 拥有机器人和频道功能。 * 拥有机器人和公众号功能。
* 代码开源率高,二次开发简单。可以先试用3到6个月,二开搞定后再购买。 * 社区版IM服务和移动端免费商用,可以免费构建出完备的移动互联网应用。
* 收费项目非常便宜,野火独有的试用模式,可以先部署到客户环境试用6个月,试用满意后再购买,避免上当受骗。
* 技术支持专业和高效,核心研发人员及时处理issue和论坛回复。
## 野火开源项目 ## 野火开源项目
主要包括以下项目: 主要包括以下项目:
| [GitHub仓库地址](https://github.com/wildfirechat) | [码云仓库地址](https://gitee.com/wfchat) | 说明 | [GitHub仓库地址](https://github.com/wildfirechat) | [码云仓库地址](https://gitee.com/wfchat) | 说明
| ------------------------------------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------- | --------------------------------------------------------------------------
| [im-server](https://github.com/wildfirechat/im-server) | [im-server](https://gitee.com/wfchat/im-server) | 野火社区版IM服务,野火IM的核心服务,处理所有IM相关业务。 | | [im-server](https://github.com/wildfirechat/im-server) | [im-server](https://gitee.com/wfchat/im-server) | 野火社区版IM服务,野火IM的核心服务,处理所有IM相关业务。 |
| [app_server](https://github.com/wildfirechat/app_server) | [app_server](https://gitee.com/wfchat/app_server) | Demo应用服务,模拟客户的应用服登陆处理逻辑及部分二次开发示例。 | | [app-server](https://github.com/wildfirechat/app-server) | [app-server](https://gitee.com/wfchat/app-server) | Demo应用服务,模拟客户的应用服登陆处理逻辑及部分二次开发示例。 |
| [robot_server](https://github.com/wildfirechat/robot_server) | [robot_server](https://gitee.com/wfchat/robot_server) | 机器人服务,演示野火机器人对接其它系统的方法。 | | [robot_server](https://github.com/wildfirechat/robot_server) | [robot_server](https://gitee.com/wfchat/robot_server) | 机器人服务,演示野火机器人对接其它系统的方法。 |
| [push_server](https://github.com/wildfirechat/push_server) | [push_server](https://gitee.com/wfchat/push_server) | 推送服务器,可以对接所有的系统厂商推送服务或者第三方推送服务。 | | [push_server](https://github.com/wildfirechat/push_server) | [push_server](https://gitee.com/wfchat/push_server) | 推送服务器,可以对接所有的系统厂商推送服务或者第三方推送服务。 |
| [wf-minio](https://github.com/wildfirechat/WF-minio) | [wf-minio](https://gitee.com/wfchat/WF-minio) | 私有对象存储服务,用来支持野火IM专业版的文件存储。 | | [wf-minio](https://github.com/wildfirechat/WF-minio) | [wf-minio](https://gitee.com/wfchat/WF-minio) | 私有对象存储服务,用来支持野火IM专业版的文件存储。 |
| [wf-janus](https://github.com/wildfirechat/wf-janus ) | [wf-janus](https://gitee.com/wfchat/wf-janus ) | 高级音视频媒体服务。 | | [wf-janus](https://github.com/wildfirechat/wf-janus ) | [wf-janus](https://gitee.com/wfchat/wf-janus ) | 高级音视频媒体服务。 |
| [open-platform](https://github.com/wildfirechat/open-platform) | [open-platform](https://gitee.com/wfchat/open-platform) | 野火开放平台服务。 | | [open-platform](https://github.com/wildfirechat/open-platform) | [open-platform](https://gitee.com/wfchat/open-platform) | 野火开放平台服务。 |
| [daily-report](https://github.com/wildfirechat/daily-report) | [daily-report](https://github.com/wildfirechat/daily-report) | 野火开放平台日报 demo服务。 | | [daily-report](https://github.com/wildfirechat/daily-report) | [daily-report](https://github.com/wildfirechat/daily-report) | 野火开放平台日报 demo服务。 |
| [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码。 | [channel-platform](https://github.com/wildfirechat/channel-platform) | [channel-platform](https://gitee.com/wfchat/channel-platform) | 野火频道(公众号)平台服务。 |
| [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS SDK源码和App源码。 | [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码。 |
| [pc-chat](https://github.com/wildfirechat/vue-pc-chat) | [pc-chat](https://gitee.com/wfchat/vue-pc-chat) | 基于[Electron](https://electronjs.org/)的PC 端,支持Windows、Mac、Linux(包括国产化linux系统和CPU)。 | | [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS SDK源码和App源码。|
| [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](https://web.wildfirechat.cn) | | [pc-chat](https://github.com/wildfirechat/vue-pc-chat) | [pc-chat](https://gitee.com/wfchat/vue-pc-chat) | 基于[Electron](https://electronjs.org/)的PC 端,支持Windows、Mac、Linux(包括国产化linux系统和CPU)。 |
| [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台) | | [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](https://web.wildfirechat.cn)。 |
| [uni-chat](https://github.com/wildfirechat/uni-chat) | [uni-chat](https://gitee.com/wfchat/uni-chat) | UniApp平台移动端应用(使用原生插件) | | [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台)。 |
| [uni-wfc-client](https://github.com/wildfirechat/uni-wfc-client) | [uni-wfc-client](https://gitee.com/wfchat/uni-wfc-client) | UniApp平台原生插件 | | [uni-chat](https://github.com/wildfirechat/uni-chat) | [uni-chat](https://gitee.com/wfchat/uni-chat) | UniApp平台移动端应用(使用原生插件)。 |
| [flutter_imclient](https://github.com/wildfirechat/flutter_imclient) | [flutter_imclient](https://gitee.com/wfchat/flutter_imclient) | Flutter平台原生插件 | | [uni-wfc-client](https://github.com/wildfirechat/uni-wfc-client) | [uni-wfc-client](https://gitee.com/wfchat/uni-wfc-client) | UniApp平台原生插件。 |
| [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.wildfirechat.cn/) | | [flutter_imclient](https://github.com/wildfirechat/flutter_imclient) | [flutter_imclient](https://gitee.com/wfchat/flutter_imclient) | Flutter平台原生插件。 |
| [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.wildfirechat.cn/)。 |
## 野火开发文档 ## 野火开发文档
[在线文档](https://docs.wildfirechat.cn/) [在线文档](https://docs.wildfirechat.cn/)
## 野火性能测试报告及测试方法
[Github](https://github.com/wildfirechat/Performance_Test), [码云](https://gitee.com/wfchat/Performance_Test)。
## 野火Demo ## 野火Demo
请使用微信扫码下载安装体验野火IM移动客户端 请使用微信扫码下载安装体验野火IM移动客户端
@@ -60,6 +68,10 @@ PC客户端点[这里](https://github.com/wildfirechat/vue-pc-chat/releases)下
![野火IM](http://static.wildfirechat.net/wx.jpg) ![野火IM](http://static.wildfirechat.net/wx.jpg)
## 快速使用
可以按照[快速开始](https://docs.wildfirechat.cn/quick_start/)来部署应用服务和IM服务,然后打包移动端,就可以完成聊天和通话功能。
之后再部署开放平台服务、频道(公众号)服务、机器人服务、推送服务、Turn服务(音视频服务)来实现完整的应用功能,详情可以详细阅读文档。
## 应用截图 ## 应用截图
![ios-demo](http://static.wildfirechat.cn/ios-demo.gif) ![ios-demo](http://static.wildfirechat.cn/ios-demo.gif)
@@ -86,46 +98,56 @@ PC客户端点[这里](https://github.com/wildfirechat/vue-pc-chat/releases)下
## 编译 ## 编译
在安装JDK1.8以上及maven的前提下,在命令行中执行```mvn clean package```,生成的目标文件在```./distribution/target/distribution-xxxx-bundle-tar.tar.gz``` 由于```distribution/pom.xml```中使用了生成RPM包的plugin,所以需要本地安装有rpm。如果不需要RPM包,可以删除掉```distribution/pom.xml```文件中的RPM plugin。
> 由于使用了一个git的maven插件,如果本地没有git信息就会编译出错,请使用```git clone```的方法下载代码,或者下载压缩包解压后在根目录创建```.git```的空目录。建议用```git clone```的方式下载代码。
在安装RPM后或者删除```distribution/pom.xml```文件中的RPM plugin后,执行下面命令:
```
mvn clean package
```
生成的目标文件在```./distribution/target/distribution-xxxx-bundle-tar.tar.gz```
## 配置 ## 配置
解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```。修改如下部分: 解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```,可以阅读配置文件注释和文档,对其它配置项进行调整。
1. 修改```server.ip```为您的服务器的公网IP,或者域名。如果用域名需要做好域名解析。
2. 可以阅读配置文件注释和文档,对其它配置项进行调整。
## 运行 ## 运行
在程序目录运行如下命令: 在解压```distribution-xxxx-bundle-tar.tar.gz```后的目录下运行如下命令:
``` ```
./bin/wildfirechat.sh ./bin/wildfirechat.sh
``` ```
## 验证 ## 验证
1. 在浏览器中输入地址 ```http://${ip}/api/version```可以看到返回一个json文件。 1. 在浏览器中输入地址 ```http://${ip}/api/version```可以看到返回一个json文件。
2. 部署[应用服务](应用服务),配置和编译[Android客户端](https://github.com/wildfirechat/android-chat)和[iOS客户端](https://github.com/wildfirechat/ios-chat)进行验证。详情可参考[快速开始](https://docs.wildfirechat.cn/quick_start/)。 2. 部署[应用服务](https://github.com/wildfirechat/app-server),配置和编译[Android客户端](https://github.com/wildfirechat/android-chat)和[iOS客户端](https://github.com/wildfirechat/ios-chat)进行验证。详情可参考[快速开始](https://docs.wildfirechat.cn/quick_start/)。
## 升级说明 ## 升级说明
1. 从0.42 版本增加了群成员数限制,默认为2000。如果您从之前的版本升级到这个版本或以后,需要注意到群成员数的限制。升级之后超出限制的群不受影响,但不能继续加人,如果您想修改默认值,可以在升级版本之后,修改t_setting表,把默认的大小改为您期望的人数。另外修改t_group表,把已经存在的群组max_member_count改成您期望的,然后重启。 1. 从0.42 版本增加了群成员数限制,默认为2000。如果您从之前的版本升级到这个版本或以后,需要注意到群成员数的限制。升级之后超出限制的群不受影响,但不能继续加人,如果您想修改默认值,可以在升级版本之后,修改t_setting表,把默认的大小改为您期望的人数。另外修改t_group表,把已经存在的群组max_member_count改成您期望的,然后重启。
2. 0.46和0.47版本升级到0.48及以后版本时,可能会提示flyway migrate 38错误,请执行 [修复脚本](https://github.com/wildfirechat/server/blob/wildfirechat/flyway_repaire_migrate_38.sql) 进行修复。0.46和0.47版本之外的版本不会出现此问题。 2. 0.50版本添加了是否允许客户端发送群操作通知的配置。如果您在客户端自定义群通知,需要在服务器端配置允许,没有使用自定义群操作通知的不受影响。***
3. 从0.54之前版本升级到0.54及以后版本时,会提示flyway migrate错误。因为0.54版本删除了sql脚本中默认敏感词的内容,flyway checksum失败。请执行```update flyway_schema_history set checksum = 0 where script = 'V17__add_default_sensitive_word.sql';```来修复。 3. 0.46和0.47版本升级到0.48及以后版本时,可能会提示flyway migrate 38错误,请执行 [修复脚本](https://github.com/wildfirechat/server/blob/wildfirechat/flyway_repaire_migrate_38.sql) 进行修复。0.46和0.47版本之外的版本不会出现此问题。
4. 从0.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。 4. 从0.54之前版本升级到0.54及以后版本时,会提示flyway migrate错误。因为0.54版本删除了sql脚本中默认敏感词的内容,flyway checksum失败。请执行```update flyway_schema_history set checksum = 0 where script = 'V17__add_default_sensitive_word.sql';```来修复。
5. 从0.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。
6. 0.62/0.63 版本有严重的问题,请使用0.64及以后版本,或者0.61版。
7. 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端)
8. 从0.78 版本起把MySQL数据库中关键字都改为大小写敏感,另外生成id的方法也做了改变,只生成小写的id,避免出现id重复的问题,建议所有客户都升级
9. 从0.79 版本起把log4j升级到log4j2,因为log4j已经不再维护而且还有已知的漏洞,建议所有客户都升级,升级时注意更新log4j2的配置文件
10. 0.97版本更改了启动脚本```wildfirechat.sh```,如果是升级服务,请注意更新启动脚本。
## 联系我们 ## 联系我们
商务合作请使用如下邮箱和微信联系: 商务合作请微信联系:
邮箱: support@wildfirechat.cn 微信1:wfchat 微信2:wildfirechat * 微信1:wildfirechat
* 微信2:wfchat
## 问题交流 ## 问题交流
1. 如果大家发现bug,请在GitHub提issue;如果有需求也请给我们提issue。 1. 如果大家发现bug,请在GitHub或码云提issue;如果有需求也请给我们提issue。
2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习 2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习
3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。 3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。
<img src="http://static.wildfirechat.cn/wx_wfc_qrcode.jpg" width = 50% height = 50% /> <img src="http://static.wildfirechat.cn/wx_wfc_qrcode.jpg" width = 50% height = 50% />
> 我们有研发工程师轮流值班处理issue和论坛,一般简单问题几个小时就会回复一遍,疑难Bug的修改和新需求的开发我们也会尽快解决。 我们有核心研发工程师轮流值班处理issue和论坛,会及时处理的,疑难Bug的修改和新需求的开发我们也会尽快解决。
## 特别感谢 ## 特别感谢
1. [moquette](https://github.com/moquette-io/moquette) 本项目是基于此项目二次开发而来,处理MQTT相关业务。 1. [moquette](https://github.com/moquette-io/moquette) 本项目是基于此项目二次开发而来,处理MQTT相关业务。
-3
View File
@@ -1,3 +0,0 @@
#native2ascii -encoding UTF-8 messages.properties temp_messages.properties
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
@@ -1,2 +0,0 @@
Above_Greeting_Message=This's the greeting
Friend_Can_Start_Chat=You're already friend, start chat now.
@@ -1,2 +0,0 @@
Above_Greeting_Message=This's the greeting
Friend_Can_Start_Chat=You're already friend, start chat now.
@@ -1,2 +0,0 @@
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
@@ -1,2 +0,0 @@
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
+3 -11
View File
@@ -42,6 +42,9 @@ embed.db 1
## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。 ## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。
db.auto_clean_history_messages true db.auto_clean_history_messages true
## h2db数据库的路径,默认为程序目录下的,可以指定其他目录。如果使用MySQL,可以忽略此配置
h2db.path ./h2db/wfchat
##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。 ##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。
http.admin.secret_key 123456 http.admin.secret_key 123456
@@ -139,12 +142,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。 ##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000 friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出 ## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000 chatroom.participant_idle_time 900000
@@ -190,11 +187,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。 ##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100 client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#********************************************************************* #*********************************************************************
# Media server configuration # Media server configuration
#********************************************************************* #*********************************************************************
+4 -4
View File
@@ -1,11 +1,11 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" <?xml version="1.0" encoding="UTF-8"?>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<relativePath>../</relativePath> <relativePath>../</relativePath>
<artifactId>wildfirechat-parent</artifactId> <artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId> <groupId>cn.wildfirechat</groupId>
<version>0.94</version> <version>0.97</version>
</parent> </parent>
<artifactId>moquette-broker</artifactId> <artifactId>moquette-broker</artifactId>
@@ -120,7 +120,7 @@
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>1.10</version> <version>1.13</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -112,7 +112,7 @@ abstract public class AdminAction extends Action {
} }
protected void sendApiMessage(Response response, String fromUser, String clientId, String topic, byte[] message, ApiCallback callback, boolean noAdmin) { protected void sendApiMessage(Response response, String fromUser, String clientId, String topic, byte[] message, ApiCallback callback, boolean noAdmin) {
ServerAPIHelper.sendRequest(fromUser, clientId, topic, message, new ServerAPIHelper.Callback() { ServerAPIHelper.sendRequest(fromUser, clientId, topic, message, callback == null ? null : new ServerAPIHelper.Callback() {
@Override @Override
public void onSuccess(byte[] result) { public void onSuccess(byte[] result) {
if(callback != null) { if(callback != null) {
@@ -81,18 +81,6 @@ public class Request {
// request URI parameters // request URI parameters
this.putParams(new QueryStringDecoder(uri)); this.putParams(new QueryStringDecoder(uri));
if(nettyRequest.method() != HttpMethod.GET){
HttpPostRequestDecoder decoder = null;
try {
decoder = new HttpPostRequestDecoder(HTTP_DATA_FACTORY, nettyRequest);
this.putParams(decoder);
} finally {
if(null != decoder){
decoder.destroy();
decoder = null;
}
}
}
// IP // IP
this.putIp(ctx); this.putIp(ctx);
@@ -112,6 +112,8 @@ public final class BrokerConstants {
public static final String EMBED_DB_PROPERTY_NAME = "embed.db"; public static final String EMBED_DB_PROPERTY_NAME = "embed.db";
public static final String DB_AUTO_CLEAN_HISTORY_MESSAGES = "db.auto_clean_history_messages"; public static final String DB_AUTO_CLEAN_HISTORY_MESSAGES = "db.auto_clean_history_messages";
public static final String H2DB_PATH = "h2db.path";
public static final String SENSITIVE_Filter_Type = "sensitive.filter.type"; public static final String SENSITIVE_Filter_Type = "sensitive.filter.type";
@@ -154,7 +156,6 @@ public final class BrokerConstants {
public static final String FRIEND_Repeat_Request_Duration = "friend.repeat_request_duration"; public static final String FRIEND_Repeat_Request_Duration = "friend.repeat_request_duration";
public static final String FRIEND_Reject_Request_Duration = "friend.reject_request_duration"; public static final String FRIEND_Reject_Request_Duration = "friend.reject_request_duration";
public static final String FRIEND_Request_Expiration_Duration = "friend.request_expiration_duration"; public static final String FRIEND_Request_Expiration_Duration = "friend.request_expiration_duration";
public static final String FRIEND_New_Welcome_Message = "friend.new_welcome_message";
public static final String CHATROOM_Participant_Idle_Time = "chatroom.participant_idle_time"; public static final String CHATROOM_Participant_Idle_Time = "chatroom.participant_idle_time";
public static final String CHATROOM_Rejoin_When_Active = "chatroom.rejoin_when_active"; public static final String CHATROOM_Rejoin_When_Active = "chatroom.rejoin_when_active";
@@ -10,12 +10,10 @@ package io.moquette.imhandler;
import cn.wildfirechat.proto.ProtoConstants; import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage; import cn.wildfirechat.proto.WFCMessage;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.spi.impl.Qos1PublishHandler; import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import cn.wildfirechat.common.ErrorCode; import cn.wildfirechat.common.ErrorCode;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import win.liyufan.im.I18n;
import win.liyufan.im.IMTopic; import win.liyufan.im.IMTopic;
import win.liyufan.im.MessageShardingUtil; import win.liyufan.im.MessageShardingUtil;
@@ -43,17 +41,8 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType); saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType);
} }
MemorySessionStore.Session session = m_sessionsStore.getSession(clientID);
String language = "zh_CN";
if (session != null && !StringUtil.isNullOrEmpty(session.getLanguage())) {
language = session.getLanguage();
}
WFCMessage.MessageContent.Builder contentBuilder = WFCMessage.MessageContent.newBuilder(); WFCMessage.MessageContent.Builder contentBuilder = WFCMessage.MessageContent.newBuilder();
if (m_messagesStore.isNewFriendWelcomeMessage()) { contentBuilder.setType(92);
contentBuilder.setType(92);
} else {
contentBuilder.setType(90).setContent(I18n.getString(language, "Above_Greeting_Message"));
}
builder = WFCMessage.Message.newBuilder(); builder = WFCMessage.Message.newBuilder();
builder.setFromUser(request.getTargetUid()); builder.setFromUser(request.getTargetUid());
@@ -64,13 +53,7 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
messageId = MessageShardingUtil.generateId(); messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId); builder.setMessageId(messageId);
saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType); saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType);
contentBuilder.setType(93);
if (m_messagesStore.isNewFriendWelcomeMessage()) {
contentBuilder.setType(93);
} else {
contentBuilder.setContent(I18n.getString(language, "Friend_Can_Start_Chat"));
}
builder.setContent(contentBuilder); builder.setContent(contentBuilder);
messageId = MessageShardingUtil.generateId(); messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId); builder.setMessageId(messageId);
@@ -72,7 +72,7 @@ public class InterceptConnectMessage extends InterceptAbstractMessage {
} }
public byte[] getPassword() { public byte[] getPassword() {
return msg.payload().password(); return msg.payload().passwordInBytes();
} }
public String getWillTopic() { public String getWillTopic() {
@@ -138,7 +138,6 @@ public class MemoryMessagesStore implements IMessagesStore {
private long mFriendRequestDuration = 7 * 24 * 60 * 60 * 1000; private long mFriendRequestDuration = 7 * 24 * 60 * 60 * 1000;
private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000; private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000;
private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000; private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000;
private boolean mFriendNewWelcomeMessage = true;
private boolean mMultiPlatformNotification = false; private boolean mMultiPlatformNotification = false;
private boolean mMobileDefaultSilentWhenPCOnline = true; private boolean mMobileDefaultSilentWhenPCOnline = true;
@@ -256,15 +255,6 @@ public class MemoryMessagesStore implements IMessagesStore {
printMissConfigLog(FRIEND_Request_Expiration_Duration, mFriendRequestExpiration + ""); printMissConfigLog(FRIEND_Request_Expiration_Duration, mFriendRequestExpiration + "");
} }
try {
mFriendNewWelcomeMessage = Boolean.parseBoolean(m_Server.getConfig().getProperty(FRIEND_New_Welcome_Message));
} catch (Exception e) {
e.printStackTrace();
Utility.printExecption(LOG, e);
printMissConfigLog(FRIEND_New_Welcome_Message, mFriendNewWelcomeMessage + "");
}
try { try {
mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active)); mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active));
} catch (Exception e) { } catch (Exception e) {
@@ -539,8 +529,17 @@ public class MemoryMessagesStore implements IMessagesStore {
int pullType = ProtoConstants.PullType.Pull_Normal; int pullType = ProtoConstants.PullType.Pull_Normal;
if (type == ProtoConstants.ConversationType.ConversationType_Private) { if (type == ProtoConstants.ConversationType.ConversationType_Private) {
notifyReceivers.add(fromUser); if(message.getToCount() > 0) {
notifyReceivers.add(message.getConversation().getTarget()); if(message.getToList().contains(fromUser)) {
notifyReceivers.add(fromUser);
}
if(message.getToList().contains(message.getConversation().getTarget())) {
notifyReceivers.add(message.getConversation().getTarget());
}
} else {
notifyReceivers.add(fromUser);
notifyReceivers.add(message.getConversation().getTarget());
}
pullType = ProtoConstants.PullType.Pull_Normal; pullType = ProtoConstants.PullType.Pull_Normal;
} else if (type == ProtoConstants.ConversationType.ConversationType_Group) { } else if (type == ProtoConstants.ConversationType.ConversationType_Group) {
notifyReceivers.add(fromUser); notifyReceivers.add(fromUser);
@@ -3363,10 +3362,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) { if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) {
try { try {
URL url = new URL(robotData.getCallback()); URL url = new URL(robotData.getCallback());
if(url.getHost().equals(host)) { if(url.getHost() == null) {
LOG.error("get application auth code error, application callback is invalid url");
} else if(url.getHost().equals(host)) {
secret = robotData.getSecret(); secret = robotData.getSecret();
} else { } else {
LOG.warn("get application auth code error, request host is not the same host with callback host"); LOG.error("get application auth code error, request host is not the same host with callback host");
} }
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
@@ -3387,10 +3388,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) { if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) {
try { try {
URL url = new URL(channelData.getCallback()); URL url = new URL(channelData.getCallback());
if(url.getHost().equals(host)) { if(url.getHost() == null) {
LOG.error("get application auth code error, application callback is invalid url");
} else if(url.getHost().equals(host)) {
secret = channelData.getSecret(); secret = channelData.getSecret();
} else { } else {
LOG.warn("get application auth code error, request host is not the same host with callback host"); LOG.error("get application auth code error, request host is not the same host with callback host");
} }
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
@@ -3496,11 +3499,6 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_AUTH_FAILURE; return ErrorCode.ERROR_CODE_AUTH_FAILURE;
} }
@Override
public boolean isNewFriendWelcomeMessage() {
return mFriendNewWelcomeMessage;
}
@Override @Override
public ErrorCode deleteFriend(String userId, String friendUid, long[] head) { public ErrorCode deleteFriend(String userId, String friendUid, long[] head) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
@@ -415,7 +415,7 @@ public class MemorySessionStore implements ISessionsStore {
remove = true; remove = true;
} }
} else if(platform == ProtoConstants.Platform.Platform_OSX || platform == ProtoConstants.Platform.Platform_Windows || platform == ProtoConstants.Platform.Platform_LINUX) { } else if(platform == ProtoConstants.Platform.Platform_OSX || platform == ProtoConstants.Platform.Platform_Windows || platform == ProtoConstants.Platform.Platform_LINUX) {
if (s.getPlatform() == ProtoConstants.Platform.Platform_OSX || s.getPlatform() == ProtoConstants.Platform.Platform_Windows || platform == ProtoConstants.Platform.Platform_LINUX) { if (s.getPlatform() == ProtoConstants.Platform.Platform_OSX || s.getPlatform() == ProtoConstants.Platform.Platform_Windows || s.getPlatform() == ProtoConstants.Platform.Platform_LINUX) {
remove = true; remove = true;
} }
} else { } else {
@@ -378,7 +378,7 @@ public class Server {
LOG.info("Configuring embedded Hazelcast instance"); LOG.info("Configuring embedded Hazelcast instance");
serverIp = getServerIp(config); serverIp = getServerIp(config);
String hzConfigPath = "config/hazelcast.xml"; String hzConfigPath = System.getProperty("hazelcast.configuration", "config/hazelcast.xml");
if (hzConfigPath != null) { if (hzConfigPath != null) {
boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null; boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null;
Config hzconfig = isHzConfigOnClasspath Config hzconfig = isHzConfigOnClasspath
@@ -179,7 +179,6 @@ public interface IMessagesStore {
ErrorCode saveAddFriendRequest(String userId, WFCMessage.AddFriendRequest request, long[] head, boolean isAdmin); ErrorCode saveAddFriendRequest(String userId, WFCMessage.AddFriendRequest request, long[] head, boolean isAdmin);
ErrorCode handleFriendRequest(String userId, WFCMessage.HandleFriendRequest request, WFCMessage.Message.Builder msgBuilder, long[] heads, boolean isAdmin); ErrorCode handleFriendRequest(String userId, WFCMessage.HandleFriendRequest request, WFCMessage.Message.Builder msgBuilder, long[] heads, boolean isAdmin);
boolean isNewFriendWelcomeMessage();
ErrorCode deleteFriend(String userId, String friendUid, long[] head); ErrorCode deleteFriend(String userId, String friendUid, long[] head);
ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head); ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head);
FriendData getFriendData(String fromUser, String targetUserId); FriendData getFriendData(String fromUser, String targetUserId);
@@ -652,6 +652,8 @@ public class MessagesPublisher {
pushContent = "[文件]"; pushContent = "[文件]";
} else if(type == ProtoConstants.ContentType.Sticker) { } else if(type == ProtoConstants.ContentType.Sticker) {
pushContent = "[表情]"; pushContent = "[表情]";
} else if (type == ProtoConstants.ContentType.Name_Card) {
pushContent = "[名片]";
} }
} }
@@ -308,7 +308,7 @@ public class ProtocolProcessor {
} }
byte[] pwd = null; byte[] pwd = null;
if (msg.variableHeader().hasPassword()) { if (msg.variableHeader().hasPassword()) {
pwd = msg.payload().password(); pwd = msg.payload().passwordInBytes();
MemorySessionStore.Session session = m_sessionsStore.getSession(clientId); MemorySessionStore.Session session = m_sessionsStore.getSession(clientId);
if (session == null) { if (session == null) {
@@ -164,7 +164,7 @@ public class Qos1PublishHandler extends QosPublishHandler {
} else { } else {
MemorySessionStore.Session session = m_sessionStore.getSession(clientID); MemorySessionStore.Session session = m_sessionStore.getSession(clientID);
if (session != null && session.getUsername().equals(fromUser)) { if (session != null && session.getUsername().equals(fromUser)) {
if (data.length > 7*1024 && session.getMqttVersion().protocolLevel() >= MqttVersion.Wildfire_1.protocolLevel()) { if (data.length > 7*1024 && session.getMqttVersion().protocolLevel() >= MqttVersion.MQTT_5.protocolLevel()) {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = null; GZIPOutputStream gzip = null;
try { try {
+32 -9
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,13 +16,30 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.DecoderException;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
final class MqttCodecUtil { final class MqttCodecUtil {
private static final char[] TOPIC_WILDCARDS = {'#', '+'}; private static final char[] TOPIC_WILDCARDS = {'#', '+'};
private static final int MIN_CLIENT_ID_LENGTH = 1;
private static final int MAX_CLIENT_ID_LENGTH = 23; static final AttributeKey<MqttVersion> MQTT_VERSION_KEY = AttributeKey.valueOf("NETTY_CODEC_MQTT_VERSION");
static MqttVersion getMqttVersion(ChannelHandlerContext ctx) {
Attribute<MqttVersion> attr = ctx.channel().attr(MQTT_VERSION_KEY);
MqttVersion version = attr.get();
if (version == null) {
return MqttVersion.MQTT_3_1_1;
}
return version;
}
static void setMqttVersion(ChannelHandlerContext ctx, MqttVersion version) {
Attribute<MqttVersion> attr = ctx.channel().attr(MQTT_VERSION_KEY);
attr.set(version);
}
static boolean isValidPublishTopicName(String topicName) { static boolean isValidPublishTopicName(String topicName) {
// publish topic name must not contain any wildcard // publish topic name must not contain any wildcard
@@ -38,20 +55,20 @@ final class MqttCodecUtil {
return messageId != 0; return messageId != 0;
} }
static boolean isValidClientId(MqttVersion mqttVersion, String clientId) { static boolean isValidClientId(MqttVersion mqttVersion, int maxClientIdLength, String clientId) {
if (mqttVersion == MqttVersion.MQTT_3_1) { if (mqttVersion == MqttVersion.MQTT_3_1) {
return clientId != null && clientId.length() >= MIN_CLIENT_ID_LENGTH && return clientId != null && clientId.length() >= MqttConstant.MIN_CLIENT_ID_LENGTH &&
clientId.length() <= MAX_CLIENT_ID_LENGTH; clientId.length() <= maxClientIdLength;
} }
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) { if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) {
// In 3.1.3.1 Client Identifier of MQTT 3.1.1 specification, The Server MAY allow ClientId’s // In 3.1.3.1 Client Identifier of MQTT 3.1.1 and 5.0 specifications, The Server MAY allow ClientId’s
// that contain more than 23 encoded bytes. And, The Server MAY allow zero-length ClientId. // that contain more than 23 encoded bytes. And, The Server MAY allow zero-length ClientId.
return clientId != null; return clientId != null;
} }
throw new IllegalArgumentException(mqttVersion + " is unknown mqtt version"); throw new IllegalArgumentException(mqttVersion + " is unknown mqtt version");
} }
static MqttFixedHeader validateFixedHeader(MqttFixedHeader mqttFixedHeader) { static MqttFixedHeader validateFixedHeader(ChannelHandlerContext ctx, MqttFixedHeader mqttFixedHeader) {
switch (mqttFixedHeader.messageType()) { switch (mqttFixedHeader.messageType()) {
case PUBREL: case PUBREL:
case SUBSCRIBE: case SUBSCRIBE:
@@ -59,6 +76,12 @@ final class MqttCodecUtil {
if (mqttFixedHeader.qosLevel() != MqttQoS.AT_LEAST_ONCE) { if (mqttFixedHeader.qosLevel() != MqttQoS.AT_LEAST_ONCE) {
throw new DecoderException(mqttFixedHeader.messageType().name() + " message must have QoS 1"); throw new DecoderException(mqttFixedHeader.messageType().name() + " message must have QoS 1");
} }
return mqttFixedHeader;
case AUTH:
if (MqttCodecUtil.getMqttVersion(ctx) != MqttVersion.MQTT_5) {
throw new DecoderException("AUTH message requires at least MQTT 5");
}
return mqttFixedHeader;
default: default:
return mqttFixedHeader; return mqttFixedHeader;
} }
@@ -75,7 +98,7 @@ final class MqttCodecUtil {
case UNSUBACK: case UNSUBACK:
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
//case DISCONNECT: case DISCONNECT:
if (mqttFixedHeader.isDup() || if (mqttFixedHeader.isDup() ||
mqttFixedHeader.qosLevel() != MqttQoS.AT_MOST_ONCE || mqttFixedHeader.qosLevel() != MqttQoS.AT_MOST_ONCE ||
mqttFixedHeader.isRetain()) { mqttFixedHeader.isRetain()) {
+2 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connack">MQTTV3.1/connack</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connack">MQTTV3.1/connack</a>
*/ */
public final class MqttConnAckMessage extends MqttMessage { public final class MqttConnAckMessage extends MqttMessage {
+13 -1
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -27,9 +27,17 @@ public final class MqttConnAckVariableHeader {
private final boolean sessionPresent; private final boolean sessionPresent;
private final MqttProperties properties;
public MqttConnAckVariableHeader(MqttConnectReturnCode connectReturnCode, boolean sessionPresent) { public MqttConnAckVariableHeader(MqttConnectReturnCode connectReturnCode, boolean sessionPresent) {
this(connectReturnCode, sessionPresent, MqttProperties.NO_PROPERTIES);
}
public MqttConnAckVariableHeader(MqttConnectReturnCode connectReturnCode, boolean sessionPresent,
MqttProperties properties) {
this.connectReturnCode = connectReturnCode; this.connectReturnCode = connectReturnCode;
this.sessionPresent = sessionPresent; this.sessionPresent = sessionPresent;
this.properties = MqttProperties.withEmptyDefaults(properties);
} }
public MqttConnectReturnCode connectReturnCode() { public MqttConnectReturnCode connectReturnCode() {
@@ -40,6 +48,10 @@ public final class MqttConnAckVariableHeader {
return sessionPresent; return sessionPresent;
} }
public MqttProperties properties() {
return properties;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
+2 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connect">MQTTV3.1/connect</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connect">MQTTV3.1/connect</a>
*/ */
public final class MqttConnectMessage extends MqttMessage { public final class MqttConnectMessage extends MqttMessage {
+65 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,6 +16,9 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import java.util.Arrays;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
/** /**
@@ -24,18 +27,55 @@ import io.netty.util.internal.StringUtil;
public final class MqttConnectPayload { public final class MqttConnectPayload {
private final String clientIdentifier; private final String clientIdentifier;
private final MqttProperties willProperties;
private final String willTopic; private final String willTopic;
private final String willMessage; private final byte[] willMessage;
private final String userName; private final String userName;
private final byte[] password; private final byte[] password;
/**
* @deprecated use {@link MqttConnectPayload#MqttConnectPayload(String,
* MqttProperties, String, byte[], String, byte[])} instead
*/
@Deprecated
public MqttConnectPayload( public MqttConnectPayload(
String clientIdentifier, String clientIdentifier,
String willTopic, String willTopic,
String willMessage, String willMessage,
String userName, String userName,
String password) {
this(
clientIdentifier,
MqttProperties.NO_PROPERTIES,
willTopic,
willMessage.getBytes(CharsetUtil.UTF_8),
userName,
password.getBytes(CharsetUtil.UTF_8));
}
public MqttConnectPayload(
String clientIdentifier,
String willTopic,
byte[] willMessage,
String userName,
byte[] password) {
this(clientIdentifier,
MqttProperties.NO_PROPERTIES,
willTopic,
willMessage,
userName,
password);
}
public MqttConnectPayload(
String clientIdentifier,
MqttProperties willProperties,
String willTopic,
byte[] willMessage,
String userName,
byte[] password) { byte[] password) {
this.clientIdentifier = clientIdentifier; this.clientIdentifier = clientIdentifier;
this.willProperties = MqttProperties.withEmptyDefaults(willProperties);
this.willTopic = willTopic; this.willTopic = willTopic;
this.willMessage = willMessage; this.willMessage = willMessage;
this.userName = userName; this.userName = userName;
@@ -46,11 +86,23 @@ public final class MqttConnectPayload {
return clientIdentifier; return clientIdentifier;
} }
public MqttProperties willProperties() {
return willProperties;
}
public String willTopic() { public String willTopic() {
return willTopic; return willTopic;
} }
/**
* @deprecated use {@link MqttConnectPayload#willMessageInBytes()} instead
*/
@Deprecated
public String willMessage() { public String willMessage() {
return willMessage == null ? null : new String(willMessage, CharsetUtil.UTF_8);
}
public byte[] willMessageInBytes() {
return willMessage; return willMessage;
} }
@@ -58,7 +110,15 @@ public final class MqttConnectPayload {
return userName; return userName;
} }
public byte[] password() { /**
* @deprecated use {@link MqttConnectPayload#passwordInBytes()} instead
*/
@Deprecated
public String password() {
return password == null ? null : new String(password, CharsetUtil.UTF_8);
}
public byte[] passwordInBytes() {
return password; return password;
} }
@@ -68,9 +128,9 @@ public final class MqttConnectPayload {
.append('[') .append('[')
.append("clientIdentifier=").append(clientIdentifier) .append("clientIdentifier=").append(clientIdentifier)
.append(", willTopic=").append(willTopic) .append(", willTopic=").append(willTopic)
.append(", willMessage=").append(willMessage) .append(", willMessage=").append(Arrays.toString(willMessage))
.append(", userName=").append(userName) .append(", userName=").append(userName)
.append(", password=").append(password) .append(", password=").append(Arrays.toString(password))
.append(']') .append(']')
.toString(); .toString();
} }
+42 -14
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,31 +16,52 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/** /**
* Return Code of {@link MqttConnAckMessage} * Return Code of {@link MqttConnAckMessage}
*/ */
public enum MqttConnectReturnCode { public enum MqttConnectReturnCode {
CONNECTION_ACCEPTED((byte) 0x00), CONNECTION_ACCEPTED((byte) 0x00),
//MQTT 3 codes
CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION((byte) 0X01), CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION((byte) 0X01),
CONNECTION_REFUSED_IDENTIFIER_REJECTED((byte) 0x02), CONNECTION_REFUSED_IDENTIFIER_REJECTED((byte) 0x02),
CONNECTION_REFUSED_SERVER_UNAVAILABLE((byte) 0x03), CONNECTION_REFUSED_SERVER_UNAVAILABLE((byte) 0x03),
CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD((byte) 0x04), CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD((byte) 0x04),
CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05), CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05),
CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06), CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06),
CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07); CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07),
//MQTT 5 codes
CONNECTION_REFUSED_UNSPECIFIED_ERROR((byte) 0x80),
CONNECTION_REFUSED_MALFORMED_PACKET((byte) 0x81),
CONNECTION_REFUSED_PROTOCOL_ERROR((byte) 0x82),
CONNECTION_REFUSED_IMPLEMENTATION_SPECIFIC((byte) 0x83),
CONNECTION_REFUSED_UNSUPPORTED_PROTOCOL_VERSION((byte) 0x84),
CONNECTION_REFUSED_CLIENT_IDENTIFIER_NOT_VALID((byte) 0x85),
CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD((byte) 0x86),
CONNECTION_REFUSED_NOT_AUTHORIZED_5((byte) 0x87),
CONNECTION_REFUSED_SERVER_UNAVAILABLE_5((byte) 0x88),
CONNECTION_REFUSED_SERVER_BUSY((byte) 0x89),
CONNECTION_REFUSED_BANNED((byte) 0x8A),
CONNECTION_REFUSED_BAD_AUTHENTICATION_METHOD((byte) 0x8C),
CONNECTION_REFUSED_TOPIC_NAME_INVALID((byte) 0x90),
CONNECTION_REFUSED_PACKET_TOO_LARGE((byte) 0x95),
CONNECTION_REFUSED_QUOTA_EXCEEDED((byte) 0x97),
CONNECTION_REFUSED_PAYLOAD_FORMAT_INVALID((byte) 0x99),
CONNECTION_REFUSED_RETAIN_NOT_SUPPORTED((byte) 0x9A),
CONNECTION_REFUSED_QOS_NOT_SUPPORTED((byte) 0x9B),
CONNECTION_REFUSED_USE_ANOTHER_SERVER((byte) 0x9C),
CONNECTION_REFUSED_SERVER_MOVED((byte) 0x9D),
CONNECTION_REFUSED_CONNECTION_RATE_EXCEEDED((byte) 0x9F);
private static final Map<Byte, MqttConnectReturnCode> VALUE_TO_CODE_MAP; private static final MqttConnectReturnCode[] VALUES;
static { static {
final Map<Byte, MqttConnectReturnCode> valueMap = new HashMap<Byte, MqttConnectReturnCode>(); MqttConnectReturnCode[] values = values();
for (MqttConnectReturnCode code: values()) { VALUES = new MqttConnectReturnCode[160];
valueMap.put(code.byteValue, code); for (MqttConnectReturnCode code : values) {
final int unsignedByte = code.byteValue & 0xFF;
// Suppress a warning about out of bounds access since the enum contains only correct values
VALUES[unsignedByte] = code; // lgtm [java/index-out-of-bounds]
} }
VALUE_TO_CODE_MAP = Collections.unmodifiableMap(valueMap);
} }
private final byte byteValue; private final byte byteValue;
@@ -54,9 +75,16 @@ public enum MqttConnectReturnCode {
} }
public static MqttConnectReturnCode valueOf(byte b) { public static MqttConnectReturnCode valueOf(byte b) {
if (VALUE_TO_CODE_MAP.containsKey(b)) { final int unsignedByte = b & 0xFF;
return VALUE_TO_CODE_MAP.get(b); MqttConnectReturnCode mqttConnectReturnCode = null;
try {
mqttConnectReturnCode = VALUES[unsignedByte];
} catch (ArrayIndexOutOfBoundsException ignored) {
// no op
} }
throw new IllegalArgumentException("unknown connect return code: " + (b & 0xFF)); if (mqttConnectReturnCode == null) {
throw new IllegalArgumentException("unknown connect return code: " + unsignedByte);
}
return mqttConnectReturnCode;
} }
} }
+30 -1
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -32,6 +32,7 @@ public final class MqttConnectVariableHeader {
private final boolean isWillFlag; private final boolean isWillFlag;
private final boolean isCleanSession; private final boolean isCleanSession;
private final int keepAliveTimeSeconds; private final int keepAliveTimeSeconds;
private final MqttProperties properties;
public MqttConnectVariableHeader( public MqttConnectVariableHeader(
String name, String name,
@@ -43,6 +44,29 @@ public final class MqttConnectVariableHeader {
boolean isWillFlag, boolean isWillFlag,
boolean isCleanSession, boolean isCleanSession,
int keepAliveTimeSeconds) { int keepAliveTimeSeconds) {
this(name,
version,
hasUserName,
hasPassword,
isWillRetain,
willQos,
isWillFlag,
isCleanSession,
keepAliveTimeSeconds,
MqttProperties.NO_PROPERTIES);
}
public MqttConnectVariableHeader(
String name,
int version,
boolean hasUserName,
boolean hasPassword,
boolean isWillRetain,
int willQos,
boolean isWillFlag,
boolean isCleanSession,
int keepAliveTimeSeconds,
MqttProperties properties) {
this.name = name; this.name = name;
this.version = version; this.version = version;
this.hasUserName = hasUserName; this.hasUserName = hasUserName;
@@ -52,6 +76,7 @@ public final class MqttConnectVariableHeader {
this.isWillFlag = isWillFlag; this.isWillFlag = isWillFlag;
this.isCleanSession = isCleanSession; this.isCleanSession = isCleanSession;
this.keepAliveTimeSeconds = keepAliveTimeSeconds; this.keepAliveTimeSeconds = keepAliveTimeSeconds;
this.properties = MqttProperties.withEmptyDefaults(properties);
} }
public String name() { public String name() {
@@ -90,6 +115,10 @@ public final class MqttConnectVariableHeader {
return keepAliveTimeSeconds; return keepAliveTimeSeconds;
} }
public MqttProperties properties() {
return properties;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
@@ -0,0 +1,39 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
public final class MqttConstant {
private MqttConstant() {
}
/**
* Default max bytes in message
*/
public static final int DEFAULT_MAX_BYTES_IN_MESSAGE = 262144; //256KB
/**
* min client id length
*/
public static final int MIN_CLIENT_ID_LENGTH = 1;
/**
* Default max client id length,In the mqtt3.1 protocol,
* the default maximum Client Identifier length is 23
*/
public static final int DEFAULT_MAX_CLIENT_ID_LENGTH = 23;
}
+436 -112
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -20,26 +20,22 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.ReplayingDecoder; import io.netty.handler.codec.ReplayingDecoder;
import io.netty.handler.codec.mqtt.MqttDecoder.DecoderState; import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import io.netty.util.internal.ObjectUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidClientId;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidMessageId;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidPublishTopicName;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.resetUnusedFields;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.validateFixedHeader;
/** /**
* Decodes Mqtt messages from bytes, following * Decodes Mqtt messages from bytes, following
* <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html"> * the MQTT protocol specification
* the MQTT protocol specification v3.1</a> * <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">v3.1</a>
* or
* <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html">v5.0</a>, depending on the
* version specified in the CONNECT message that first goes through the channel.
*/ */
public final class MqttDecoder extends ReplayingDecoder<DecoderState> { public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState> {
private static final int DEFAULT_MAX_BYTES_IN_MESSAGE = 262144; //256K
/** /**
* States of the decoder. * States of the decoder.
@@ -58,31 +54,42 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private int bytesRemainingInVariablePart; private int bytesRemainingInVariablePart;
private final int maxBytesInMessage; private final int maxBytesInMessage;
private final int maxClientIdLength;
public MqttDecoder() { public MqttDecoder() {
this(DEFAULT_MAX_BYTES_IN_MESSAGE); this(MqttConstant.DEFAULT_MAX_BYTES_IN_MESSAGE, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH);
} }
public MqttDecoder(int maxBytesInMessage) { public MqttDecoder(int maxBytesInMessage) {
this(maxBytesInMessage, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH);
}
public MqttDecoder(int maxBytesInMessage, int maxClientIdLength) {
super(DecoderState.READ_FIXED_HEADER); super(DecoderState.READ_FIXED_HEADER);
this.maxBytesInMessage = maxBytesInMessage; this.maxBytesInMessage = ObjectUtil.checkPositive(maxBytesInMessage, "maxBytesInMessage");
this.maxClientIdLength = ObjectUtil.checkPositive(maxClientIdLength, "maxClientIdLength");
} }
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
switch (state()) { switch (state()) {
case READ_FIXED_HEADER: case READ_FIXED_HEADER: try {
mqttFixedHeader = decodeFixedHeader(buffer); mqttFixedHeader = decodeFixedHeader(ctx, buffer);
bytesRemainingInVariablePart = mqttFixedHeader.remainingLength(); bytesRemainingInVariablePart = mqttFixedHeader.remainingLength();
checkpoint(DecoderState.READ_VARIABLE_HEADER); checkpoint(DecoderState.READ_VARIABLE_HEADER);
// fall through // fall through
} catch (Exception cause) {
out.add(invalidMessage(cause));
return;
}
case READ_VARIABLE_HEADER: try { case READ_VARIABLE_HEADER: try {
if (bytesRemainingInVariablePart > maxBytesInMessage) { final Result<?> decodedVariableHeader = decodeVariableHeader(ctx, buffer, mqttFixedHeader);
throw new DecoderException("too large message: " + bytesRemainingInVariablePart + " bytes");
}
final Result<?> decodedVariableHeader = decodeVariableHeader(buffer, mqttFixedHeader);
variableHeader = decodedVariableHeader.value; variableHeader = decodedVariableHeader.value;
if (bytesRemainingInVariablePart > maxBytesInMessage) {
buffer.skipBytes(actualReadableBytes());
throw new TooLongFrameException("too large message: " + bytesRemainingInVariablePart + " bytes");
}
bytesRemainingInVariablePart -= decodedVariableHeader.numberOfBytesConsumed; bytesRemainingInVariablePart -= decodedVariableHeader.numberOfBytesConsumed;
checkpoint(DecoderState.READ_PAYLOAD); checkpoint(DecoderState.READ_PAYLOAD);
// fall through // fall through
@@ -94,9 +101,11 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
case READ_PAYLOAD: try { case READ_PAYLOAD: try {
final Result<?> decodedPayload = final Result<?> decodedPayload =
decodePayload( decodePayload(
ctx,
buffer, buffer,
mqttFixedHeader.messageType(), mqttFixedHeader.messageType(),
bytesRemainingInVariablePart, bytesRemainingInVariablePart,
maxClientIdLength,
variableHeader); variableHeader);
bytesRemainingInVariablePart -= decodedPayload.numberOfBytesConsumed; bytesRemainingInVariablePart -= decodedPayload.numberOfBytesConsumed;
if (bytesRemainingInVariablePart != 0) { if (bytesRemainingInVariablePart != 0) {
@@ -129,16 +138,20 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private MqttMessage invalidMessage(Throwable cause) { private MqttMessage invalidMessage(Throwable cause) {
checkpoint(DecoderState.BAD_MESSAGE); checkpoint(DecoderState.BAD_MESSAGE);
return MqttMessageFactory.newInvalidMessage(cause); return MqttMessageFactory.newInvalidMessage(mqttFixedHeader, variableHeader, cause);
} }
/** /**
* Decodes the fixed header. It's one byte for the flags and then variable bytes for the remaining length. * Decodes the fixed header. It's one byte for the flags and then variable
* bytes for the remaining length.
*
* @see
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180841
* *
* @param buffer the buffer to decode from * @param buffer the buffer to decode from
* @return the fixed header * @return the fixed header
*/ */
private static MqttFixedHeader decodeFixedHeader(ByteBuf buffer) { private static MqttFixedHeader decodeFixedHeader(ChannelHandlerContext ctx, ByteBuf buffer) {
short b1 = buffer.readUnsignedByte(); short b1 = buffer.readUnsignedByte();
MqttMessageType messageType = MqttMessageType.valueOf(b1 >> 4); MqttMessageType messageType = MqttMessageType.valueOf(b1 >> 4);
@@ -146,6 +159,59 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
int qosLevel = (b1 & 0x06) >> 1; int qosLevel = (b1 & 0x06) >> 1;
boolean retain = (b1 & 0x01) != 0; boolean retain = (b1 & 0x01) != 0;
switch (messageType) {
case PUBLISH:
if (qosLevel == 3) {
throw new DecoderException("Illegal QOS Level in fixed header of PUBLISH message ("
+ qosLevel + ')');
}
break;
case PUBREL:
case SUBSCRIBE:
case UNSUBSCRIBE:
if (dupFlag) {
throw new DecoderException("Illegal BIT 3 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
if (qosLevel != 1) {
throw new DecoderException("Illegal QOS Level in fixed header of " + messageType
+ " message, must be 1, found " + qosLevel);
}
if (retain) {
throw new DecoderException("Illegal BIT 0 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
break;
case AUTH:
case CONNACK:
case CONNECT:
case DISCONNECT:
case PINGREQ:
case PINGRESP:
case PUBACK:
case PUBCOMP:
case PUBREC:
case SUBACK:
case UNSUBACK:
if (dupFlag) {
throw new DecoderException("Illegal BIT 3 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
if (qosLevel != 0) {
throw new DecoderException("Illegal BIT 2 or 1 in fixed header of " + messageType
+ " message, must be 0, found " + qosLevel);
}
if (retain) {
throw new DecoderException("Illegal BIT 0 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
break;
default:
throw new DecoderException("Unknown message type, do not know how to validate fixed header");
}
int remainingLength = 0; int remainingLength = 0;
int multiplier = 1; int multiplier = 1;
short digit; short digit;
@@ -163,7 +229,7 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
} }
MqttFixedHeader decodedFixedHeader = MqttFixedHeader decodedFixedHeader =
new MqttFixedHeader(messageType, dupFlag, MqttQoS.valueOf(qosLevel), retain, remainingLength); new MqttFixedHeader(messageType, dupFlag, MqttQoS.valueOf(qosLevel), retain, remainingLength);
return validateFixedHeader(resetUnusedFields(decodedFixedHeader)); return MqttCodecUtil.validateFixedHeader(ctx, MqttCodecUtil.resetUnusedFields(decodedFixedHeader));
} }
/** /**
@@ -172,50 +238,60 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
* @param mqttFixedHeader MqttFixedHeader of the same message * @param mqttFixedHeader MqttFixedHeader of the same message
* @return the variable header * @return the variable header
*/ */
private static Result<?> decodeVariableHeader(ByteBuf buffer, MqttFixedHeader mqttFixedHeader) { private Result<?> decodeVariableHeader(ChannelHandlerContext ctx, ByteBuf buffer, MqttFixedHeader mqttFixedHeader) {
switch (mqttFixedHeader.messageType()) { switch (mqttFixedHeader.messageType()) {
case CONNECT: case CONNECT:
return decodeConnectionVariableHeader(buffer); return decodeConnectionVariableHeader(ctx, buffer);
case CONNACK: case CONNACK:
return decodeConnAckVariableHeader(buffer); return decodeConnAckVariableHeader(ctx, buffer);
case SUBSCRIBE:
case UNSUBSCRIBE: case UNSUBSCRIBE:
case SUBSCRIBE:
case SUBACK: case SUBACK:
case UNSUBACK: case UNSUBACK:
return decodeMessageIdAndPropertiesVariableHeader(ctx, buffer);
case PUBACK: case PUBACK:
case PUBREC: case PUBREC:
case PUBCOMP: case PUBCOMP:
case PUBREL: case PUBREL:
return decodeMessageIdVariableHeader(buffer); return decodePubReplyMessage(buffer);
case PUBLISH: case PUBLISH:
return decodePublishVariableHeader(buffer, mqttFixedHeader); return decodePublishVariableHeader(ctx, buffer, mqttFixedHeader);
case DISCONNECT:
case AUTH:
return decodeReasonCodeAndPropertiesVariableHeader(buffer);
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
case DISCONNECT:
// Empty variable header // Empty variable header
return new Result<Object>(null, 0); return new Result<Object>(null, 0);
default:
//shouldn't reach here
throw new DecoderException("Unknown message type: " + mqttFixedHeader.messageType());
} }
return new Result<Object>(null, 0); //should never reach here
} }
private static Result<MqttConnectVariableHeader> decodeConnectionVariableHeader(ByteBuf buffer) { private static Result<MqttConnectVariableHeader> decodeConnectionVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer) {
final Result<String> protoString = decodeString(buffer); final Result<String> protoString = decodeString(buffer);
int numberOfBytesConsumed = protoString.numberOfBytesConsumed; int numberOfBytesConsumed = protoString.numberOfBytesConsumed;
final byte protocolLevel = buffer.readByte(); final byte protocolLevel = buffer.readByte();
numberOfBytesConsumed += 1; numberOfBytesConsumed += 1;
final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(protoString.value, protocolLevel); MqttVersion version = MqttVersion.fromProtocolNameAndLevel(protoString.value, protocolLevel);
MqttCodecUtil.setMqttVersion(ctx, version);
final int b1 = buffer.readUnsignedByte(); final int b1 = buffer.readUnsignedByte();
numberOfBytesConsumed += 1; numberOfBytesConsumed += 1;
final Result<Integer> keepAlive = decodeMsbLsb(buffer); final int keepAlive = decodeMsbLsb(buffer);
numberOfBytesConsumed += keepAlive.numberOfBytesConsumed; numberOfBytesConsumed += 2;
final boolean hasUserName = (b1 & 0x80) == 0x80; final boolean hasUserName = (b1 & 0x80) == 0x80;
final boolean hasPassword = (b1 & 0x40) == 0x40; final boolean hasPassword = (b1 & 0x40) == 0x40;
@@ -223,69 +299,178 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
final int willQos = (b1 & 0x18) >> 3; final int willQos = (b1 & 0x18) >> 3;
final boolean willFlag = (b1 & 0x04) == 0x04; final boolean willFlag = (b1 & 0x04) == 0x04;
final boolean cleanSession = (b1 & 0x02) == 0x02; final boolean cleanSession = (b1 & 0x02) == 0x02;
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) { if (version == MqttVersion.MQTT_3_1_1 || version == MqttVersion.MQTT_5) {
final boolean zeroReservedFlag = (b1 & 0x01) == 0x0; final boolean zeroReservedFlag = (b1 & 0x01) == 0x0;
if (!zeroReservedFlag) { if (!zeroReservedFlag) {
// MQTT v3.1.1: The Server MUST validate that the reserved flag in the CONNECT Control Packet is // MQTT v3.1.1: The Server MUST validate that the reserved flag in the CONNECT Control Packet is
// set to zero and disconnect the Client if it is not zero. // set to zero and disconnect the Client if it is not zero.
// See http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230 // See https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230
throw new DecoderException("non-zero reserved flag"); throw new DecoderException("non-zero reserved flag");
} }
} }
final MqttProperties properties;
if (version == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
properties = MqttProperties.NO_PROPERTIES;
}
final MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader( final MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader(
mqttVersion.protocolName(), version.protocolName(),
mqttVersion.protocolLevel(), version.protocolLevel(),
hasUserName, hasUserName,
hasPassword, hasPassword,
willRetain, willRetain,
willQos, willQos,
willFlag, willFlag,
cleanSession, cleanSession,
keepAlive.value); keepAlive,
properties);
return new Result<MqttConnectVariableHeader>(mqttConnectVariableHeader, numberOfBytesConsumed); return new Result<MqttConnectVariableHeader>(mqttConnectVariableHeader, numberOfBytesConsumed);
} }
private static Result<MqttConnAckVariableHeader> decodeConnAckVariableHeader(ByteBuf buffer) { private static Result<MqttConnAckVariableHeader> decodeConnAckVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer) {
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
final boolean sessionPresent = (buffer.readUnsignedByte() & 0x01) == 0x01; final boolean sessionPresent = (buffer.readUnsignedByte() & 0x01) == 0x01;
byte returnCode = buffer.readByte(); byte returnCode = buffer.readByte();
final int numberOfBytesConsumed = 2; int numberOfBytesConsumed = 2;
final MqttProperties properties;
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
properties = MqttProperties.NO_PROPERTIES;
}
final MqttConnAckVariableHeader mqttConnAckVariableHeader = final MqttConnAckVariableHeader mqttConnAckVariableHeader =
new MqttConnAckVariableHeader(MqttConnectReturnCode.valueOf(returnCode), sessionPresent); new MqttConnAckVariableHeader(MqttConnectReturnCode.valueOf(returnCode), sessionPresent, properties);
return new Result<MqttConnAckVariableHeader>(mqttConnAckVariableHeader, numberOfBytesConsumed); return new Result<MqttConnAckVariableHeader>(mqttConnAckVariableHeader, numberOfBytesConsumed);
} }
private static Result<MqttMessageIdVariableHeader> decodeMessageIdVariableHeader(ByteBuf buffer) { private static Result<MqttMessageIdAndPropertiesVariableHeader> decodeMessageIdAndPropertiesVariableHeader(
final Result<Integer> messageId = decodeMessageId(buffer); ChannelHandlerContext ctx,
return new Result<MqttMessageIdVariableHeader>( ByteBuf buffer) {
MqttMessageIdVariableHeader.from(messageId.value), final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
messageId.numberOfBytesConsumed); final int packetId = decodeMessageId(buffer);
final MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader;
final int mqtt5Consumed;
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> properties = decodeProperties(buffer);
mqttVariableHeader = new MqttMessageIdAndPropertiesVariableHeader(packetId, properties.value);
mqtt5Consumed = properties.numberOfBytesConsumed;
} else {
mqttVariableHeader = new MqttMessageIdAndPropertiesVariableHeader(packetId,
MqttProperties.NO_PROPERTIES);
mqtt5Consumed = 0;
}
return new Result<MqttMessageIdAndPropertiesVariableHeader>(mqttVariableHeader,
2 + mqtt5Consumed);
} }
private static Result<MqttPublishVariableHeader> decodePublishVariableHeader( private Result<MqttPubReplyMessageVariableHeader> decodePubReplyMessage(ByteBuf buffer) {
final int packetId = decodeMessageId(buffer);
final MqttPubReplyMessageVariableHeader mqttPubAckVariableHeader;
final int consumed;
final int packetIdNumberOfBytesConsumed = 2;
if (bytesRemainingInVariablePart > 3) {
final byte reasonCode = buffer.readByte();
final Result<MqttProperties> properties = decodeProperties(buffer);
mqttPubAckVariableHeader = new MqttPubReplyMessageVariableHeader(packetId,
reasonCode,
properties.value);
consumed = packetIdNumberOfBytesConsumed + 1 + properties.numberOfBytesConsumed;
} else if (bytesRemainingInVariablePart > 2) {
final byte reasonCode = buffer.readByte();
mqttPubAckVariableHeader = new MqttPubReplyMessageVariableHeader(packetId,
reasonCode,
MqttProperties.NO_PROPERTIES);
consumed = packetIdNumberOfBytesConsumed + 1;
} else {
mqttPubAckVariableHeader = new MqttPubReplyMessageVariableHeader(packetId,
(byte) 0,
MqttProperties.NO_PROPERTIES);
consumed = packetIdNumberOfBytesConsumed;
}
return new Result<MqttPubReplyMessageVariableHeader>(mqttPubAckVariableHeader, consumed);
}
private Result<MqttReasonCodeAndPropertiesVariableHeader> decodeReasonCodeAndPropertiesVariableHeader(
ByteBuf buffer) {
final byte reasonCode;
final MqttProperties properties;
final int consumed;
if (bytesRemainingInVariablePart > 1) {
reasonCode = buffer.readByte();
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
consumed = 1 + propertiesResult.numberOfBytesConsumed;
} else if (bytesRemainingInVariablePart > 0) {
reasonCode = buffer.readByte();
properties = MqttProperties.NO_PROPERTIES;
consumed = 1;
} else {
reasonCode = 0;
properties = MqttProperties.NO_PROPERTIES;
consumed = 0;
}
final MqttReasonCodeAndPropertiesVariableHeader mqttReasonAndPropsVariableHeader =
new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, properties);
return new Result<MqttReasonCodeAndPropertiesVariableHeader>(
mqttReasonAndPropsVariableHeader,
consumed);
}
private Result<MqttPublishVariableHeader> decodePublishVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer, ByteBuf buffer,
MqttFixedHeader mqttFixedHeader) { MqttFixedHeader mqttFixedHeader) {
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
final Result<String> decodedTopic = decodeString(buffer); final Result<String> decodedTopic = decodeString(buffer);
if (!isValidPublishTopicName(decodedTopic.value)) { if (!MqttCodecUtil.isValidPublishTopicName(decodedTopic.value)) {
throw new DecoderException("invalid publish topic name: " + decodedTopic.value + " (contains wildcards)"); throw new DecoderException("invalid publish topic name: " + decodedTopic.value + " (contains wildcards)");
} }
int numberOfBytesConsumed = decodedTopic.numberOfBytesConsumed; int numberOfBytesConsumed = decodedTopic.numberOfBytesConsumed;
int messageId = -1; int messageId = -1;
if (mqttFixedHeader.qosLevel().value() > 0) { if (mqttFixedHeader.qosLevel().value() > 0) {
final Result<Integer> decodedMessageId = decodeMessageId(buffer); messageId = decodeMessageId(buffer);
messageId = decodedMessageId.value; numberOfBytesConsumed += 2;
numberOfBytesConsumed += decodedMessageId.numberOfBytesConsumed;
} }
final MqttProperties properties;
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
properties = MqttProperties.NO_PROPERTIES;
}
final MqttPublishVariableHeader mqttPublishVariableHeader = final MqttPublishVariableHeader mqttPublishVariableHeader =
new MqttPublishVariableHeader(decodedTopic.value, messageId); new MqttPublishVariableHeader(decodedTopic.value, messageId, properties);
return new Result<MqttPublishVariableHeader>(mqttPublishVariableHeader, numberOfBytesConsumed); return new Result<MqttPublishVariableHeader>(mqttPublishVariableHeader, numberOfBytesConsumed);
} }
private static Result<Integer> decodeMessageId(ByteBuf buffer) { /**
final Result<Integer> messageId = decodeMsbLsb(buffer); * @return messageId with numberOfBytesConsumed is 2
if (!isValidMessageId(messageId.value)) { */
throw new DecoderException("invalid messageId: " + messageId.value); private static int decodeMessageId(ByteBuf buffer) {
final int messageId = decodeMsbLsb(buffer);
if (!MqttCodecUtil.isValidMessageId(messageId)) {
throw new DecoderException("invalid messageId: " + messageId);
} }
return messageId; return messageId;
} }
@@ -300,13 +485,15 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
* @return the payload * @return the payload
*/ */
private static Result<?> decodePayload( private static Result<?> decodePayload(
ChannelHandlerContext ctx,
ByteBuf buffer, ByteBuf buffer,
MqttMessageType messageType, MqttMessageType messageType,
int bytesRemainingInVariablePart, int bytesRemainingInVariablePart,
int maxClientIdLength,
Object variableHeader) { Object variableHeader) {
switch (messageType) { switch (messageType) {
case CONNECT: case CONNECT:
return decodeConnectionPayload(buffer, (MqttConnectVariableHeader) variableHeader); return decodeConnectionPayload(buffer, maxClientIdLength, (MqttConnectVariableHeader) variableHeader);
case SUBSCRIBE: case SUBSCRIBE:
return decodeSubscribePayload(buffer, bytesRemainingInVariablePart); return decodeSubscribePayload(buffer, bytesRemainingInVariablePart);
@@ -317,6 +504,9 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
case UNSUBSCRIBE: case UNSUBSCRIBE:
return decodeUnsubscribePayload(buffer, bytesRemainingInVariablePart); return decodeUnsubscribePayload(buffer, bytesRemainingInVariablePart);
case UNSUBACK:
return decodeUnsubAckPayload(ctx, buffer, bytesRemainingInVariablePart);
case PUBLISH: case PUBLISH:
return decodePublishPayload(buffer, bytesRemainingInVariablePart); return decodePublishPayload(buffer, bytesRemainingInVariablePart);
@@ -328,42 +518,55 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private static Result<MqttConnectPayload> decodeConnectionPayload( private static Result<MqttConnectPayload> decodeConnectionPayload(
ByteBuf buffer, ByteBuf buffer,
int maxClientIdLength,
MqttConnectVariableHeader mqttConnectVariableHeader) { MqttConnectVariableHeader mqttConnectVariableHeader) {
final Result<String> decodedClientId = decodeString(buffer); final Result<String> decodedClientId = decodeString(buffer);
final String decodedClientIdValue = decodedClientId.value; final String decodedClientIdValue = decodedClientId.value;
final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(mqttConnectVariableHeader.name(), final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(mqttConnectVariableHeader.name(),
(byte) mqttConnectVariableHeader.version()); (byte) mqttConnectVariableHeader.version());
if (!isValidClientId(mqttVersion, decodedClientIdValue)) { if (!MqttCodecUtil.isValidClientId(mqttVersion, maxClientIdLength, decodedClientIdValue)) {
throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + decodedClientIdValue); throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + decodedClientIdValue);
} }
int numberOfBytesConsumed = decodedClientId.numberOfBytesConsumed; int numberOfBytesConsumed = decodedClientId.numberOfBytesConsumed;
Result<String> decodedWillTopic = null; Result<String> decodedWillTopic = null;
Result<String> decodedWillMessage = null; byte[] decodedWillMessage = null;
final MqttProperties willProperties;
if (mqttConnectVariableHeader.isWillFlag()) { if (mqttConnectVariableHeader.isWillFlag()) {
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
willProperties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
willProperties = MqttProperties.NO_PROPERTIES;
}
decodedWillTopic = decodeString(buffer, 0, 32767); decodedWillTopic = decodeString(buffer, 0, 32767);
numberOfBytesConsumed += decodedWillTopic.numberOfBytesConsumed; numberOfBytesConsumed += decodedWillTopic.numberOfBytesConsumed;
decodedWillMessage = decodeAsciiString(buffer); decodedWillMessage = decodeByteArray(buffer);
numberOfBytesConsumed += decodedWillMessage.numberOfBytesConsumed; numberOfBytesConsumed += decodedWillMessage.length + 2;
} else {
willProperties = MqttProperties.NO_PROPERTIES;
} }
Result<String> decodedUserName = null; Result<String> decodedUserName = null;
Result<byte[]> decodedPassword = null; byte[] decodedPassword = null;
if (mqttConnectVariableHeader.hasUserName()) { if (mqttConnectVariableHeader.hasUserName()) {
decodedUserName = decodeString(buffer); decodedUserName = decodeString(buffer);
numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed; numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed;
} }
if (mqttConnectVariableHeader.hasPassword()) { if (mqttConnectVariableHeader.hasPassword()) {
decodedPassword = decodeByte(buffer); decodedPassword = decodeByteArray(buffer);
numberOfBytesConsumed += decodedPassword.numberOfBytesConsumed; numberOfBytesConsumed += decodedPassword.length + 2;
} }
final MqttConnectPayload mqttConnectPayload = final MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload( new MqttConnectPayload(
decodedClientId.value, decodedClientId.value,
willProperties,
decodedWillTopic != null ? decodedWillTopic.value : null, decodedWillTopic != null ? decodedWillTopic.value : null,
decodedWillMessage != null ? decodedWillMessage.value : null, decodedWillMessage,
decodedUserName != null ? decodedUserName.value : null, decodedUserName != null ? decodedUserName.value : null,
decodedPassword != null ? decodedPassword.value : null); decodedPassword);
return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed); return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed);
} }
@@ -375,9 +578,21 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
while (numberOfBytesConsumed < bytesRemainingInVariablePart) { while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
final Result<String> decodedTopicName = decodeString(buffer); final Result<String> decodedTopicName = decodeString(buffer);
numberOfBytesConsumed += decodedTopicName.numberOfBytesConsumed; numberOfBytesConsumed += decodedTopicName.numberOfBytesConsumed;
int qos = buffer.readUnsignedByte() & 0x03; //See 3.8.3.1 Subscription Options of MQTT 5.0 specification for optionByte details
final short optionByte = buffer.readUnsignedByte();
MqttQoS qos = MqttQoS.valueOf(optionByte & 0x03);
boolean noLocal = ((optionByte & 0x04) >> 2) == 1;
boolean retainAsPublished = ((optionByte & 0x08) >> 3) == 1;
MqttSubscriptionOption.RetainedHandlingPolicy retainHandling = MqttSubscriptionOption.RetainedHandlingPolicy.valueOf((optionByte & 0x30) >> 4);
final MqttSubscriptionOption subscriptionOption = new MqttSubscriptionOption(qos,
noLocal,
retainAsPublished,
retainHandling);
numberOfBytesConsumed++; numberOfBytesConsumed++;
subscribeTopics.add(new MqttTopicSubscription(decodedTopicName.value, MqttQoS.valueOf(qos))); subscribeTopics.add(new MqttTopicSubscription(decodedTopicName.value, subscriptionOption));
} }
return new Result<MqttSubscribePayload>(new MqttSubscribePayload(subscribeTopics), numberOfBytesConsumed); return new Result<MqttSubscribePayload>(new MqttSubscribePayload(subscribeTopics), numberOfBytesConsumed);
} }
@@ -385,16 +600,30 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private static Result<MqttSubAckPayload> decodeSubackPayload( private static Result<MqttSubAckPayload> decodeSubackPayload(
ByteBuf buffer, ByteBuf buffer,
int bytesRemainingInVariablePart) { int bytesRemainingInVariablePart) {
final List<Integer> grantedQos = new ArrayList<Integer>(); final List<Integer> grantedQos = new ArrayList<Integer>(bytesRemainingInVariablePart);
int numberOfBytesConsumed = 0; int numberOfBytesConsumed = 0;
while (numberOfBytesConsumed < bytesRemainingInVariablePart) { while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
int qos = buffer.readUnsignedByte() & 0x03; int reasonCode = buffer.readUnsignedByte();
numberOfBytesConsumed++; numberOfBytesConsumed++;
grantedQos.add(qos); grantedQos.add(reasonCode);
} }
return new Result<MqttSubAckPayload>(new MqttSubAckPayload(grantedQos), numberOfBytesConsumed); return new Result<MqttSubAckPayload>(new MqttSubAckPayload(grantedQos), numberOfBytesConsumed);
} }
private static Result<MqttUnsubAckPayload> decodeUnsubAckPayload(
ChannelHandlerContext ctx,
ByteBuf buffer,
int bytesRemainingInVariablePart) {
final List<Short> reasonCodes = new ArrayList<Short>(bytesRemainingInVariablePart);
int numberOfBytesConsumed = 0;
while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
short reasonCode = buffer.readUnsignedByte();
numberOfBytesConsumed++;
reasonCodes.add(reasonCode);
}
return new Result<MqttUnsubAckPayload>(new MqttUnsubAckPayload(reasonCodes), numberOfBytesConsumed);
}
private static Result<MqttUnsubscribePayload> decodeUnsubscribePayload( private static Result<MqttUnsubscribePayload> decodeUnsubscribePayload(
ByteBuf buffer, ByteBuf buffer,
int bytesRemainingInVariablePart) { int bytesRemainingInVariablePart) {
@@ -419,25 +648,9 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
return decodeString(buffer, 0, Integer.MAX_VALUE); return decodeString(buffer, 0, Integer.MAX_VALUE);
} }
private static Result<String> decodeAsciiString(ByteBuf buffer) {
Result<String> result = decodeString(buffer, 0, Integer.MAX_VALUE);
final String s = result.value;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) > 127) {
return new Result<String>(null, result.numberOfBytesConsumed);
}
}
return new Result<String>(s, result.numberOfBytesConsumed);
}
private static Result<byte[]> decodeByte(ByteBuf buffer) {
return decodeByte(buffer, 0, Integer.MAX_VALUE);
}
private static Result<String> decodeString(ByteBuf buffer, int minBytes, int maxBytes) { private static Result<String> decodeString(ByteBuf buffer, int minBytes, int maxBytes) {
final Result<Integer> decodedSize = decodeMsbLsb(buffer); int size = decodeMsbLsb(buffer);
int size = decodedSize.value; int numberOfBytesConsumed = 2;
int numberOfBytesConsumed = decodedSize.numberOfBytesConsumed;
if (size < minBytes || size > maxBytes) { if (size < minBytes || size > maxBytes) {
buffer.skipBytes(size); buffer.skipBytes(size);
numberOfBytesConsumed += size; numberOfBytesConsumed += size;
@@ -449,35 +662,68 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
return new Result<String>(s, numberOfBytesConsumed); return new Result<String>(s, numberOfBytesConsumed);
} }
private static Result<byte[]> decodeByte(ByteBuf buffer, int minBytes, int maxBytes) { /**
final Result<Integer> decodedSize = decodeMsbLsb(buffer); *
int size = decodedSize.value; * @return the decoded byte[], numberOfBytesConsumed = byte[].length + 2
int numberOfBytesConsumed = decodedSize.numberOfBytesConsumed; */
if (size < minBytes || size > maxBytes) { private static byte[] decodeByteArray(ByteBuf buffer) {
buffer.skipBytes(size); int size = decodeMsbLsb(buffer);
numberOfBytesConsumed += size; byte[] bytes = new byte[size];
return new Result<>(null, numberOfBytesConsumed); buffer.readBytes(bytes);
} return bytes;
byte[] s = new byte[size];
buffer.getBytes(buffer.readerIndex(), s, 0, size);
buffer.skipBytes(size);
numberOfBytesConsumed += size;
return new Result<>(s, numberOfBytesConsumed);
} }
private static Result<Integer> decodeMsbLsb(ByteBuf buffer) { // packing utils to reduce the amount of garbage while decoding ints
return decodeMsbLsb(buffer, 0, 65535); private static long packInts(int a, int b) {
return (((long) a) << 32) | (b & 0xFFFFFFFFL);
} }
private static Result<Integer> decodeMsbLsb(ByteBuf buffer, int min, int max) { private static int unpackA(long ints) {
return (int) (ints >> 32);
}
private static int unpackB(long ints) {
return (int) ints;
}
/**
* numberOfBytesConsumed = 2. return decoded result.
*/
private static int decodeMsbLsb(ByteBuf buffer) {
int min = 0;
int max = 65535;
short msbSize = buffer.readUnsignedByte(); short msbSize = buffer.readUnsignedByte();
short lsbSize = buffer.readUnsignedByte(); short lsbSize = buffer.readUnsignedByte();
final int numberOfBytesConsumed = 2;
int result = msbSize << 8 | lsbSize; int result = msbSize << 8 | lsbSize;
if (result < min || result > max) { if (result < min || result > max) {
result = -1; result = -1;
} }
return new Result<Integer>(result, numberOfBytesConsumed); return result;
}
/**
* See 1.5.5 Variable Byte Integer section of MQTT 5.0 specification for encoding/decoding rules
*
* @param buffer the buffer to decode from
* @return result pack with a = decoded integer, b = numberOfBytesConsumed. Need to unpack to read them.
* @throws DecoderException if bad MQTT protocol limits Remaining Length
*/
private static long decodeVariableByteInteger(ByteBuf buffer) {
int remainingLength = 0;
int multiplier = 1;
short digit;
int loops = 0;
do {
digit = buffer.readUnsignedByte();
remainingLength += (digit & 127) * multiplier;
multiplier *= 128;
loops++;
} while ((digit & 128) != 0 && loops < 4);
if (loops == 4 && (digit & 128) != 0) {
throw new DecoderException("MQTT protocol limits Remaining Length to 4 bytes");
}
return packInts(remainingLength, loops);
} }
private static final class Result<T> { private static final class Result<T> {
@@ -490,4 +736,82 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
this.numberOfBytesConsumed = numberOfBytesConsumed; this.numberOfBytesConsumed = numberOfBytesConsumed;
} }
} }
private static Result<MqttProperties> decodeProperties(ByteBuf buffer) {
final long propertiesLength = decodeVariableByteInteger(buffer);
int totalPropertiesLength = unpackA(propertiesLength);
int numberOfBytesConsumed = unpackB(propertiesLength);
MqttProperties decodedProperties = new MqttProperties();
while (numberOfBytesConsumed < totalPropertiesLength) {
long propertyId = decodeVariableByteInteger(buffer);
final int propertyIdValue = unpackA(propertyId);
numberOfBytesConsumed += unpackB(propertyId);
MqttProperties.MqttPropertyType propertyType = MqttProperties.MqttPropertyType.valueOf(propertyIdValue);
switch (propertyType) {
case PAYLOAD_FORMAT_INDICATOR:
case REQUEST_PROBLEM_INFORMATION:
case REQUEST_RESPONSE_INFORMATION:
case MAXIMUM_QOS:
case RETAIN_AVAILABLE:
case WILDCARD_SUBSCRIPTION_AVAILABLE:
case SUBSCRIPTION_IDENTIFIER_AVAILABLE:
case SHARED_SUBSCRIPTION_AVAILABLE:
final int b1 = buffer.readUnsignedByte();
numberOfBytesConsumed++;
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, b1));
break;
case SERVER_KEEP_ALIVE:
case RECEIVE_MAXIMUM:
case TOPIC_ALIAS_MAXIMUM:
case TOPIC_ALIAS:
final int int2BytesResult = decodeMsbLsb(buffer);
numberOfBytesConsumed += 2;
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, int2BytesResult));
break;
case PUBLICATION_EXPIRY_INTERVAL:
case SESSION_EXPIRY_INTERVAL:
case WILL_DELAY_INTERVAL:
case MAXIMUM_PACKET_SIZE:
final int maxPacketSize = buffer.readInt();
numberOfBytesConsumed += 4;
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, maxPacketSize));
break;
case SUBSCRIPTION_IDENTIFIER:
long vbIntegerResult = decodeVariableByteInteger(buffer);
numberOfBytesConsumed += unpackB(vbIntegerResult);
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, unpackA(vbIntegerResult)));
break;
case CONTENT_TYPE:
case RESPONSE_TOPIC:
case ASSIGNED_CLIENT_IDENTIFIER:
case AUTHENTICATION_METHOD:
case RESPONSE_INFORMATION:
case SERVER_REFERENCE:
case REASON_STRING:
final Result<String> stringResult = decodeString(buffer);
numberOfBytesConsumed += stringResult.numberOfBytesConsumed;
decodedProperties.add(new MqttProperties.StringProperty(propertyIdValue, stringResult.value));
break;
case USER_PROPERTY:
final Result<String> keyResult = decodeString(buffer);
final Result<String> valueResult = decodeString(buffer);
numberOfBytesConsumed += keyResult.numberOfBytesConsumed;
numberOfBytesConsumed += valueResult.numberOfBytesConsumed;
decodedProperties.add(new MqttProperties.UserProperty(keyResult.value, valueResult.value));
break;
case CORRELATION_DATA:
case AUTHENTICATION_DATA:
final byte[] binaryDataResult = decodeByteArray(buffer);
numberOfBytesConsumed += binaryDataResult.length + 2;
decodedProperties.add(new MqttProperties.BinaryProperty(propertyIdValue, binaryDataResult));
break;
default:
//shouldn't reach here
throw new DecoderException("Unknown property type: " + propertyType);
}
}
return new Result<MqttProperties>(decodedProperties, numberOfBytesConsumed);
}
} }
+517 -172
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -18,19 +18,22 @@ package io.netty.handler.codec.mqtt;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.EncoderException;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.EmptyArrays;
import java.util.List; import java.util.List;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.*; import static io.netty.buffer.ByteBufUtil.*;
/** /**
* Encodes Mqtt messages into bytes following the protocol specification v3.1 * Encodes Mqtt messages into bytes following the protocol specification v3.1
* as described here <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">MQTTV3.1</a> * as described here <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">MQTTV3.1</a>
* or v5.0 as described here <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html">MQTTv5.0</a> -
* depending on the version specified in the first CONNECT message that goes through the channel.
*/ */
@ChannelHandler.Sharable @ChannelHandler.Sharable
public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> { public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
@@ -41,49 +44,57 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
@Override @Override
protected void encode(ChannelHandlerContext ctx, MqttMessage msg, List<Object> out) throws Exception { protected void encode(ChannelHandlerContext ctx, MqttMessage msg, List<Object> out) throws Exception {
out.add(doEncode(ctx.alloc(), msg)); out.add(doEncode(ctx, msg));
} }
/** /**
* This is the main encoding method. * This is the main encoding method.
* It's only visible for testing. * It's only visible for testing.
* *
* @param byteBufAllocator Allocates ByteBuf
* @param message MQTT message to encode * @param message MQTT message to encode
* @return ByteBuf with encoded bytes * @return ByteBuf with encoded bytes
*/ */
static ByteBuf doEncode(ByteBufAllocator byteBufAllocator, MqttMessage message) { static ByteBuf doEncode(ChannelHandlerContext ctx,
MqttMessage message) {
switch (message.fixedHeader().messageType()) { switch (message.fixedHeader().messageType()) {
case CONNECT: case CONNECT:
return encodeConnectMessage(byteBufAllocator, (MqttConnectMessage) message); return encodeConnectMessage(ctx, (MqttConnectMessage) message);
case CONNACK: case CONNACK:
return encodeConnAckMessage(byteBufAllocator, (MqttConnAckMessage) message); return encodeConnAckMessage(ctx, (MqttConnAckMessage) message);
case PUBLISH: case PUBLISH:
return encodePublishMessage(byteBufAllocator, (MqttPublishMessage) message); return encodePublishMessage(ctx, (MqttPublishMessage) message);
case SUBSCRIBE: case SUBSCRIBE:
return encodeSubscribeMessage(byteBufAllocator, (MqttSubscribeMessage) message); return encodeSubscribeMessage(ctx, (MqttSubscribeMessage) message);
case UNSUBSCRIBE: case UNSUBSCRIBE:
return encodeUnsubscribeMessage(byteBufAllocator, (MqttUnsubscribeMessage) message); return encodeUnsubscribeMessage(ctx, (MqttUnsubscribeMessage) message);
case SUBACK: case SUBACK:
return encodeSubAckMessage(byteBufAllocator, (MqttSubAckMessage) message); return encodeSubAckMessage(ctx, (MqttSubAckMessage) message);
case UNSUBACK: case UNSUBACK:
if (message instanceof MqttUnsubAckMessage) {
return encodeUnsubAckMessage(ctx, (MqttUnsubAckMessage) message);
}
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
case PUBACK: case PUBACK:
case PUBREC: case PUBREC:
case PUBREL: case PUBREL:
case PUBCOMP: case PUBCOMP:
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(byteBufAllocator, message); return encodePubReplyMessage(ctx, message);
case DISCONNECT:
case AUTH:
return encodeReasonCodePlusPropertiesMessage(ctx, message);
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
case DISCONNECT: return encodeMessageWithOnlySingleByteFixedHeader(ctx.alloc(), message);
return encodeMessageWithOnlySingleByteFixedHeader(byteBufAllocator, message);
default: default:
throw new IllegalArgumentException( throw new IllegalArgumentException(
@@ -92,7 +103,7 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} }
private static ByteBuf encodeConnectMessage( private static ByteBuf encodeConnectMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttConnectMessage message) { MqttConnectMessage message) {
int payloadBufferSize = 0; int payloadBufferSize = 0;
@@ -101,70 +112,96 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
MqttConnectPayload payload = message.payload(); MqttConnectPayload payload = message.payload();
MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(variableHeader.name(), MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(variableHeader.name(),
(byte) variableHeader.version()); (byte) variableHeader.version());
MqttCodecUtil.setMqttVersion(ctx, mqttVersion);
// as MQTT 3.1 & 3.1.1 spec, If the User Name Flag is set to 0, the Password Flag MUST be set to 0
if (!variableHeader.hasUserName() && variableHeader.hasPassword()) {
throw new EncoderException("Without a username, the password MUST be not set");
}
// Client id // Client id
String clientIdentifier = payload.clientIdentifier(); String clientIdentifier = payload.clientIdentifier();
if (!isValidClientId(mqttVersion, clientIdentifier)) { if (!MqttCodecUtil.isValidClientId(mqttVersion, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH, clientIdentifier)) {
throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + clientIdentifier); throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + clientIdentifier);
} }
byte[] clientIdentifierBytes = encodeStringUtf8(clientIdentifier); int clientIdentifierBytes = utf8Bytes(clientIdentifier);
payloadBufferSize += 2 + clientIdentifierBytes.length; payloadBufferSize += 2 + clientIdentifierBytes;
// Will topic and message // Will topic and message
String willTopic = payload.willTopic(); String willTopic = payload.willTopic();
byte[] willTopicBytes = willTopic != null ? encodeStringUtf8(willTopic) : EmptyArrays.EMPTY_BYTES; int willTopicBytes = nullableUtf8Bytes(willTopic);
String willMessage = payload.willMessage(); byte[] willMessage = payload.willMessageInBytes();
byte[] willMessageBytes = willMessage != null ? encodeStringUtf8(willMessage) : EmptyArrays.EMPTY_BYTES; byte[] willMessageBytes = willMessage != null ? willMessage : EmptyArrays.EMPTY_BYTES;
if (variableHeader.isWillFlag()) { if (variableHeader.isWillFlag()) {
payloadBufferSize += 2 + willTopicBytes.length; payloadBufferSize += 2 + willTopicBytes;
payloadBufferSize += 2 + willMessageBytes.length; payloadBufferSize += 2 + willMessageBytes.length;
} }
String userName = payload.userName(); String userName = payload.userName();
byte[] userNameBytes = userName != null ? encodeStringUtf8(userName) : EmptyArrays.EMPTY_BYTES; int userNameBytes = nullableUtf8Bytes(userName);
if (variableHeader.hasUserName()) { if (variableHeader.hasUserName()) {
payloadBufferSize += 2 + userNameBytes.length; payloadBufferSize += 2 + userNameBytes;
} }
byte[] passwordBytes = payload.password(); byte[] password = payload.passwordInBytes();
byte[] passwordBytes = password != null ? password : EmptyArrays.EMPTY_BYTES;
if (variableHeader.hasPassword()) { if (variableHeader.hasPassword()) {
payloadBufferSize += 2 + passwordBytes.length; payloadBufferSize += 2 + passwordBytes.length;
} }
// Fixed header // Fixed and variable header
byte[] protocolNameBytes = mqttVersion.protocolNameBytes(); byte[] protocolNameBytes = mqttVersion.protocolNameBytes();
int variableHeaderBufferSize = 2 + protocolNameBytes.length + 4; ByteBuf propertiesBuf = encodePropertiesIfNeeded(
int variablePartSize = variableHeaderBufferSize + payloadBufferSize; mqttVersion,
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize); ctx.alloc(),
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize); message.variableHeader().properties());
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader)); try {
writeVariableLengthInt(buf, variablePartSize); final ByteBuf willPropertiesBuf;
if (variableHeader.isWillFlag()) {
willPropertiesBuf = encodePropertiesIfNeeded(mqttVersion, ctx.alloc(), payload.willProperties());
payloadBufferSize += willPropertiesBuf.readableBytes();
} else {
willPropertiesBuf = Unpooled.EMPTY_BUFFER;
}
try {
int variableHeaderBufferSize = 2 + protocolNameBytes.length + 4 + propertiesBuf.readableBytes();
buf.writeShort(protocolNameBytes.length); int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
buf.writeBytes(protocolNameBytes); int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
buf.writeByte(variableHeader.version()); buf.writeShort(protocolNameBytes.length);
buf.writeByte(getConnVariableHeaderFlag(variableHeader)); buf.writeBytes(protocolNameBytes);
buf.writeShort(variableHeader.keepAliveTimeSeconds());
// Payload buf.writeByte(variableHeader.version());
buf.writeShort(clientIdentifierBytes.length); buf.writeByte(getConnVariableHeaderFlag(variableHeader));
buf.writeBytes(clientIdentifierBytes, 0, clientIdentifierBytes.length); buf.writeShort(variableHeader.keepAliveTimeSeconds());
if (variableHeader.isWillFlag()) { buf.writeBytes(propertiesBuf);
buf.writeShort(willTopicBytes.length);
buf.writeBytes(willTopicBytes, 0, willTopicBytes.length); // Payload
buf.writeShort(willMessageBytes.length); writeExactUTF8String(buf, clientIdentifier, clientIdentifierBytes);
buf.writeBytes(willMessageBytes, 0, willMessageBytes.length); if (variableHeader.isWillFlag()) {
buf.writeBytes(willPropertiesBuf);
writeExactUTF8String(buf, willTopic, willTopicBytes);
buf.writeShort(willMessageBytes.length);
buf.writeBytes(willMessageBytes, 0, willMessageBytes.length);
}
if (variableHeader.hasUserName()) {
writeExactUTF8String(buf, userName, userNameBytes);
}
if (variableHeader.hasPassword()) {
buf.writeShort(passwordBytes.length);
buf.writeBytes(passwordBytes, 0, passwordBytes.length);
}
return buf;
} finally {
willPropertiesBuf.release();
}
} finally {
propertiesBuf.release();
} }
if (variableHeader.hasUserName()) {
buf.writeShort(userNameBytes.length);
buf.writeBytes(userNameBytes, 0, userNameBytes.length);
}
if (variableHeader.hasPassword()) {
buf.writeShort(passwordBytes.length);
buf.writeBytes(passwordBytes, 0, passwordBytes.length);
}
return buf;
} }
private static int getConnVariableHeaderFlag(MqttConnectVariableHeader variableHeader) { private static int getConnVariableHeaderFlag(MqttConnectVariableHeader variableHeader) {
@@ -189,149 +226,285 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} }
private static ByteBuf encodeConnAckMessage( private static ByteBuf encodeConnAckMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttConnAckMessage message) { MqttConnAckMessage message) {
int length = 4; final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttConnectAckPayload payload = null; ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
if (message.payload() != null) { ctx.alloc(),
payload = (MqttConnectAckPayload)message.payload(); message.variableHeader().properties());
if (payload.getData() != null && payload.getData().length > 0) {
length += payload.getData().length;
}
}
ByteBuf buf = byteBufAllocator.buffer(length);
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
buf.writeByte(length - 2);
buf.writeByte(message.variableHeader().isSessionPresent() ? 0x01 : 0x00);
buf.writeByte(message.variableHeader().connectReturnCode().byteValue());
if (length > 4) {
buf.writeBytes(payload.getData());
}
return buf; try {
int length = 4;
MqttConnectAckPayload payload = null;
if (message.payload() != null) {
payload = (MqttConnectAckPayload)message.payload();
if (payload.getData() != null && payload.getData().length > 0) {
length += payload.getData().length;
}
}
ByteBuf buf = ctx.alloc().buffer(length + propertiesBuf.readableBytes());
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, length - 2 + propertiesBuf.readableBytes());
buf.writeByte(message.variableHeader().isSessionPresent() ? 0x01 : 0x00);
buf.writeByte(message.variableHeader().connectReturnCode().byteValue());
if (length > 4) {
buf.writeBytes(payload.getData());
}
buf.writeBytes(propertiesBuf);
return buf;
} finally {
propertiesBuf.release();
}
} }
private static ByteBuf encodeSubscribeMessage( private static ByteBuf encodeSubscribeMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttSubscribeMessage message) { MqttSubscribeMessage message) {
int variableHeaderBufferSize = 2; MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
int payloadBufferSize = 0; ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
MqttFixedHeader mqttFixedHeader = message.fixedHeader(); try {
MqttMessageIdVariableHeader variableHeader = message.variableHeader(); final int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
MqttSubscribePayload payload = message.payload(); int payloadBufferSize = 0;
for (MqttTopicSubscription topic : payload.topicSubscriptions()) { MqttFixedHeader mqttFixedHeader = message.fixedHeader();
String topicName = topic.topicName(); MqttMessageIdVariableHeader variableHeader = message.variableHeader();
byte[] topicNameBytes = encodeStringUtf8(topicName); MqttSubscribePayload payload = message.payload();
payloadBufferSize += 2 + topicNameBytes.length;
payloadBufferSize += 1; for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
String topicName = topic.topicName();
int topicNameBytes = utf8Bytes(topicName);
payloadBufferSize += 2 + topicNameBytes;
payloadBufferSize += 1;
}
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
buf.writeBytes(propertiesBuf);
// Payload
for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
writeUnsafeUTF8String(buf, topic.topicName());
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) {
buf.writeByte(topic.qualityOfService().value());
} else {
final MqttSubscriptionOption option = topic.option();
int optionEncoded = option.retainHandling().value() << 4;
if (option.isRetainAsPublished()) {
optionEncoded |= 0x08;
}
if (option.isNoLocal()) {
optionEncoded |= 0x04;
}
optionEncoded |= option.qos().value();
buf.writeByte(optionEncoded);
}
}
return buf;
} finally {
propertiesBuf.release();
} }
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
// Payload
for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
String topicName = topic.topicName();
byte[] topicNameBytes = encodeStringUtf8(topicName);
buf.writeShort(topicNameBytes.length);
buf.writeBytes(topicNameBytes, 0, topicNameBytes.length);
buf.writeByte(topic.qualityOfService().value());
}
return buf;
} }
private static ByteBuf encodeUnsubscribeMessage( private static ByteBuf encodeUnsubscribeMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttUnsubscribeMessage message) { MqttUnsubscribeMessage message) {
int variableHeaderBufferSize = 2; MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
int payloadBufferSize = 0; ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
MqttFixedHeader mqttFixedHeader = message.fixedHeader(); try {
MqttMessageIdVariableHeader variableHeader = message.variableHeader(); final int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
MqttUnsubscribePayload payload = message.payload(); int payloadBufferSize = 0;
for (String topicName : payload.topics()) { MqttFixedHeader mqttFixedHeader = message.fixedHeader();
byte[] topicNameBytes = encodeStringUtf8(topicName); MqttMessageIdVariableHeader variableHeader = message.variableHeader();
payloadBufferSize += 2 + topicNameBytes.length; MqttUnsubscribePayload payload = message.payload();
for (String topicName : payload.topics()) {
int topicNameBytes = utf8Bytes(topicName);
payloadBufferSize += 2 + topicNameBytes;
}
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
buf.writeBytes(propertiesBuf);
// Payload
for (String topicName : payload.topics()) {
writeUnsafeUTF8String(buf, topicName);
}
return buf;
} finally {
propertiesBuf.release();
} }
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
// Payload
for (String topicName : payload.topics()) {
byte[] topicNameBytes = encodeStringUtf8(topicName);
buf.writeShort(topicNameBytes.length);
buf.writeBytes(topicNameBytes, 0, topicNameBytes.length);
}
return buf;
} }
private static ByteBuf encodeSubAckMessage( private static ByteBuf encodeSubAckMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttSubAckMessage message) { MqttSubAckMessage message) {
int variableHeaderBufferSize = 2; MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
int payloadBufferSize = message.payload().grantedQoSLevels().size(); ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
int variablePartSize = variableHeaderBufferSize + payloadBufferSize; ctx.alloc(),
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize); message.idAndPropertiesVariableHeader().properties());
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize); try {
buf.writeByte(getFixedHeaderByte1(message.fixedHeader())); int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
writeVariableLengthInt(buf, variablePartSize); int payloadBufferSize = message.payload().grantedQoSLevels().size();
buf.writeShort(message.variableHeader().messageId()); int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
for (int qos : message.payload().grantedQoSLevels()) { int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
buf.writeByte(qos); ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
} buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, variablePartSize);
buf.writeShort(message.variableHeader().messageId());
buf.writeBytes(propertiesBuf);
for (int code: message.payload().reasonCodes()) {
buf.writeByte(code);
}
return buf; return buf;
} finally {
propertiesBuf.release();
}
}
private static ByteBuf encodeUnsubAckMessage(
ChannelHandlerContext ctx,
MqttUnsubAckMessage message) {
if (message.variableHeader() instanceof MqttMessageIdAndPropertiesVariableHeader) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
try {
int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
MqttUnsubAckPayload payload = message.payload();
int payloadBufferSize = payload == null ? 0 : payload.unsubscribeReasonCodes().size();
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, variablePartSize);
buf.writeShort(message.variableHeader().messageId());
buf.writeBytes(propertiesBuf);
if (payload != null) {
for (Short reasonCode : payload.unsubscribeReasonCodes()) {
buf.writeByte(reasonCode);
}
}
return buf;
} finally {
propertiesBuf.release();
}
} else {
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
}
} }
private static ByteBuf encodePublishMessage( private static ByteBuf encodePublishMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttPublishMessage message) { MqttPublishMessage message) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttFixedHeader mqttFixedHeader = message.fixedHeader(); MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttPublishVariableHeader variableHeader = message.variableHeader(); MqttPublishVariableHeader variableHeader = message.variableHeader();
ByteBuf payload = message.payload().duplicate(); ByteBuf payload = message.payload().duplicate();
String topicName = variableHeader.topicName(); String topicName = variableHeader.topicName();
byte[] topicNameBytes = encodeStringUtf8(topicName); int topicNameBytes = utf8Bytes(topicName);
int variableHeaderBufferSize = 2 + topicNameBytes.length + ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
(mqttFixedHeader.qosLevel().value() > 0 ? 2 : 0); ctx.alloc(),
int payloadBufferSize = payload.readableBytes(); message.variableHeader().properties());
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize); try {
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader)); int variableHeaderBufferSize = 2 + topicNameBytes +
writeVariableLengthInt(buf, variablePartSize); (mqttFixedHeader.qosLevel().value() > 0 ? 2 : 0) + propertiesBuf.readableBytes();
buf.writeShort(topicNameBytes.length); int payloadBufferSize = payload.readableBytes();
buf.writeBytes(topicNameBytes); int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
if (mqttFixedHeader.qosLevel().value() > 0) { int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
buf.writeShort(variableHeader.messageId());
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
writeExactUTF8String(buf, topicName, topicNameBytes);
if (mqttFixedHeader.qosLevel().value() > 0) {
buf.writeShort(variableHeader.packetId());
}
buf.writeBytes(propertiesBuf);
buf.writeBytes(payload);
return buf;
} finally {
propertiesBuf.release();
} }
buf.writeBytes(payload); }
return buf; private static ByteBuf encodePubReplyMessage(ChannelHandlerContext ctx,
MqttMessage message) {
if (message.variableHeader() instanceof MqttPubReplyMessageVariableHeader) {
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttPubReplyMessageVariableHeader variableHeader =
(MqttPubReplyMessageVariableHeader) message.variableHeader();
int msgId = variableHeader.messageId();
final ByteBuf propertiesBuf;
final boolean includeReasonCode;
final int variableHeaderBufferSize;
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
if (mqttVersion == MqttVersion.MQTT_5 &&
(variableHeader.reasonCode() != MqttPubReplyMessageVariableHeader.REASON_CODE_OK ||
!variableHeader.properties().isEmpty())) {
propertiesBuf = encodeProperties(ctx.alloc(), variableHeader.properties());
includeReasonCode = true;
variableHeaderBufferSize = 3 + propertiesBuf.readableBytes();
} else {
propertiesBuf = Unpooled.EMPTY_BUFFER;
includeReasonCode = false;
variableHeaderBufferSize = 2;
}
try {
final int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variableHeaderBufferSize);
buf.writeShort(msgId);
if (includeReasonCode) {
buf.writeByte(variableHeader.reasonCode());
}
buf.writeBytes(propertiesBuf);
return buf;
} finally {
propertiesBuf.release();
}
} else {
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
}
} }
private static ByteBuf encodeMessageWithOnlySingleByteFixedHeaderAndMessageId( private static ByteBuf encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(
@@ -342,12 +515,11 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
int msgId = variableHeader.messageId(); int msgId = variableHeader.messageId();
int variableHeaderBufferSize = 2; // variable part only has a message id int variableHeaderBufferSize = 2; // variable part only has a message id
ByteBuf payload = null; ByteBuf payload = null;
if (message.payload() != null) { if (message.payload() != null) {
payload = ((ByteBuf)message.payload()).duplicate(); payload = ((ByteBuf)message.payload()).duplicate();
variableHeaderBufferSize += payload.readableBytes(); variableHeaderBufferSize += payload.readableBytes();
} }
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize); int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variableHeaderBufferSize); ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader)); buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
@@ -355,11 +527,53 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
buf.writeShort(msgId); buf.writeShort(msgId);
if (payload != null) { if (payload != null) {
buf.writeBytes(payload); buf.writeBytes(payload);
} }
return buf; return buf;
} }
private static ByteBuf encodeReasonCodePlusPropertiesMessage(
ChannelHandlerContext ctx,
MqttMessage message) {
if (message.variableHeader() instanceof MqttReasonCodeAndPropertiesVariableHeader) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttReasonCodeAndPropertiesVariableHeader variableHeader =
(MqttReasonCodeAndPropertiesVariableHeader) message.variableHeader();
final ByteBuf propertiesBuf;
final boolean includeReasonCode;
final int variableHeaderBufferSize;
if (mqttVersion == MqttVersion.MQTT_5 &&
(variableHeader.reasonCode() != MqttReasonCodeAndPropertiesVariableHeader.REASON_CODE_OK ||
!variableHeader.properties().isEmpty())) {
propertiesBuf = encodeProperties(ctx.alloc(), variableHeader.properties());
includeReasonCode = true;
variableHeaderBufferSize = 1 + propertiesBuf.readableBytes();
} else {
propertiesBuf = Unpooled.EMPTY_BUFFER;
includeReasonCode = false;
variableHeaderBufferSize = 0;
}
try {
final int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variableHeaderBufferSize);
if (includeReasonCode) {
buf.writeByte(variableHeader.reasonCode());
}
buf.writeBytes(propertiesBuf);
return buf;
} finally {
propertiesBuf.release();
}
} else {
return encodeMessageWithOnlySingleByteFixedHeader(ctx.alloc(), message);
}
}
private static ByteBuf encodeMessageWithOnlySingleByteFixedHeader( private static ByteBuf encodeMessageWithOnlySingleByteFixedHeader(
ByteBufAllocator byteBufAllocator, ByteBufAllocator byteBufAllocator,
MqttMessage message) { MqttMessage message) {
@@ -371,6 +585,104 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
return buf; return buf;
} }
private static ByteBuf encodePropertiesIfNeeded(MqttVersion mqttVersion,
ByteBufAllocator byteBufAllocator,
MqttProperties mqttProperties) {
if (mqttVersion == MqttVersion.MQTT_5) {
return encodeProperties(byteBufAllocator, mqttProperties);
}
return Unpooled.EMPTY_BUFFER;
}
private static ByteBuf encodeProperties(ByteBufAllocator byteBufAllocator,
MqttProperties mqttProperties) {
ByteBuf propertiesHeaderBuf = byteBufAllocator.buffer();
// encode also the Properties part
try {
ByteBuf propertiesBuf = byteBufAllocator.buffer();
try {
for (MqttProperties.MqttProperty property : mqttProperties.listAll()) {
MqttProperties.MqttPropertyType propertyType =
MqttProperties.MqttPropertyType.valueOf(property.propertyId);
switch (propertyType) {
case PAYLOAD_FORMAT_INDICATOR:
case REQUEST_PROBLEM_INFORMATION:
case REQUEST_RESPONSE_INFORMATION:
case MAXIMUM_QOS:
case RETAIN_AVAILABLE:
case WILDCARD_SUBSCRIPTION_AVAILABLE:
case SUBSCRIPTION_IDENTIFIER_AVAILABLE:
case SHARED_SUBSCRIPTION_AVAILABLE:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final byte bytePropValue = ((MqttProperties.IntegerProperty) property).value.byteValue();
propertiesBuf.writeByte(bytePropValue);
break;
case SERVER_KEEP_ALIVE:
case RECEIVE_MAXIMUM:
case TOPIC_ALIAS_MAXIMUM:
case TOPIC_ALIAS:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final short twoBytesInPropValue =
((MqttProperties.IntegerProperty) property).value.shortValue();
propertiesBuf.writeShort(twoBytesInPropValue);
break;
case PUBLICATION_EXPIRY_INTERVAL:
case SESSION_EXPIRY_INTERVAL:
case WILL_DELAY_INTERVAL:
case MAXIMUM_PACKET_SIZE:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final int fourBytesIntPropValue = ((MqttProperties.IntegerProperty) property).value;
propertiesBuf.writeInt(fourBytesIntPropValue);
break;
case SUBSCRIPTION_IDENTIFIER:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final int vbi = ((MqttProperties.IntegerProperty) property).value;
writeVariableLengthInt(propertiesBuf, vbi);
break;
case CONTENT_TYPE:
case RESPONSE_TOPIC:
case ASSIGNED_CLIENT_IDENTIFIER:
case AUTHENTICATION_METHOD:
case RESPONSE_INFORMATION:
case SERVER_REFERENCE:
case REASON_STRING:
writeVariableLengthInt(propertiesBuf, property.propertyId);
writeEagerUTF8String(propertiesBuf, ((MqttProperties.StringProperty) property).value);
break;
case USER_PROPERTY:
final List<MqttProperties.StringPair> pairs =
((MqttProperties.UserProperties) property).value;
for (MqttProperties.StringPair pair : pairs) {
writeVariableLengthInt(propertiesBuf, property.propertyId);
writeEagerUTF8String(propertiesBuf, pair.key);
writeEagerUTF8String(propertiesBuf, pair.value);
}
break;
case CORRELATION_DATA:
case AUTHENTICATION_DATA:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final byte[] binaryPropValue = ((MqttProperties.BinaryProperty) property).value;
propertiesBuf.writeShort(binaryPropValue.length);
propertiesBuf.writeBytes(binaryPropValue, 0, binaryPropValue.length);
break;
default:
//shouldn't reach here
throw new EncoderException("Unknown property type: " + propertyType);
}
}
writeVariableLengthInt(propertiesHeaderBuf, propertiesBuf.readableBytes());
propertiesHeaderBuf.writeBytes(propertiesBuf);
return propertiesHeaderBuf;
} finally {
propertiesBuf.release();
}
} catch (RuntimeException e) {
propertiesHeaderBuf.release();
throw e;
}
}
private static int getFixedHeaderByte1(MqttFixedHeader header) { private static int getFixedHeaderByte1(MqttFixedHeader header) {
int ret = 0; int ret = 0;
ret |= header.messageType().value() << 4; ret |= header.messageType().value() << 4;
@@ -395,6 +707,42 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} while (num > 0); } while (num > 0);
} }
private static int nullableUtf8Bytes(String s) {
return s == null? 0 : utf8Bytes(s);
}
private static int nullableMaxUtf8Bytes(String s) {
return s == null? 0 : utf8MaxBytes(s);
}
private static void writeExactUTF8String(ByteBuf buf, String s, int utf8Length) {
buf.ensureWritable(utf8Length + 2);
buf.writeShort(utf8Length);
if (utf8Length > 0) {
final int writtenUtf8Length = reserveAndWriteUtf8(buf, s, utf8Length);
assert writtenUtf8Length == utf8Length;
}
}
private static void writeEagerUTF8String(ByteBuf buf, String s) {
final int maxUtf8Length = nullableMaxUtf8Bytes(s);
buf.ensureWritable(maxUtf8Length + 2);
final int writerIndex = buf.writerIndex();
final int startUtf8String = writerIndex + 2;
buf.writerIndex(startUtf8String);
final int utf8Length = s != null? reserveAndWriteUtf8(buf, s, maxUtf8Length) : 0;
buf.setShort(writerIndex, utf8Length);
}
private static void writeUnsafeUTF8String(ByteBuf buf, String s) {
final int writerIndex = buf.writerIndex();
final int startUtf8String = writerIndex + 2;
// no need to reserve any capacity here, already done earlier: that's why is Unsafe
buf.writerIndex(startUtf8String);
final int utf8Length = s != null? reserveAndWriteUtf8(buf, s, 0) : 0;
buf.setShort(writerIndex, utf8Length);
}
private static int getVariableLengthInt(int num) { private static int getVariableLengthInt(int num) {
int count = 0; int count = 0;
do { do {
@@ -404,7 +752,4 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
return count; return count;
} }
private static byte[] encodeStringUtf8(String s) {
return s.getBytes(CharsetUtil.UTF_8);
}
} }
+2 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -20,7 +20,7 @@ import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#fixed-header"> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#fixed-header">
* MQTTV3.1/fixed-header</a> * MQTTV3.1/fixed-header</a>
*/ */
public final class MqttFixedHeader { public final class MqttFixedHeader {
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+12 -1
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -29,6 +29,17 @@ public class MqttMessage {
private final Object payload; private final Object payload;
private final DecoderResult decoderResult; private final DecoderResult decoderResult;
// Constants for fixed-header only message types with all flags set to 0 (see
// https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_2.2_-)
public static final MqttMessage PINGREQ = new MqttMessage(new MqttFixedHeader(MqttMessageType.PINGREQ, false,
MqttQoS.AT_MOST_ONCE, false, 0));
public static final MqttMessage PINGRESP = new MqttMessage(new MqttFixedHeader(MqttMessageType.PINGRESP, false,
MqttQoS.AT_MOST_ONCE, false, 0));
public static final MqttMessage DISCONNECT = new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false,
MqttQoS.AT_MOST_ONCE, false, 0));
public MqttMessage(MqttFixedHeader mqttFixedHeader) { public MqttMessage(MqttFixedHeader mqttFixedHeader) {
this(mqttFixedHeader, null, null); this(mqttFixedHeader, null, null);
} }
+493 -14
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -15,10 +15,14 @@
*/ */
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
public final class MqttMessageBuilders { public final class MqttMessageBuilders {
@@ -29,6 +33,7 @@ public final class MqttMessageBuilders {
private MqttQoS qos; private MqttQoS qos;
private ByteBuf payload; private ByteBuf payload;
private int messageId; private int messageId;
private MqttProperties mqttProperties;
PublishBuilder() { PublishBuilder() {
} }
@@ -58,9 +63,15 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public PublishBuilder properties(MqttProperties properties) {
this.mqttProperties = properties;
return this;
}
public MqttPublishMessage build() { public MqttPublishMessage build() {
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retained, 0); MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retained, 0);
MqttPublishVariableHeader mqttVariableHeader = new MqttPublishVariableHeader(topic, messageId); MqttPublishVariableHeader mqttVariableHeader =
new MqttPublishVariableHeader(topic, messageId, mqttProperties);
return new MqttPublishMessage(mqttFixedHeader, mqttVariableHeader, Unpooled.buffer().writeBytes(payload)); return new MqttPublishMessage(mqttFixedHeader, mqttVariableHeader, Unpooled.buffer().writeBytes(payload));
} }
} }
@@ -73,13 +84,15 @@ public final class MqttMessageBuilders {
private boolean hasUser; private boolean hasUser;
private boolean hasPassword; private boolean hasPassword;
private int keepAliveSecs; private int keepAliveSecs;
private MqttProperties willProperties = MqttProperties.NO_PROPERTIES;
private boolean willFlag; private boolean willFlag;
private boolean willRetain; private boolean willRetain;
private MqttQoS willQos = MqttQoS.AT_MOST_ONCE; private MqttQoS willQos = MqttQoS.AT_MOST_ONCE;
private String willTopic; private String willTopic;
private String willMessage; private byte[] willMessage;
private String username; private String username;
private byte[] password; private byte[] password;
private MqttProperties properties = MqttProperties.NO_PROPERTIES;
ConnectBuilder() { ConnectBuilder() {
} }
@@ -119,7 +132,16 @@ public final class MqttMessageBuilders {
return this; return this;
} }
/**
* @deprecated use {@link ConnectBuilder#willMessage(byte[])} instead
*/
@Deprecated
public ConnectBuilder willMessage(String willMessage) { public ConnectBuilder willMessage(String willMessage) {
willMessage(willMessage == null ? null : willMessage.getBytes(CharsetUtil.UTF_8));
return this;
}
public ConnectBuilder willMessage(byte[] willMessage) {
this.willMessage = willMessage; this.willMessage = willMessage;
return this; return this;
} }
@@ -129,6 +151,11 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public ConnectBuilder willProperties(MqttProperties willProperties) {
this.willProperties = willProperties;
return this;
}
public ConnectBuilder hasUser(boolean value) { public ConnectBuilder hasUser(boolean value) {
this.hasUser = value; this.hasUser = value;
return this; return this;
@@ -140,17 +167,31 @@ public final class MqttMessageBuilders {
} }
public ConnectBuilder username(String username) { public ConnectBuilder username(String username) {
this.hasUser = true; this.hasUser = username != null;
this.username = username; this.username = username;
return this; return this;
} }
/**
* @deprecated use {@link ConnectBuilder#password(byte[])} instead
*/
@Deprecated
public ConnectBuilder password(String password) {
password(password == null ? null : password.getBytes(CharsetUtil.UTF_8));
return this;
}
public ConnectBuilder password(byte[] password) { public ConnectBuilder password(byte[] password) {
this.hasPassword = true; this.hasPassword = password != null;
this.password = password; this.password = password;
return this; return this;
} }
public ConnectBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttConnectMessage build() { public MqttConnectMessage build() {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
@@ -164,9 +205,10 @@ public final class MqttMessageBuilders {
willQos.value(), willQos.value(),
willFlag, willFlag,
cleanSession, cleanSession,
keepAliveSecs); keepAliveSecs,
properties);
MqttConnectPayload mqttConnectPayload = MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload(clientId, willTopic, willMessage, username, password); new MqttConnectPayload(clientId, willProperties, willTopic, willMessage, username, password);
return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload); return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload);
} }
} }
@@ -175,36 +217,54 @@ public final class MqttMessageBuilders {
private List<MqttTopicSubscription> subscriptions; private List<MqttTopicSubscription> subscriptions;
private int messageId; private int messageId;
private MqttProperties properties;
SubscribeBuilder() { SubscribeBuilder() {
} }
public SubscribeBuilder addSubscription(MqttQoS qos, String topic) { public SubscribeBuilder addSubscription(MqttQoS qos, String topic) {
if (subscriptions == null) { ensureSubscriptionsExist();
subscriptions = new ArrayList<MqttTopicSubscription>(5);
}
subscriptions.add(new MqttTopicSubscription(topic, qos)); subscriptions.add(new MqttTopicSubscription(topic, qos));
return this; return this;
} }
public SubscribeBuilder addSubscription(String topic, MqttSubscriptionOption option) {
ensureSubscriptionsExist();
subscriptions.add(new MqttTopicSubscription(topic, option));
return this;
}
public SubscribeBuilder messageId(int messageId) { public SubscribeBuilder messageId(int messageId) {
this.messageId = messageId; this.messageId = messageId;
return this; return this;
} }
public SubscribeBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttSubscribeMessage build() { public MqttSubscribeMessage build() {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(messageId); MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(messageId, properties);
MqttSubscribePayload mqttSubscribePayload = new MqttSubscribePayload(subscriptions); MqttSubscribePayload mqttSubscribePayload = new MqttSubscribePayload(subscriptions);
return new MqttSubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload); return new MqttSubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
} }
private void ensureSubscriptionsExist() {
if (subscriptions == null) {
subscriptions = new ArrayList<MqttTopicSubscription>(5);
}
}
} }
public static final class UnsubscribeBuilder { public static final class UnsubscribeBuilder {
private List<String> topicFilters; private List<String> topicFilters;
private int messageId; private int messageId;
private MqttProperties properties;
UnsubscribeBuilder() { UnsubscribeBuilder() {
} }
@@ -222,21 +282,33 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public UnsubscribeBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttUnsubscribeMessage build() { public MqttUnsubscribeMessage build() {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(messageId); MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(messageId, properties);
MqttUnsubscribePayload mqttSubscribePayload = new MqttUnsubscribePayload(topicFilters); MqttUnsubscribePayload mqttSubscribePayload = new MqttUnsubscribePayload(topicFilters);
return new MqttUnsubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload); return new MqttUnsubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
} }
} }
public interface PropertiesInitializer<T> {
void apply(T builder);
}
public static final class ConnAckBuilder { public static final class ConnAckBuilder {
private MqttConnectReturnCode returnCode; private MqttConnectReturnCode returnCode;
private boolean sessionPresent; private boolean sessionPresent;
private MqttProperties properties = MqttProperties.NO_PROPERTIES;
private ConnAckPropertiesBuilder propsBuilder;
ConnAckBuilder() { private ConnAckBuilder() {
} }
public ConnAckBuilder returnCode(MqttConnectReturnCode returnCode) { public ConnAckBuilder returnCode(MqttConnectReturnCode returnCode) {
@@ -249,15 +321,402 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public ConnAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public ConnAckBuilder properties(PropertiesInitializer<ConnAckPropertiesBuilder> consumer) {
if (propsBuilder == null) {
propsBuilder = new ConnAckPropertiesBuilder();
}
consumer.apply(propsBuilder);
return this;
}
public MqttConnAckMessage build() { public MqttConnAckMessage build() {
if (propsBuilder != null) {
properties = propsBuilder.build();
}
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttConnAckVariableHeader mqttConnAckVariableHeader = MqttConnAckVariableHeader mqttConnAckVariableHeader =
new MqttConnAckVariableHeader(returnCode, sessionPresent); new MqttConnAckVariableHeader(returnCode, sessionPresent, properties);
return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader);
} }
} }
public static final class ConnAckPropertiesBuilder {
private String clientId;
private Long sessionExpiryInterval;
private int receiveMaximum;
private Byte maximumQos;
private boolean retain;
private Long maximumPacketSize;
private int topicAliasMaximum;
private String reasonString;
private final MqttProperties.UserProperties userProperties = new MqttProperties.UserProperties();
private Boolean wildcardSubscriptionAvailable;
private Boolean subscriptionIdentifiersAvailable;
private Boolean sharedSubscriptionAvailable;
private Integer serverKeepAlive;
private String responseInformation;
private String serverReference;
private String authenticationMethod;
private byte[] authenticationData;
public MqttProperties build() {
final MqttProperties props = new MqttProperties();
if (clientId != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.ASSIGNED_CLIENT_IDENTIFIER.value(),
clientId));
}
if (sessionExpiryInterval != null) {
props.add(new MqttProperties.IntegerProperty(
MqttProperties.MqttPropertyType.SESSION_EXPIRY_INTERVAL.value(), sessionExpiryInterval.intValue()));
}
if (receiveMaximum > 0) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.RECEIVE_MAXIMUM.value(), receiveMaximum));
}
if (maximumQos != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.MAXIMUM_QOS.value(), receiveMaximum));
}
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.RETAIN_AVAILABLE.value(), retain ? 1 : 0));
if (maximumPacketSize != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.MAXIMUM_PACKET_SIZE.value(),
maximumPacketSize.intValue()));
}
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS_MAXIMUM.value(),
topicAliasMaximum));
if (reasonString != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.REASON_STRING.value(), reasonString));
}
props.add(userProperties);
if (wildcardSubscriptionAvailable != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.WILDCARD_SUBSCRIPTION_AVAILABLE.value(),
wildcardSubscriptionAvailable ? 1 : 0));
}
if (subscriptionIdentifiersAvailable != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SUBSCRIPTION_IDENTIFIER_AVAILABLE.value(),
subscriptionIdentifiersAvailable ? 1 : 0));
}
if (sharedSubscriptionAvailable != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SHARED_SUBSCRIPTION_AVAILABLE.value(),
sharedSubscriptionAvailable ? 1 : 0));
}
if (serverKeepAlive != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SERVER_KEEP_ALIVE.value(),
serverKeepAlive));
}
if (responseInformation != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.RESPONSE_INFORMATION.value(),
responseInformation));
}
if (serverReference != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.SERVER_REFERENCE.value(),
serverReference));
}
if (authenticationMethod != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_METHOD.value(),
authenticationMethod));
}
if (authenticationData != null) {
props.add(new MqttProperties.BinaryProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_DATA.value(),
authenticationData));
}
return props;
}
public ConnAckPropertiesBuilder sessionExpiryInterval(long seconds) {
this.sessionExpiryInterval = seconds;
return this;
}
public ConnAckPropertiesBuilder receiveMaximum(int value) {
this.receiveMaximum = checkPositive(value, "value");
return this;
}
public ConnAckPropertiesBuilder maximumQos(byte value) {
if (value != 0 && value != 1) {
throw new IllegalArgumentException("maximum QoS property could be 0 or 1");
}
this.maximumQos = value;
return this;
}
public ConnAckPropertiesBuilder retainAvailable(boolean retain) {
this.retain = retain;
return this;
}
public ConnAckPropertiesBuilder maximumPacketSize(long size) {
this.maximumPacketSize = checkPositive(size, "size");
return this;
}
public ConnAckPropertiesBuilder assignedClientId(String clientId) {
this.clientId = clientId;
return this;
}
public ConnAckPropertiesBuilder topicAliasMaximum(int value) {
this.topicAliasMaximum = value;
return this;
}
public ConnAckPropertiesBuilder reasonString(String reason) {
this.reasonString = reason;
return this;
}
public ConnAckPropertiesBuilder userProperty(String name, String value) {
userProperties.add(name, value);
return this;
}
public ConnAckPropertiesBuilder wildcardSubscriptionAvailable(boolean value) {
this.wildcardSubscriptionAvailable = value;
return this;
}
public ConnAckPropertiesBuilder subscriptionIdentifiersAvailable(boolean value) {
this.subscriptionIdentifiersAvailable = value;
return this;
}
public ConnAckPropertiesBuilder sharedSubscriptionAvailable(boolean value) {
this.sharedSubscriptionAvailable = value;
return this;
}
public ConnAckPropertiesBuilder serverKeepAlive(int seconds) {
this.serverKeepAlive = seconds;
return this;
}
public ConnAckPropertiesBuilder responseInformation(String value) {
this.responseInformation = value;
return this;
}
public ConnAckPropertiesBuilder serverReference(String host) {
this.serverReference = host;
return this;
}
public ConnAckPropertiesBuilder authenticationMethod(String methodName) {
this.authenticationMethod = methodName;
return this;
}
public ConnAckPropertiesBuilder authenticationData(byte[] rawData) {
this.authenticationData = rawData.clone();
return this;
}
}
public static final class PubAckBuilder {
private int packetId;
private byte reasonCode;
private MqttProperties properties;
PubAckBuilder() {
}
public PubAckBuilder reasonCode(byte reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public PubAckBuilder packetId(int packetId) {
this.packetId = packetId;
return this;
}
/**
* @deprecated use {@link PubAckBuilder#packetId(int)} instead
*/
@Deprecated
public PubAckBuilder packetId(short packetId) {
return packetId(packetId & 0xFFFF);
}
public PubAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttPubReplyMessageVariableHeader mqttPubAckVariableHeader =
new MqttPubReplyMessageVariableHeader(packetId, reasonCode, properties);
return new MqttMessage(mqttFixedHeader, mqttPubAckVariableHeader);
}
}
public static final class SubAckBuilder {
private int packetId;
private MqttProperties properties;
private final List<MqttQoS> grantedQoses = new ArrayList<MqttQoS>();
SubAckBuilder() {
}
public SubAckBuilder packetId(int packetId) {
this.packetId = packetId;
return this;
}
/**
* @deprecated use {@link SubAckBuilder#packetId(int)} instead
*/
@Deprecated
public SubAckBuilder packetId(short packetId) {
return packetId(packetId & 0xFFFF);
}
public SubAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public SubAckBuilder addGrantedQos(MqttQoS qos) {
this.grantedQoses.add(qos);
return this;
}
public SubAckBuilder addGrantedQoses(MqttQoS... qoses) {
this.grantedQoses.addAll(Arrays.asList(qoses));
return this;
}
public MqttSubAckMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttMessageIdAndPropertiesVariableHeader mqttSubAckVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(packetId, properties);
//transform to primitive types
int[] grantedQoses = new int[this.grantedQoses.size()];
int i = 0;
for (MqttQoS grantedQos : this.grantedQoses) {
grantedQoses[i++] = grantedQos.value();
}
MqttSubAckPayload subAckPayload = new MqttSubAckPayload(grantedQoses);
return new MqttSubAckMessage(mqttFixedHeader, mqttSubAckVariableHeader, subAckPayload);
}
}
public static final class UnsubAckBuilder {
private int packetId;
private MqttProperties properties;
private final List<Short> reasonCodes = new ArrayList<Short>();
UnsubAckBuilder() {
}
public UnsubAckBuilder packetId(int packetId) {
this.packetId = packetId;
return this;
}
/**
* @deprecated use {@link UnsubAckBuilder#packetId(int)} instead
*/
@Deprecated
public UnsubAckBuilder packetId(short packetId) {
return packetId(packetId & 0xFFFF);
}
public UnsubAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public UnsubAckBuilder addReasonCode(short reasonCode) {
this.reasonCodes.add(reasonCode);
return this;
}
public UnsubAckBuilder addReasonCodes(Short... reasonCodes) {
this.reasonCodes.addAll(Arrays.asList(reasonCodes));
return this;
}
public MqttUnsubAckMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.UNSUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttMessageIdAndPropertiesVariableHeader mqttSubAckVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(packetId, properties);
MqttUnsubAckPayload subAckPayload = new MqttUnsubAckPayload(reasonCodes);
return new MqttUnsubAckMessage(mqttFixedHeader, mqttSubAckVariableHeader, subAckPayload);
}
}
public static final class DisconnectBuilder {
private MqttProperties properties;
private byte reasonCode;
DisconnectBuilder() {
}
public DisconnectBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public DisconnectBuilder reasonCode(byte reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public MqttMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttReasonCodeAndPropertiesVariableHeader mqttDisconnectVariableHeader =
new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, properties);
return new MqttMessage(mqttFixedHeader, mqttDisconnectVariableHeader);
}
}
public static final class AuthBuilder {
private MqttProperties properties;
private byte reasonCode;
AuthBuilder() {
}
public AuthBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public AuthBuilder reasonCode(byte reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public MqttMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.AUTH, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttReasonCodeAndPropertiesVariableHeader mqttAuthVariableHeader =
new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, properties);
return new MqttMessage(mqttFixedHeader, mqttAuthVariableHeader);
}
}
public static ConnectBuilder connect() { public static ConnectBuilder connect() {
return new ConnectBuilder(); return new ConnectBuilder();
} }
@@ -278,6 +737,26 @@ public final class MqttMessageBuilders {
return new UnsubscribeBuilder(); return new UnsubscribeBuilder();
} }
public static PubAckBuilder pubAck() {
return new PubAckBuilder();
}
public static SubAckBuilder subAck() {
return new SubAckBuilder();
}
public static UnsubAckBuilder unsubAck() {
return new UnsubAckBuilder();
}
public static DisconnectBuilder disconnect() {
return new DisconnectBuilder();
}
public static AuthBuilder auth() {
return new AuthBuilder();
}
private MqttMessageBuilders() { private MqttMessageBuilders() {
} }
} }
+16 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -50,7 +50,8 @@ public final class MqttMessageFactory {
case UNSUBACK: case UNSUBACK:
return new MqttUnsubAckMessage( return new MqttUnsubAckMessage(
mqttFixedHeader, mqttFixedHeader,
(MqttMessageIdVariableHeader) variableHeader); (MqttMessageIdVariableHeader) variableHeader,
(MqttUnsubAckPayload) payload);
case UNSUBSCRIBE: case UNSUBSCRIBE:
return new MqttUnsubscribeMessage( return new MqttUnsubscribeMessage(
@@ -65,17 +66,24 @@ public final class MqttMessageFactory {
(ByteBuf) payload); (ByteBuf) payload);
case PUBACK: case PUBACK:
//Having MqttPubReplyMessageVariableHeader or MqttMessageIdVariableHeader
return new MqttPubAckMessage(mqttFixedHeader, (MqttMessageIdVariableHeader) variableHeader); return new MqttPubAckMessage(mqttFixedHeader, (MqttMessageIdVariableHeader) variableHeader);
case PUBREC: case PUBREC:
case PUBREL: case PUBREL:
case PUBCOMP: case PUBCOMP:
//Having MqttPubReplyMessageVariableHeader or MqttMessageIdVariableHeader
return new MqttMessage(mqttFixedHeader, variableHeader); return new MqttMessage(mqttFixedHeader, variableHeader);
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
case DISCONNECT:
return new MqttMessage(mqttFixedHeader); return new MqttMessage(mqttFixedHeader);
case DISCONNECT:
case AUTH:
//Having MqttReasonCodeAndPropertiesVariableHeader
return new MqttMessage(mqttFixedHeader,
variableHeader);
default: default:
throw new IllegalArgumentException("unknown message type: " + mqttFixedHeader.messageType()); throw new IllegalArgumentException("unknown message type: " + mqttFixedHeader.messageType());
} }
@@ -85,5 +93,10 @@ public final class MqttMessageFactory {
return new MqttMessage(null, null, null, DecoderResult.failure(cause)); return new MqttMessage(null, null, null, DecoderResult.failure(cause));
} }
public static MqttMessage newInvalidMessage(MqttFixedHeader mqttFixedHeader, Object variableHeader,
Throwable cause) {
return new MqttMessage(mqttFixedHeader, variableHeader, null, DecoderResult.failure(cause));
}
private MqttMessageFactory() { } private MqttMessageFactory() { }
} }
@@ -0,0 +1,52 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.StringUtil;
/**
* Variable Header containing, Packet Id and Properties as in MQTT v5 spec.
*/
public final class MqttMessageIdAndPropertiesVariableHeader extends MqttMessageIdVariableHeader {
private final MqttProperties properties;
public MqttMessageIdAndPropertiesVariableHeader(int messageId, MqttProperties properties) {
super(messageId);
if (messageId < 1 || messageId > 0xffff) {
throw new IllegalArgumentException("messageId: " + messageId + " (expected: 1 ~ 65535)");
}
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
return StringUtil.simpleClassName(this) + "[" +
"messageId=" + messageId() +
", properties=" + properties +
']';
}
@Override
MqttMessageIdAndPropertiesVariableHeader withDefaultEmptyProperties() {
return this;
}
}
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -20,9 +20,9 @@ import io.netty.util.internal.StringUtil;
/** /**
* Variable Header containing only Message Id * Variable Header containing only Message Id
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#msg-id">MQTTV3.1/msg-id</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#msg-id">MQTTV3.1/msg-id</a>
*/ */
public final class MqttMessageIdVariableHeader { public class MqttMessageIdVariableHeader {
private final int messageId; private final int messageId;
@@ -33,7 +33,7 @@ public final class MqttMessageIdVariableHeader {
return new MqttMessageIdVariableHeader(messageId); return new MqttMessageIdVariableHeader(messageId);
} }
private MqttMessageIdVariableHeader(int messageId) { protected MqttMessageIdVariableHeader(int messageId) {
this.messageId = messageId; this.messageId = messageId;
} }
@@ -49,4 +49,12 @@ public final class MqttMessageIdVariableHeader {
.append(']') .append(']')
.toString(); .toString();
} }
public MqttMessageIdAndPropertiesVariableHeader withEmptyProperties() {
return new MqttMessageIdAndPropertiesVariableHeader(messageId, MqttProperties.NO_PROPERTIES);
}
MqttMessageIdAndPropertiesVariableHeader withDefaultEmptyProperties() {
return withEmptyProperties();
}
} }
+22 -7
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -33,7 +33,24 @@ public enum MqttMessageType {
UNSUBACK(11), UNSUBACK(11),
PINGREQ(12), PINGREQ(12),
PINGRESP(13), PINGRESP(13),
DISCONNECT(14); DISCONNECT(14),
AUTH(15);
private static final MqttMessageType[] VALUES;
static {
// this prevent values to be assigned with the wrong order
// and ensure valueOf to work fine
final MqttMessageType[] values = values();
VALUES = new MqttMessageType[values.length + 1];
for (MqttMessageType mqttMessageType : values) {
final int value = mqttMessageType.value;
if (VALUES[value] != null) {
throw new AssertionError("value already in use: " + value);
}
VALUES[value] = mqttMessageType;
}
}
private final int value; private final int value;
@@ -46,12 +63,10 @@ public enum MqttMessageType {
} }
public static MqttMessageType valueOf(int type) { public static MqttMessageType valueOf(int type) {
for (MqttMessageType t : values()) { if (type <= 0 || type >= VALUES.length) {
if (t.value == type) { throw new IllegalArgumentException("unknown message type: " + type);
return t;
}
} }
throw new IllegalArgumentException("unknown message type: " + type); return VALUES[type];
} }
} }
@@ -0,0 +1,414 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.collection.IntObjectHashMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
/**
* MQTT Properties container
* */
public final class MqttProperties {
public enum MqttPropertyType {
// single byte properties
PAYLOAD_FORMAT_INDICATOR(0x01),
REQUEST_PROBLEM_INFORMATION(0x17),
REQUEST_RESPONSE_INFORMATION(0x19),
MAXIMUM_QOS(0x24),
RETAIN_AVAILABLE(0x25),
WILDCARD_SUBSCRIPTION_AVAILABLE(0x28),
SUBSCRIPTION_IDENTIFIER_AVAILABLE(0x29),
SHARED_SUBSCRIPTION_AVAILABLE(0x2A),
// two bytes properties
SERVER_KEEP_ALIVE(0x13),
RECEIVE_MAXIMUM(0x21),
TOPIC_ALIAS_MAXIMUM(0x22),
TOPIC_ALIAS(0x23),
// four bytes properties
PUBLICATION_EXPIRY_INTERVAL(0x02),
SESSION_EXPIRY_INTERVAL(0x11),
WILL_DELAY_INTERVAL(0x18),
MAXIMUM_PACKET_SIZE(0x27),
// Variable Byte Integer
SUBSCRIPTION_IDENTIFIER(0x0B),
// UTF-8 Encoded String properties
CONTENT_TYPE(0x03),
RESPONSE_TOPIC(0x08),
ASSIGNED_CLIENT_IDENTIFIER(0x12),
AUTHENTICATION_METHOD(0x15),
RESPONSE_INFORMATION(0x1A),
SERVER_REFERENCE(0x1C),
REASON_STRING(0x1F),
USER_PROPERTY(0x26),
// Binary Data
CORRELATION_DATA(0x09),
AUTHENTICATION_DATA(0x16);
private static final MqttPropertyType[] VALUES;
static {
VALUES = new MqttPropertyType[43];
for (MqttPropertyType v : values()) {
VALUES[v.value] = v;
}
}
private final int value;
MqttPropertyType(int value) {
this.value = value;
}
public int value() {
return value;
}
public static MqttPropertyType valueOf(int type) {
MqttPropertyType t = null;
try {
t = VALUES[type];
} catch (ArrayIndexOutOfBoundsException ignored) {
// nop
}
if (t == null) {
throw new IllegalArgumentException("unknown property type: " + type);
}
return t;
}
}
public static final MqttProperties NO_PROPERTIES = new MqttProperties(false);
static MqttProperties withEmptyDefaults(MqttProperties properties) {
if (properties == null) {
return MqttProperties.NO_PROPERTIES;
}
return properties;
}
/**
* MQTT property base class
*
* @param <T> property type
*/
public abstract static class MqttProperty<T> {
final T value;
final int propertyId;
protected MqttProperty(int propertyId, T value) {
this.propertyId = propertyId;
this.value = value;
}
/**
* Get MQTT property value
*
* @return property value
*/
public T value() {
return value;
}
/**
* Get MQTT property ID
* @return property ID
*/
public int propertyId() {
return propertyId;
}
@Override
public int hashCode() {
return propertyId + 31 * value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MqttProperty that = (MqttProperty) obj;
return this.propertyId == that.propertyId && this.value.equals(that.value);
}
}
public static final class IntegerProperty extends MqttProperty<Integer> {
public IntegerProperty(int propertyId, Integer value) {
super(propertyId, value);
}
@Override
public String toString() {
return "IntegerProperty(" + propertyId + ", " + value + ")";
}
}
public static final class StringProperty extends MqttProperty<String> {
public StringProperty(int propertyId, String value) {
super(propertyId, value);
}
@Override
public String toString() {
return "StringProperty(" + propertyId + ", " + value + ")";
}
}
public static final class StringPair {
public final String key;
public final String value;
public StringPair(String key, String value) {
this.key = key;
this.value = value;
}
@Override
public int hashCode() {
return key.hashCode() + 31 * value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
StringPair that = (StringPair) obj;
return that.key.equals(this.key) && that.value.equals(this.value);
}
}
//User properties are the only properties that may be included multiple times and
//are the only properties where ordering is required. Therefore, they need a special handling
public static final class UserProperties extends MqttProperty<List<StringPair>> {
public UserProperties() {
super(MqttPropertyType.USER_PROPERTY.value, new ArrayList<StringPair>());
}
/**
* Create user properties from the collection of the String pair values
*
* @param values string pairs. Collection entries are copied, collection itself isn't shared
*/
public UserProperties(Collection<StringPair> values) {
this();
this.value.addAll(values);
}
private static UserProperties fromUserPropertyCollection(Collection<UserProperty> properties) {
UserProperties userProperties = new UserProperties();
for (UserProperty property: properties) {
userProperties.add(new StringPair(property.value.key, property.value.value));
}
return userProperties;
}
public void add(StringPair pair) {
this.value.add(pair);
}
public void add(String key, String value) {
this.value.add(new StringPair(key, value));
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("UserProperties(");
boolean first = true;
for (StringPair pair: value) {
if (!first) {
builder.append(", ");
}
builder.append(pair.key + "->" + pair.value);
first = false;
}
builder.append(")");
return builder.toString();
}
}
public static final class UserProperty extends MqttProperty<StringPair> {
public UserProperty(String key, String value) {
super(MqttPropertyType.USER_PROPERTY.value, new StringPair(key, value));
}
@Override
public String toString() {
return "UserProperty(" + value.key + ", " + value.value + ")";
}
}
public static final class BinaryProperty extends MqttProperty<byte[]> {
public BinaryProperty(int propertyId, byte[] value) {
super(propertyId, value);
}
@Override
public String toString() {
return "BinaryProperty(" + propertyId + ", " + value.length + " bytes)";
}
}
public MqttProperties() {
this(true);
}
private MqttProperties(boolean canModify) {
this.canModify = canModify;
}
private IntObjectHashMap<MqttProperty> props;
private List<UserProperty> userProperties;
private List<IntegerProperty> subscriptionIds;
private final boolean canModify;
public void add(MqttProperty property) {
if (!canModify) {
throw new UnsupportedOperationException("adding property isn't allowed");
}
IntObjectHashMap<MqttProperty> props = this.props;
if (property.propertyId == MqttPropertyType.USER_PROPERTY.value) {
List<UserProperty> userProperties = this.userProperties;
if (userProperties == null) {
userProperties = new ArrayList<UserProperty>(1);
this.userProperties = userProperties;
}
if (property instanceof UserProperty) {
userProperties.add((UserProperty) property);
} else if (property instanceof UserProperties) {
for (StringPair pair: ((UserProperties) property).value) {
userProperties.add(new UserProperty(pair.key, pair.value));
}
} else {
throw new IllegalArgumentException("User property must be of UserProperty or UserProperties type");
}
} else if (property.propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
List<IntegerProperty> subscriptionIds = this.subscriptionIds;
if (subscriptionIds == null) {
subscriptionIds = new ArrayList<IntegerProperty>(1);
this.subscriptionIds = subscriptionIds;
}
if (property instanceof IntegerProperty) {
subscriptionIds.add((IntegerProperty) property);
} else {
throw new IllegalArgumentException("Subscription ID must be an integer property");
}
} else {
if (props == null) {
props = new IntObjectHashMap<MqttProperty>();
this.props = props;
}
props.put(property.propertyId, property);
}
}
public Collection<? extends MqttProperty> listAll() {
IntObjectHashMap<MqttProperty> props = this.props;
if (props == null && subscriptionIds == null && userProperties == null) {
return Collections.<MqttProperty>emptyList();
}
if (subscriptionIds == null && userProperties == null) {
return props.values();
}
if (props == null && userProperties == null) {
return subscriptionIds;
}
List<MqttProperty> propValues = new ArrayList<MqttProperty>(props != null ? props.size() : 1);
if (props != null) {
propValues.addAll(props.values());
}
if (subscriptionIds != null) {
propValues.addAll(subscriptionIds);
}
if (userProperties != null) {
propValues.add(UserProperties.fromUserPropertyCollection(userProperties));
}
return propValues;
}
public boolean isEmpty() {
IntObjectHashMap<MqttProperty> props = this.props;
return props == null || props.isEmpty();
}
/**
* Get property by ID. If there are multiple properties of this type (can be with Subscription ID)
* then return the first one.
*
* @param propertyId ID of the property
* @return a property if it is set, null otherwise
*/
public MqttProperty getProperty(int propertyId) {
if (propertyId == MqttPropertyType.USER_PROPERTY.value) {
//special handling to keep compatibility with earlier versions
List<UserProperty> userProperties = this.userProperties;
if (userProperties == null) {
return null;
}
return UserProperties.fromUserPropertyCollection(userProperties);
}
if (propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
List<IntegerProperty> subscriptionIds = this.subscriptionIds;
if (subscriptionIds == null || subscriptionIds.isEmpty()) {
return null;
}
return subscriptionIds.get(0);
}
IntObjectHashMap<MqttProperty> props = this.props;
return props == null ? null : props.get(propertyId);
}
/**
* Get properties by ID.
* Some properties (Subscription ID and User Properties) may occur multiple times,
* this method returns all their values in order.
*
* @param propertyId ID of the property
* @return all properties having specified ID
*/
public List<? extends MqttProperty> getProperties(int propertyId) {
if (propertyId == MqttPropertyType.USER_PROPERTY.value) {
return userProperties == null ? Collections.<MqttProperty>emptyList() : userProperties;
}
if (propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
return subscriptionIds == null ? Collections.<MqttProperty>emptyList() : subscriptionIds;
}
IntObjectHashMap<MqttProperty> props = this.props;
return (props == null || !props.containsKey(propertyId)) ?
Collections.<MqttProperty>emptyList() :
Collections.singletonList(props.get(propertyId));
}
}
+2 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#puback">MQTTV3.1/puback</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#puback">MQTTV3.1/puback</a>
*/ */
public final class MqttPubAckMessage extends MqttMessage { public final class MqttPubAckMessage extends MqttMessage {
@@ -0,0 +1,56 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.StringUtil;
/**
* Variable Header containing Packet Id, reason code and Properties as in MQTT v5 spec.
*/
public final class MqttPubReplyMessageVariableHeader extends MqttMessageIdVariableHeader {
private final byte reasonCode;
private final MqttProperties properties;
public static final byte REASON_CODE_OK = 0;
public MqttPubReplyMessageVariableHeader(int messageId, byte reasonCode, MqttProperties properties) {
super(messageId);
if (messageId < 1 || messageId > 0xffff) {
throw new IllegalArgumentException("messageId: " + messageId + " (expected: 1 ~ 65535)");
}
this.reasonCode = reasonCode;
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public byte reasonCode() {
return reasonCode;
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
return StringUtil.simpleClassName(this) + "[" +
"messageId=" + messageId() +
", reasonCode=" + reasonCode +
", properties=" + properties +
']';
}
}
+4 -8
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -18,10 +18,10 @@ package io.netty.handler.codec.mqtt;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder; import io.netty.buffer.ByteBufHolder;
import io.netty.util.IllegalReferenceCountException; import io.netty.buffer.ByteBufUtil;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#publish">MQTTV3.1/publish</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#publish">MQTTV3.1/publish</a>
*/ */
public class MqttPublishMessage extends MqttMessage implements ByteBufHolder { public class MqttPublishMessage extends MqttMessage implements ByteBufHolder {
@@ -44,11 +44,7 @@ public class MqttPublishMessage extends MqttMessage implements ByteBufHolder {
@Override @Override
public ByteBuf content() { public ByteBuf content() {
final ByteBuf data = (ByteBuf) super.payload(); return ByteBufUtil.ensureAccessible((ByteBuf) super.payload());
if (data.refCnt() <= 0) {
throw new IllegalReferenceCountException(data.refCnt());
}
return data;
} }
@Override @Override
+16 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -24,11 +24,17 @@ import io.netty.util.internal.StringUtil;
public final class MqttPublishVariableHeader { public final class MqttPublishVariableHeader {
private final String topicName; private final String topicName;
private final int packetId; private int packetId;
private final MqttProperties properties;
public MqttPublishVariableHeader(String topicName, int packetId) { public MqttPublishVariableHeader(String topicName, int packetId) {
this(topicName, packetId, MqttProperties.NO_PROPERTIES);
}
public MqttPublishVariableHeader(String topicName, int packetId, MqttProperties properties) {
this.topicName = topicName; this.topicName = topicName;
this.packetId = packetId; this.packetId = packetId;
this.properties = MqttProperties.withEmptyDefaults(properties);
} }
public String topicName() { public String topicName() {
@@ -47,6 +53,14 @@ public final class MqttPublishVariableHeader {
return packetId; return packetId;
} }
public MqttProperties properties() {
return properties;
}
public void setPacketId(int packetId) {
this.packetId = packetId;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
+12 -6
View File
@@ -5,7 +5,7 @@
* "License"); you may not use this file except in compliance with the License. You may obtain a * "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at: * copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License * Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
@@ -32,11 +32,17 @@ public enum MqttQoS {
} }
public static MqttQoS valueOf(int value) { public static MqttQoS valueOf(int value) {
for (MqttQoS q: values()) { switch (value) {
if (q.value == value) { case 0:
return q; return AT_MOST_ONCE;
} case 1:
return AT_LEAST_ONCE;
case 2:
return EXACTLY_ONCE;
case 0x80:
return FAILURE;
default:
throw new IllegalArgumentException("invalid QoS: " + value);
} }
throw new IllegalArgumentException("invalid QoS: " + value);
} }
} }
@@ -0,0 +1,54 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.StringUtil;
/**
* Variable Header for AUTH and DISCONNECT messages represented by {@link MqttMessage}
*/
public final class MqttReasonCodeAndPropertiesVariableHeader {
private final byte reasonCode;
private final MqttProperties properties;
public static final byte REASON_CODE_OK = 0;
public MqttReasonCodeAndPropertiesVariableHeader(byte reasonCode,
MqttProperties properties) {
this.reasonCode = reasonCode;
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public byte reasonCode() {
return reasonCode;
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this))
.append('[')
.append("reasonCode=").append(reasonCode)
.append(", properties=").append(properties)
.append(']')
.toString();
}
}
+14 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,22 +17,33 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#suback">MQTTV3.1/suback</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#suback">MQTTV3.1/suback</a>
*/ */
public final class MqttSubAckMessage extends MqttMessage { public final class MqttSubAckMessage extends MqttMessage {
public MqttSubAckMessage( public MqttSubAckMessage(
MqttFixedHeader mqttFixedHeader, MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader, MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttSubAckPayload payload) { MqttSubAckPayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
public MqttSubAckMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttSubAckPayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override @Override
public MqttSubAckPayload payload() { public MqttSubAckPayload payload() {
return (MqttSubAckPayload) super.payload(); return (MqttSubAckPayload) super.payload();
+26 -17
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,6 +16,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import java.util.ArrayList; import java.util.ArrayList;
@@ -27,43 +28,51 @@ import java.util.List;
*/ */
public class MqttSubAckPayload { public class MqttSubAckPayload {
private final List<Integer> grantedQoSLevels; private final List<Integer> reasonCodes;
public MqttSubAckPayload(int... grantedQoSLevels) { public MqttSubAckPayload(int... reasonCodes) {
if (grantedQoSLevels == null) { ObjectUtil.checkNotNull(reasonCodes, "reasonCodes");
throw new NullPointerException("grantedQoSLevels");
}
List<Integer> list = new ArrayList<Integer>(grantedQoSLevels.length); List<Integer> list = new ArrayList<Integer>(reasonCodes.length);
for (int v: grantedQoSLevels) { for (int v: reasonCodes) {
list.add(v); list.add(v);
} }
this.grantedQoSLevels = Collections.unmodifiableList(list); this.reasonCodes = Collections.unmodifiableList(list);
} }
public MqttSubAckPayload(Iterable<Integer> grantedQoSLevels) { public MqttSubAckPayload(Iterable<Integer> reasonCodes) {
if (grantedQoSLevels == null) { ObjectUtil.checkNotNull(reasonCodes, "reasonCodes");
throw new NullPointerException("grantedQoSLevels");
}
List<Integer> list = new ArrayList<Integer>(); List<Integer> list = new ArrayList<Integer>();
for (Integer v: grantedQoSLevels) { for (Integer v: reasonCodes) {
if (v == null) { if (v == null) {
break; break;
} }
list.add(v); list.add(v);
} }
this.grantedQoSLevels = Collections.unmodifiableList(list); this.reasonCodes = Collections.unmodifiableList(list);
} }
public List<Integer> grantedQoSLevels() { public List<Integer> grantedQoSLevels() {
return grantedQoSLevels; List<Integer> qosLevels = new ArrayList<Integer>(reasonCodes.size());
for (int code: reasonCodes) {
if (code > MqttQoS.EXACTLY_ONCE.value()) {
qosLevels.add(MqttQoS.FAILURE.value());
} else {
qosLevels.add(code);
}
}
return qosLevels;
}
public List<Integer> reasonCodes() {
return reasonCodes;
} }
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
.append('[') .append('[')
.append("grantedQoSLevels=").append(grantedQoSLevels) .append("reasonCodes=").append(reasonCodes)
.append(']') .append(']')
.toString(); .toString();
} }
+14 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,23 +17,34 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#subscribe"> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#subscribe">
* MQTTV3.1/subscribe</a> * MQTTV3.1/subscribe</a>
*/ */
public final class MqttSubscribeMessage extends MqttMessage { public final class MqttSubscribeMessage extends MqttMessage {
public MqttSubscribeMessage( public MqttSubscribeMessage(
MqttFixedHeader mqttFixedHeader, MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader, MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttSubscribePayload payload) { MqttSubscribePayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
public MqttSubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttSubscribePayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override @Override
public MqttSubscribePayload payload() { public MqttSubscribePayload payload() {
return (MqttSubscribePayload) super.payload(); return (MqttSubscribePayload) super.payload();
+6 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -39,11 +39,12 @@ public final class MqttSubscribePayload {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('['); StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
for (int i = 0; i < topicSubscriptions.size() - 1; i++) { for (int i = 0; i < topicSubscriptions.size(); i++) {
builder.append(topicSubscriptions.get(i)).append(", "); builder.append(topicSubscriptions.get(i)).append(", ");
} }
builder.append(topicSubscriptions.get(topicSubscriptions.size() - 1)); if (!topicSubscriptions.isEmpty()) {
builder.append(']'); builder.setLength(builder.length() - 2);
return builder.toString(); }
return builder.append(']').toString();
} }
} }
@@ -0,0 +1,128 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
/**
* Model the SubscriptionOption used in Subscribe MQTT v5 packet
*/
public final class MqttSubscriptionOption {
public enum RetainedHandlingPolicy {
SEND_AT_SUBSCRIBE(0),
SEND_AT_SUBSCRIBE_IF_NOT_YET_EXISTS(1),
DONT_SEND_AT_SUBSCRIBE(2);
private final int value;
RetainedHandlingPolicy(int value) {
this.value = value;
}
public int value() {
return value;
}
public static RetainedHandlingPolicy valueOf(int value) {
switch (value) {
case 0:
return SEND_AT_SUBSCRIBE;
case 1:
return SEND_AT_SUBSCRIBE_IF_NOT_YET_EXISTS;
case 2:
return DONT_SEND_AT_SUBSCRIBE;
default:
throw new IllegalArgumentException("invalid RetainedHandlingPolicy: " + value);
}
}
}
private final MqttQoS qos;
private final boolean noLocal;
private final boolean retainAsPublished;
private final RetainedHandlingPolicy retainHandling;
public static MqttSubscriptionOption onlyFromQos(MqttQoS qos) {
return new MqttSubscriptionOption(qos, false, false, RetainedHandlingPolicy.SEND_AT_SUBSCRIBE);
}
public MqttSubscriptionOption(MqttQoS qos,
boolean noLocal,
boolean retainAsPublished,
RetainedHandlingPolicy retainHandling) {
this.qos = qos;
this.noLocal = noLocal;
this.retainAsPublished = retainAsPublished;
this.retainHandling = retainHandling;
}
public MqttQoS qos() {
return qos;
}
public boolean isNoLocal() {
return noLocal;
}
public boolean isRetainAsPublished() {
return retainAsPublished;
}
public RetainedHandlingPolicy retainHandling() {
return retainHandling;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MqttSubscriptionOption that = (MqttSubscriptionOption) o;
if (noLocal != that.noLocal) {
return false;
}
if (retainAsPublished != that.retainAsPublished) {
return false;
}
if (qos != that.qos) {
return false;
}
return retainHandling == that.retainHandling;
}
@Override
public int hashCode() {
int result = qos.hashCode();
result = 31 * result + (noLocal ? 1 : 0);
result = 31 * result + (retainAsPublished ? 1 : 0);
result = 31 * result + retainHandling.hashCode();
return result;
}
@Override
public String toString() {
return "SubscriptionOption[" +
"qos=" + qos +
", noLocal=" + noLocal +
", retainAsPublished=" + retainAsPublished +
", retainHandling=" + retainHandling +
']';
}
}
+14 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -25,11 +25,16 @@ import io.netty.util.internal.StringUtil;
public final class MqttTopicSubscription { public final class MqttTopicSubscription {
private final String topicFilter; private final String topicFilter;
private final MqttQoS qualityOfService; private final MqttSubscriptionOption option;
public MqttTopicSubscription(String topicFilter, MqttQoS qualityOfService) { public MqttTopicSubscription(String topicFilter, MqttQoS qualityOfService) {
this.topicFilter = topicFilter; this.topicFilter = topicFilter;
this.qualityOfService = qualityOfService; this.option = MqttSubscriptionOption.onlyFromQos(qualityOfService);
}
public MqttTopicSubscription(String topicFilter, MqttSubscriptionOption option) {
this.topicFilter = topicFilter;
this.option = option;
} }
public String topicName() { public String topicName() {
@@ -37,7 +42,11 @@ public final class MqttTopicSubscription {
} }
public MqttQoS qualityOfService() { public MqttQoS qualityOfService() {
return qualityOfService; return option.qos();
}
public MqttSubscriptionOption option() {
return option;
} }
@Override @Override
@@ -45,7 +54,7 @@ public final class MqttTopicSubscription {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
.append('[') .append('[')
.append("topicFilter=").append(topicFilter) .append("topicFilter=").append(topicFilter)
.append(", qualityOfService=").append(qualityOfService) .append(", option=").append(this.option)
.append(']') .append(']')
.toString(); .toString();
} }
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+34 -4
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,16 +17,46 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsuback">MQTTV3.1/unsuback</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsuback">
* MQTTV3.1/unsuback</a>
*/ */
public final class MqttUnsubAckMessage extends MqttMessage { public final class MqttUnsubAckMessage extends MqttMessage {
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader, MqttMessageIdVariableHeader variableHeader) { public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
super(mqttFixedHeader, variableHeader, null); MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttUnsubAckPayload payload) {
super(mqttFixedHeader, variableHeader, MqttUnsubAckPayload.withEmptyDefaults(payload));
}
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttUnsubAckPayload payload) {
this(mqttFixedHeader, fallbackVariableHeader(variableHeader), payload);
}
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader) {
this(mqttFixedHeader, variableHeader, null);
}
private static MqttMessageIdAndPropertiesVariableHeader fallbackVariableHeader(
MqttMessageIdVariableHeader variableHeader) {
if (variableHeader instanceof MqttMessageIdAndPropertiesVariableHeader) {
return (MqttMessageIdAndPropertiesVariableHeader) variableHeader;
}
return new MqttMessageIdAndPropertiesVariableHeader(variableHeader.messageId(), MqttProperties.NO_PROPERTIES);
} }
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override
public MqttUnsubAckPayload payload() {
return (MqttUnsubAckPayload) super.payload();
}
} }
@@ -0,0 +1,75 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Payload for MQTT unsuback message as in V5.
*/
public final class MqttUnsubAckPayload {
private final List<Short> unsubscribeReasonCodes;
private static final MqttUnsubAckPayload EMPTY = new MqttUnsubAckPayload();
public static MqttUnsubAckPayload withEmptyDefaults(MqttUnsubAckPayload payload) {
if (payload == null) {
return EMPTY;
} else {
return payload;
}
}
public MqttUnsubAckPayload(short... unsubscribeReasonCodes) {
ObjectUtil.checkNotNull(unsubscribeReasonCodes, "unsubscribeReasonCodes");
List<Short> list = new ArrayList<Short>(unsubscribeReasonCodes.length);
for (Short v: unsubscribeReasonCodes) {
list.add(v);
}
this.unsubscribeReasonCodes = Collections.unmodifiableList(list);
}
public MqttUnsubAckPayload(Iterable<Short> unsubscribeReasonCodes) {
ObjectUtil.checkNotNull(unsubscribeReasonCodes, "unsubscribeReasonCodes");
List<Short> list = new ArrayList<Short>();
for (Short v: unsubscribeReasonCodes) {
ObjectUtil.checkNotNull(v, "unsubscribeReasonCode");
list.add(v);
}
this.unsubscribeReasonCodes = Collections.unmodifiableList(list);
}
public List<Short> unsubscribeReasonCodes() {
return unsubscribeReasonCodes;
}
@Override
public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this))
.append('[')
.append("unsubscribeReasonCodes=").append(unsubscribeReasonCodes)
.append(']')
.toString();
}
}
+14 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,23 +17,34 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsubscribe"> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsubscribe">
* MQTTV3.1/unsubscribe</a> * MQTTV3.1/unsubscribe</a>
*/ */
public final class MqttUnsubscribeMessage extends MqttMessage { public final class MqttUnsubscribeMessage extends MqttMessage {
public MqttUnsubscribeMessage( public MqttUnsubscribeMessage(
MqttFixedHeader mqttFixedHeader, MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader, MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttUnsubscribePayload payload) { MqttUnsubscribePayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
public MqttUnsubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttUnsubscribePayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override @Override
public MqttUnsubscribePayload payload() { public MqttUnsubscribePayload payload() {
return (MqttUnsubscribePayload) super.payload(); return (MqttUnsubscribePayload) super.payload();
+6 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -39,11 +39,12 @@ public final class MqttUnsubscribePayload {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('['); StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
for (int i = 0; i < topics.size() - 1; i++) { for (int i = 0; i < topics.size(); i++) {
builder.append("topicName = ").append(topics.get(i)).append(", "); builder.append("topicName = ").append(topics.get(i)).append(", ");
} }
builder.append("topicName = ").append(topics.get(topics.size() - 1)) if (!topics.isEmpty()) {
.append(']'); builder.setLength(builder.length() - 2);
return builder.toString(); }
return builder.append("]").toString();
} }
} }
+24 -14
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -25,7 +25,7 @@ import io.netty.util.internal.ObjectUtil;
public enum MqttVersion { public enum MqttVersion {
MQTT_3_1("MQIsdp", (byte) 3), MQTT_3_1("MQIsdp", (byte) 3),
MQTT_3_1_1("MQTT", (byte) 4), MQTT_3_1_1("MQTT", (byte) 4),
Wildfire_1("MQTT", (byte) 5), MQTT_5("MQTT", (byte) 5),
Wildfire_2("MQTT", (byte) 6), Wildfire_2("MQTT", (byte) 6),
Wildfire_Future("MQTT", (byte) 7); Wildfire_Future("MQTT", (byte) 7);
@@ -50,19 +50,29 @@ public enum MqttVersion {
} }
public static MqttVersion fromProtocolNameAndLevel(String protocolName, byte protocolLevel) { public static MqttVersion fromProtocolNameAndLevel(String protocolName, byte protocolLevel) {
for (MqttVersion mv : values()) { MqttVersion mv = null;
if (mv.name.equals(protocolName)) { switch (protocolLevel) {
if (mv.level == protocolLevel) { case 3:
return mv; mv = MQTT_3_1;
} else { break;
continue; case 4:
} mv = MQTT_3_1_1;
} break;
case 5:
mv = MQTT_5;
break;
case 6:
mv = Wildfire_2;
break;
case 7:
mv = Wildfire_Future;
break;
default:
mv = Wildfire_Future;
break;
} }
if(protocolLevel > Wildfire_Future.level) {
return Wildfire_Future; return mv;
}
throw new MqttUnacceptableProtocolVersionException(protocolName + "is unknown protocol name");
} }
public static MqttVersion fromProtocolLevel(int protocolLevel) { public static MqttVersion fromProtocolLevel(int protocolLevel) {
+1 -1
View File
@@ -5,7 +5,7 @@
* "License"); you may not use this file except in compliance with the License. You may obtain a * "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at: * copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License * Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
@@ -24,7 +24,6 @@ import com.mchange.v2.c3p0.ComboPooledDataSource;
import io.moquette.BrokerConstants; import io.moquette.BrokerConstants;
import io.moquette.server.config.IConfig; import io.moquette.server.config.IConfig;
import org.flywaydb.core.Flyway; import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -50,6 +49,7 @@ public class DBUtil {
public static void init(IConfig config) { public static void init(IConfig config) {
String embedDB = config.getProperty(BrokerConstants.EMBED_DB_PROPERTY_NAME); String embedDB = config.getProperty(BrokerConstants.EMBED_DB_PROPERTY_NAME);
String h2dbPath = config.getProperty(BrokerConstants.H2DB_PATH, "./h2db/wfchat");
boolean autoCleanMsgs = "true".equals(config.getProperty(BrokerConstants.DB_AUTO_CLEAN_HISTORY_MESSAGES)); boolean autoCleanMsgs = "true".equals(config.getProperty(BrokerConstants.DB_AUTO_CLEAN_HISTORY_MESSAGES));
if (embedDB != null && embedDB.equals("1")) { if (embedDB != null && embedDB.equals("1")) {
IsEmbedDB = true; IsEmbedDB = true;
@@ -68,7 +68,7 @@ public class DBUtil {
migrateLocation = "filesystem:./migrate/h2"; migrateLocation = "filesystem:./migrate/h2";
comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setJdbcUrl( "jdbc:h2:./h2db/wfchat;AUTO_SERVER=TRUE;MODE=MySQL" ); comboPooledDataSource.setJdbcUrl( "jdbc:h2:" + h2dbPath + ";AUTO_SERVER=TRUE;MODE=MySQL" );
comboPooledDataSource.setUser("SA"); comboPooledDataSource.setUser("SA");
comboPooledDataSource.setPassword("SA"); comboPooledDataSource.setPassword("SA");
comboPooledDataSource.setMinPoolSize(5); comboPooledDataSource.setMinPoolSize(5);
@@ -68,7 +68,9 @@ public class GitRepositoryState {
public static GitRepositoryState getGitRepositoryState() throws IOException { public static GitRepositoryState getGitRepositoryState() throws IOException {
Properties properties = new Properties(); Properties properties = new Properties();
try { try {
InputStream inputStream = new FileInputStream("config/git.properties"); String configPath = System.getProperty("wildfirechat.path", null);
File f = new File(configPath, "config/git.properties");
InputStream inputStream = new FileInputStream(f);
BufferedReader bf = new BufferedReader(new InputStreamReader(inputStream)); BufferedReader bf = new BufferedReader(new InputStreamReader(inputStream));
properties.load(bf); properties.load(bf);
} catch (IOException e) { } catch (IOException e) {
@@ -87,8 +87,8 @@ public class HttpUtils {
} }
System.out.println(logText); System.out.println(logText);
e.printStackTrace(); e.printStackTrace();
LOG.info(logText); LOG.error(logText);
LOG.info("POST to {} with data {} failure", url, jsonStr); LOG.error("POST to {} with data {} failure", url, jsonStr);
if(httpCallback != null) { if(httpCallback != null) {
httpCallback.onFailure(-1, e.getMessage()); httpCallback.onFailure(-1, e.getMessage());
} }
@@ -1,68 +0,0 @@
package win.liyufan.im;
import io.netty.util.internal.StringUtil;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class I18n {
private static ClassLoader Loader = null;
static {
String configPath = System.getProperty("wildfirechat.path", null);
String resBundlePaht = "config/i18n";
File file = new File(configPath, resBundlePaht);
URL[] urls = new URL[0];
try {
urls = new URL[]{file.toURI().toURL()};
Loader = new URLClassLoader(urls);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
private static Map<String, ResourceBundle> bundleMap = new ConcurrentHashMap<>();
public static String getString(String language, String key) {
ResourceBundle bundle = bundleMap.get(language);
if (StringUtil.isNullOrEmpty(language)) language = "zh_CN";
if (bundle == null) {
bundle = ResourceBundle.getBundle("messages", new Locale(language), Loader);
bundleMap.put(language, bundle);
}
try {
return bundle.getString(key);
} catch (Exception e) {
e.printStackTrace();
}
return "糟糕,字符串 " + key + " 没有找到";
}
public static void test() throws MalformedURLException {
// 设置定制的语言国家代码
Locale locale1 = new Locale("zh");
Locale locale2 = new Locale("en");
ResourceBundle rb = ResourceBundle.getBundle("messages", locale1, Loader);
// 获得相应的key值
String greeting = rb.getString("Above_Greeting_Message");
String userInfo = rb.getString("Friend_Can_Start_Chat");
System.out.println(greeting);
System.out.println(userInfo);
}
public static void main(String[] args) throws Exception {
test();
}
}
-1
View File
@@ -6,7 +6,6 @@
<Properties> <Properties>
<!--自定义一些常量,之后使用${变量名}引用--> <!--自定义一些常量,之后使用${变量名}引用-->
<Property name="MSG_LOG_HOME">./logs</Property> <Property name="MSG_LOG_HOME">./logs</Property>
<Property name="logFileName">test.log</Property>
<property name="charset">UTF-8</property> <property name="charset">UTF-8</property>
<!--自定义的输出格式--> <!--自定义的输出格式-->
<property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property> <property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>wildfirechat-parent</artifactId> <artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId> <groupId>cn.wildfirechat</groupId>
<version>0.94</version> <version>0.97</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -60,6 +60,7 @@ public interface APIPath {
String Msg_ConvRead = "/admin/message/conv_read"; String Msg_ConvRead = "/admin/message/conv_read";
String Msg_Delivery = "/admin/message/delivery"; String Msg_Delivery = "/admin/message/delivery";
String Conversation_Delete = "/admin/conversation/delete"; String Conversation_Delete = "/admin/conversation/delete";
String Msg_Clear_By_User = "/admin/message/clear_by_user";
String Create_Group = "/admin/group/create"; String Create_Group = "/admin/group/create";
String Group_Dismiss = "/admin/group/del"; String Group_Dismiss = "/admin/group/del";
@@ -91,6 +92,7 @@ public interface APIPath {
String Conference_List_Participant = "/admin/conference/list_participant"; String Conference_List_Participant = "/admin/conference/list_participant";
String Conference_Create = "/admin/conference/create"; String Conference_Create = "/admin/conference/create";
String Conference_Destroy = "/admin/conference/destroy"; String Conference_Destroy = "/admin/conference/destroy";
String Conference_Recording = "/admin/conference/recording";
String Channel_User_Info = "/channel/user_info"; String Channel_User_Info = "/channel/user_info";
String Channel_Update_Profile = "/channel/update_profile"; String Channel_Update_Profile = "/channel/update_profile";
@@ -1,83 +0,0 @@
package cn.wildfirechat.messagecontentbuilder;
import cn.wildfirechat.pojos.MessagePayload;
import cn.wildfirechat.proto.ProtoConstants;
import io.netty.util.internal.StringUtil;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.function.Consumer;
public class ArticlesContentBuilder extends MessageContentBuilder{
public static class Article {
public String id;
public String cover;
public String title;
public String url;
public boolean readReport;
}
public Article topArticle;
public List<Article> subArticles;
public static ArticlesContentBuilder newBuilder(String id, String cover, String title, String url, boolean readReport) {
ArticlesContentBuilder builder = new ArticlesContentBuilder();
builder.topArticle = new Article();
builder.topArticle.id = id;
builder.topArticle.cover = cover;
builder.topArticle.title = title;
builder.topArticle.url = url;
builder.topArticle.readReport = readReport;
return builder;
}
public ArticlesContentBuilder addSubArticle(String id, String cover, String title, String url, boolean readReport) {
Article article = new Article();
article.id = id;
article.cover = cover;
article.title = title;
article.url = url;
article.readReport = readReport;
if(subArticles == null) {
subArticles = new ArrayList<>();
}
subArticles.add(article);
return this;
}
private static JSONObject toJSON(Article article) {
JSONObject top = new JSONObject();
if(!StringUtil.isNullOrEmpty(article.id))
top.put("id", article.id);
if(!StringUtil.isNullOrEmpty(article.cover))
top.put("cover", article.cover);
if(!StringUtil.isNullOrEmpty(article.title))
top.put("title", article.title);
if(!StringUtil.isNullOrEmpty(article.url))
top.put("url", article.url);
if(article.readReport)
top.put("rr", article.readReport);
return top;
}
@Override
public MessagePayload build() {
MessagePayload payload = encodeBase();
payload.setType(ProtoConstants.ContentType.Articles);
payload.setPersistFlag(ProtoConstants.PersistFlag.Persist_And_Count);
payload.setSearchableContent(topArticle.title);
JSONObject jsonObject = new JSONObject();
jsonObject.put("top", toJSON(topArticle));
if(subArticles != null && !subArticles.isEmpty()) {
JSONArray jsonArray = new JSONArray();
subArticles.forEach(article -> jsonArray.add(toJSON(article)));
jsonObject.put("subArticles", jsonArray);
}
payload.setBase64edData(Base64.getEncoder().encodeToString(jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8)));
return payload;
}
}
@@ -13,16 +13,18 @@ public class ArticleContent {
public String id; public String id;
public String cover; public String cover;
public String title; public String title;
public String digest;
public String url; public String url;
public boolean rr; public boolean rr;
public Article() { public Article() {
} }
public Article(String id, String cover, String title, String url, boolean rr) { public Article(String id, String cover, String title, String digest, String url, boolean rr) {
this.id = id; this.id = id;
this.cover = cover; this.cover = cover;
this.title = title; this.title = title;
this.digest = digest;
this.url = url; this.url = url;
this.rr = rr; this.rr = rr;
} }
@@ -33,14 +35,14 @@ public class ArticleContent {
public ArticleContent() { public ArticleContent() {
} }
public ArticleContent(String id, String cover, String title, String url, boolean rr) { public ArticleContent(String id, String cover, String title, String digest, String url, boolean rr) {
this.top = new Article(id, cover, title, url, rr); this.top = new Article(id, cover, title, digest, url, rr);
} }
public ArticleContent addSubArticle(String id, String cover, String title, String url, boolean rr) { public ArticleContent addSubArticle(String id, String cover, String title, String digest, String url, boolean rr) {
if (subArticles == null) if (subArticles == null)
subArticles = new ArrayList<>(); subArticles = new ArrayList<>();
subArticles.add(new Article(id, cover, title, url, rr)); subArticles.add(new Article(id, cover, title, digest, url, rr));
return this; return this;
} }
@@ -0,0 +1,26 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package cn.wildfirechat.pojos;
public class InputClearUserMessages {
public InputClearUserMessages() {
}
public InputClearUserMessages(String userId, Conversation conversation, long startTime, long endTime) {
this.userId = userId;
this.conversation = conversation;
this.startTime = startTime;
this.endTime = endTime;
}
public String userId;
public Conversation conversation;
public long startTime;
public long endTime;
}
-16
View File
@@ -1,21 +1,5 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package cn.wildfirechat.pojos; package cn.wildfirechat.pojos;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList;
import java.util.List;
public class OutputBooleanValue { public class OutputBooleanValue {
public boolean value; public boolean value;
} }
@@ -0,0 +1,7 @@
package cn.wildfirechat.pojos;
public class PojoConferenceRecording {
public String roomId;
public boolean recording;
public boolean advance;
}
+107 -2
View File
@@ -6,7 +6,7 @@
<!--<relativePath>../pom.xml</relativePath>--> <!--<relativePath>../pom.xml</relativePath>-->
<artifactId>wildfirechat-parent</artifactId> <artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId> <groupId>cn.wildfirechat</groupId>
<version>0.94</version> <version>0.97</version>
</parent> </parent>
<artifactId>distribution</artifactId> <artifactId>distribution</artifactId>
@@ -93,7 +93,112 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<artifactId>jdeb</artifactId>
<groupId>org.vafer</groupId>
<version>1.8</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jdeb</goal>
</goals>
<configuration>
<controlDir>${project.basedir}/src/main/deb/control</controlDir>
<skipPOMs>false</skipPOMs>
<deb>${project.build.directory}/im-server-${project.version}.deb</deb>
<dataSet>
<data>
<src>${project.build.directory}/distribution-${project.version}-bundle-tar.tar.gz</src>
<type>archive</type>
<mapper>
<type>perm</type>
<prefix>/opt/im-server</prefix>
</mapper>
</data>
<data>
<src>${project.basedir}/../systemd/im-server.service</src>
<type>file</type>
<mapper>
<type>perm</type>
<prefix>/usr/lib/systemd/system</prefix>
</mapper>
</data>
</dataSet>
</configuration>
</execution>
</executions>
</plugin>
<!-- 打包RPM 开始-->
<!-- 打包RPM 包需要本地有rpm命令才可以,linux和mac都可以安装rpm。如果是windows需要cygwin安装rpm才可以,如果不需要rpm包,可以把这个plugin注释掉-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>rpm-maven-plugin</artifactId>
<version>2.2.0</version>
<executions>
<execution>
<id>generate-rpm</id>
<goals>
<goal>rpm</goal>
</goals>
</execution>
</executions>
<configuration>
<group>Applications/Chat</group>
<name>im-server</name>
<needarch>noarch</needarch>
<targetOS>linux</targetOS>
<prefix>/opt/im-server</prefix>
<defineStatements>
<defineStatement>_unpackaged_files_terminate_build 0</defineStatement>
</defineStatements>
<copyTo>
target/im-server-${project.version}.rpm
</copyTo>
<requires>
<require>java-1.8.0-openjdk-headless</require>
</requires>
<mappings>
<mapping>
<directory>/opt/im-server</directory>
<filemode>755</filemode>
<sources>
<source>
<location>${project.build.directory}/distribution-${project.version}-bundle-tar.tar.gz</location>
</source>
</sources>
</mapping>
<mapping>
<directory>/usr/lib/systemd/system</directory>
<filemode>644</filemode>
<username>root</username>
<groupname>root</groupname>
<directoryIncluded>false</directoryIncluded>
<sources>
<source>
<location>${project.basedir}/../systemd/im-server.service</location>
</source>
</sources>
</mapping>
</mappings>
<postinstallScriptlet>
<scriptFile>${project.basedir}/src/main/rpm/install.sh</scriptFile>
<fileEncoding>utf-8</fileEncoding>
<filter>true</filter>
</postinstallScriptlet>
<postremoveScriptlet>
<scriptFile>${project.basedir}/src/main/rpm/uninstall.sh</scriptFile>
<fileEncoding>utf-8</fileEncoding>
<filter>true</filter>
</postremoveScriptlet>
</configuration>
</plugin>
<!-- 打包RPM 结束-->
</plugins> </plugins>
</build> </build>
</project> </project>
+4 -4
View File
@@ -21,13 +21,13 @@
</fileSet> </fileSet>
<fileSet> <fileSet>
<directory>../broker/config/i18n/</directory> <directory>src/main/resources/</directory>
<outputDirectory>/config/i18n</outputDirectory> <outputDirectory>config/</outputDirectory>
</fileSet> </fileSet>
<fileSet> <fileSet>
<directory>src/main/resources/</directory> <directory>../systemd/</directory>
<outputDirectory>config/</outputDirectory> <outputDirectory>/systemd</outputDirectory>
</fileSet> </fileSet>
</fileSets> </fileSets>
+10
View File
@@ -0,0 +1,10 @@
Package: im-server
Version: [[version]]
Section: misc
Priority: optional
Architecture: all
Maintainer: Wildfirechat <[email protected]>
Description: IM Server
Distribution: development
Depends: openjdk-8-jre-headless
Homepage: https://wildfirechat.cn
@@ -0,0 +1,18 @@
set -e
sed -i 's/h2db.path .\/h2db\/wfchat/h2db.path \/var\/lib\/im-server\/h2db\/imdb/' /opt/im-server/config/wildfirechat.conf
sed -i 's/local.media.storage.root .\/media/local.media.storage.root \/var\/lib\/im-server\/media/' /opt/im-server/config/wildfirechat.conf
sed -i 's/<Property name="MSG_LOG_HOME">.\/logs<\/Property>/<Property name="MSG_LOG_HOME">\/var\/log\/im-server<\/Property>/' /opt/im-server/config/log4j2.xml
sed -i 's/WILDFIRECHAT_CONFIG_PATH=$WILDFIRECHAT_HOME/WILDFIRECHAT_CONFIG_PATH=\/etc\/im-server/' /opt/im-server/bin/wildfirechat.sh
if [ ! -d /etc/im-server ]; then
mkdir /etc/im-server
fi
mv -f /opt/im-server/config /etc/im-server
systemctl daemon-reload
echo "IM server folders:"
echo "/etc/im-server/config config"
echo "/opt/im-server binary files"
echo "/var/log/im-server logs"
echo "/var/lib/im-server/h2db embed db"
echo "/var/lib/im-server/media embed media files"
+16
View File
@@ -0,0 +1,16 @@
set -e
rm -rf /opt/im-server
rm -rf /etc/im-server
rm -rf /var/log/im-server
rm -rf /usr/lib/systemd/system/im-server.service
if [ -d /var/lib/im-server/h2db ]; then
echo "IM embed db file not deleted in path /var/lib/im-server/h2db, if you don't need it anymore, please remove it manually"
fi
if [ -d /var/lib/im-server/media ]; then
echo "IM embed media files not deleted in path /var/lib/im-server/media, if you don't need it anymore, please remove it manually"
fi
systemctl daemon-reload
+21 -1
View File
@@ -7,10 +7,30 @@
6. *** 0.62/0.63 版本有严重的问题,请使用0.64及以后版本,或者0.61版。 *** 6. *** 0.62/0.63 版本有严重的问题,请使用0.64及以后版本,或者0.61版。 ***
7. *** 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端) *** 7. *** 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端) ***
8. *** 从0.78 版本起把MySQL数据库中关键字都改为大小写敏感,另外生成id的方法也做了改变,只生成小写的id,避免出现id重复的问题,建议所有客户都升级 *** 8. *** 从0.78 版本起把MySQL数据库中关键字都改为大小写敏感,另外生成id的方法也做了改变,只生成小写的id,避免出现id重复的问题,建议所有客户都升级 ***
9 *** 从0.79 版本起把log4j升级到log4j2,因为log4j已经不再维护而且还有已知的漏洞,建议所有客户都升级,升级时注意更新log4j2的配置文件 *** 9. *** 从0.79 版本起把log4j升级到log4j2,因为log4j已经不再维护而且还有已知的漏洞,建议所有客户都升级,升级时注意更新log4j2的配置文件 ***
10. *** 5. 0.97版本更改了启动脚本```wildfirechat.sh```,如果是升级服务,请注意更新启动脚本。***
# 更新记录 # 更新记录
--------------
Release note 0.97:
1. 升级部分依赖库到最新版本
2. 添加名片消息默认推送信息
3. 添加deb和rpm格式软件包
4. 解决linux平台多端的问题
--------------
Release note 0.96:
1. 文章消息添加摘要字段
2. 单聊消息支持server api只发送给其中一方
3. Server api添加会议录制/停止录制接口
--------------
Release note 0.95:
1. 添加按照用户删除会话接口
2. 升级netty版本为4.1.68.Final
3. 公众号文章添加摘要字段
-------------- --------------
Release note 0.94: Release note 0.94:
1. Server SDK添加删除会话接口 1. Server SDK添加删除会话接口
+2 -3
View File
@@ -4,11 +4,10 @@
<!--monitorInterval="30" :间隔秒数,自动检测配置文件的变更和重新配置本身--> <!--monitorInterval="30" :间隔秒数,自动检测配置文件的变更和重新配置本身-->
<configuration status="WARN" monitorInterval="60"> <configuration status="WARN" monitorInterval="60">
<Properties> <Properties>
<!--自定义一些常量,之后使用${变量名}引用--> <!-- 日志路径-->
<Property name="MSG_LOG_HOME">./logs</Property> <Property name="MSG_LOG_HOME">./logs</Property>
<Property name="logFileName">test.log</Property> <!-- <Property name="MSG_LOG_HOME">/var/log/wildfirechat/im/logs</Property>-->
<property name="charset">UTF-8</property> <property name="charset">UTF-8</property>
<!--自定义的输出格式-->
<property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property> <property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property>
</Properties> </Properties>
<!--appenders:定义输出内容,输出格式,输出方式,日志保存策略等,常用其下三种标签[console,File,RollingFile]--> <!--appenders:定义输出内容,输出格式,输出方式,日志保存策略等,常用其下三种标签[console,File,RollingFile]-->
@@ -31,6 +31,7 @@ local_port 80
## 是否使用内置DB。0使用mysql;1使用h2db。 ## 是否使用内置DB。0使用mysql;1使用h2db。
## MySQL需要把事物隔离级别改成"Read committed",使用命令来修改"set global transaction_isolation='read-committed';",其他数据库默认已经是这个级别不用修改 ## MySQL需要把事物隔离级别改成"Read committed",使用命令来修改"set global transaction_isolation='read-committed';",其他数据库默认已经是这个级别不用修改
## 如果使用MySQL,请在c3p0.xml文件中配置JDBC信息。
embed.db 1 embed.db 1
## 是否自动清理历史消息记录,当为true时,IM服务会定时扫描t_messages_x和t_user_messages_x表,清除掉3年前的历史消息。 ## 是否自动清理历史消息记录,当为true时,IM服务会定时扫描t_messages_x和t_user_messages_x表,清除掉3年前的历史消息。
@@ -39,6 +40,9 @@ embed.db 1
## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。 ## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。
db.auto_clean_history_messages true db.auto_clean_history_messages true
## h2db数据库的路径,默认为程序目录下的,可以指定其他目录。如果使用MySQL,可以忽略此配置
h2db.path ./h2db/wfchat
##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。 ##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。
http.admin.secret_key 123456 http.admin.secret_key 123456
@@ -135,12 +139,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。 ##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000 friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出 ## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000 chatroom.participant_idle_time 900000
@@ -184,11 +182,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。 ##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100 client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#********************************************************************* #*********************************************************************
# Media server configuration # Media server configuration
#********************************************************************* #*********************************************************************
+23
View File
@@ -0,0 +1,23 @@
set -e
cd /opt/im-server
tar -xzvf distribution*.tar.gz
sed -i 's/h2db.path .\/h2db\/wfchat/h2db.path \/var\/lib\/im-server\/h2db\/imdb/' /opt/im-server/config/wildfirechat.conf
sed -i 's/local.media.storage.root .\/media/local.media.storage.root \/var\/lib\/im-server\/media/' /opt/im-server/config/wildfirechat.conf
sed -i 's/<Property name="MSG_LOG_HOME">.\/logs<\/Property>/<Property name="MSG_LOG_HOME">\/var\/log\/im-server<\/Property>/' /opt/im-server/config/log4j2.xml
sed -i 's/WILDFIRECHAT_CONFIG_PATH=$WILDFIRECHAT_HOME/WILDFIRECHAT_CONFIG_PATH=\/etc\/im-server/' /opt/im-server/bin/wildfirechat.sh
if [ ! -d /etc/im-server ]; then
mkdir /etc/im-server
fi
mv -f /opt/im-server/config /etc/im-server
systemctl daemon-reload
echo "IM server folders:"
echo "/etc/im-server/config config"
echo "/opt/im-server binary files"
echo "/var/log/im-server logs"
echo "/var/lib/im-server/h2db embed db"
echo "/var/lib/im-server/media embed media files"
+16
View File
@@ -0,0 +1,16 @@
set -e
rm -rf /opt/im-server
rm -rf /etc/im-server
rm -rf /var/log/im-server
rm -rf /usr/lib/systemd/system/im-server.service
if [ -d /var/lib/im-server/h2db ]; then
echo "IM embed db file not deleted in path /var/lib/im-server/h2db, if you don't need it anymore, please remove it manually"
fi
if [ -d /var/lib/im-server/media ]; then
echo "IM embed media files not deleted in path /var/lib/im-server/media, if you don't need it anymore, please remove it manually"
fi
systemctl daemon-reload
@@ -79,4 +79,4 @@ echo
rem set JAVA_OPTS=%JAVA_OPTS% -Xmx2G rem set JAVA_OPTS=%JAVA_OPTS% -Xmx2G
rem set JAVA_OPTS=%JAVA_OPTS% -Xms2G rem set JAVA_OPTS=%JAVA_OPTS% -Xms2G
%JAVA% -server %JAVA_OPTS% %JAVA_OPTS_SCRIPT% -Dlog4j.configurationFile=%LOG_FILE% -Dcom.mchange.v2.c3p0.cfg.xml=%C3P0_CONF_FILE% -Dhazelcast.configuration=file:%HZ_CONF_FILE% -Dwildfirechat.path=%WILDFIRECHAT_PATH% -cp %WILDFIRECHAT_HOME%\lib\* cn.wildfirechat.server.Server %JAVA% -server %JAVA_OPTS% %JAVA_OPTS_SCRIPT% -Dlog4j.configurationFile=%LOG_FILE% -Dcom.mchange.v2.c3p0.cfg.xml=%C3P0_CONF_FILE% -Dhazelcast.configuration=%HZ_CONF_FILE% -Dwildfirechat.path=%WILDFIRECHAT_PATH% -cp %WILDFIRECHAT_HOME%\lib\* cn.wildfirechat.server.Server
@@ -34,11 +34,13 @@ else
fi fi
export JAVA export JAVA
LOG_FILE=$WILDFIRECHAT_HOME/config/log4j2.xml WILDFIRECHAT_CONFIG_PATH=$WILDFIRECHAT_HOME
HZ_CONF_FILE=$WILDFIRECHAT_HOME/config/hazelcast.xml
C3P0_CONF_FILE=$WILDFIRECHAT_HOME/config/c3p0-config.xml LOG_FILE=$WILDFIRECHAT_CONFIG_PATH/config/log4j2.xml
HZ_CONF_FILE=$WILDFIRECHAT_CONFIG_PATH/config/hazelcast.xml
C3P0_CONF_FILE=$WILDFIRECHAT_CONFIG_PATH/config/c3p0-config.xml
WILDFIRECHAT_PATH=$WILDFIRECHAT_HOME/
#LOG_CONSOLE_LEVEL=info #LOG_CONSOLE_LEVEL=info
#LOG_FILE_LEVEL=fine #LOG_FILE_LEVEL=fine
JAVA_OPTS_SCRIPT="-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true" JAVA_OPTS_SCRIPT="-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true"
@@ -91,4 +93,4 @@ echo ""
#JAVA_OPTS="$JAVA_OPTS -Xms2G" #JAVA_OPTS="$JAVA_OPTS -Xms2G"
$JAVA -server $JAVA_OPTS $JAVA_OPTS_SCRIPT -Dlog4j.configurationFile="file:$LOG_FILE" -Dlog4j2.formatMsgNoLookups=true -Dcom.mchange.v2.c3p0.cfg.xml="$C3P0_CONF_FILE" -Dhazelcast.configuration="file:$HZ_CONF_FILE" -Dwildfirechat.path="$WILDFIRECHAT_PATH" -cp "$WILDFIRECHAT_HOME/lib/*" cn.wildfirechat.server.Server $JAVA -server $JAVA_OPTS $JAVA_OPTS_SCRIPT -Dlog4j.configurationFile="file:$LOG_FILE" -Dlog4j2.formatMsgNoLookups=true -Dcom.mchange.v2.c3p0.cfg.xml="$C3P0_CONF_FILE" -Dhazelcast.configuration=$HZ_CONF_FILE -Dwildfirechat.path="$WILDFIRECHAT_CONFIG_PATH" -cp "$WILDFIRECHAT_HOME/lib/*" cn.wildfirechat.server.Server
+2
View File
@@ -9,6 +9,8 @@ WORKDIR /opt/im-server
VOLUME /opt/im-server/config VOLUME /opt/im-server/config
VOLUME /opt/im-server/logs VOLUME /opt/im-server/logs
VOLUME /opt/im-server/h2db
VOLUME /opt/im-server/media
EXPOSE 80/tcp 1883/tcp 8083/tcp 8084/tcp 18080/tcp EXPOSE 80/tcp 1883/tcp 8083/tcp 8084/tcp 18080/tcp
+13 -3
View File
@@ -8,12 +8,22 @@ mvn clean package
然后进入到docker目录下,编译镜像 然后进入到docker目录下,编译镜像
``` ```
sudo docker build -t wildfire_im . sudo docker build -t im-server -f Dockerfile ..
``` ```
## 运行 ## 运行
配置: 配置:
运行需要手动指定配置目录,这样不用重新打包镜像。手动指定配置目录的方法如下,其中$PATH_TO_CONFIG为im配置目录,模版为```distribution/src/main/resources```目录: 运行需要手动指定配置目录,手动指定配置目录的方法如下,其中$PATH_TO_CONFIG为im配置目录,需要为绝对路径,模版为```distribution/src/main/resources```目录。另外需要创建h2数据库目录(如果用mysql就不需要了)、日志目录、内置存储服务目录(如果使用其它对象存储服务就不需要了)。
``` ```
sudo docker run -it -v $PATH_TO_CONFIG:/opt/im-server/config -v $PATH_TO_LOGS:/opt/im-server/logs -e JVM_XMX=256M -e JVM_XMS=256M -p 80:80 -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 18080:18080 wildfire_im sudo docker run -it -v $PATH_TO_CONFIG:/opt/im-server/config -v $PATH_TO_LOGS:/opt/im-server/logs -v $PATH_TO_H2DB:/opt/im-server/h2db -v $PATH_TO_MEDIA:/opt/im-server/media -e JVM_XMX=256M -e JVM_XMS=256M -p 80:80 -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 18080:18080 im-server
```
## 导出镜像
```
sudo docker save -o im-server.tar im-server
```
## 导入镜像
```
sudo docker load -i im-server.tar
``` ```
+2 -2
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<netty.version>4.1.12.Final</netty.version> <netty.version>4.1.68.Final</netty.version>
<source.version>1.8</source.version> <source.version>1.8</source.version>
<target.version>1.8</target.version> <target.version>1.8</target.version>
</properties> </properties>
@@ -12,7 +12,7 @@
<artifactId>wildfirechat-parent</artifactId> <artifactId>wildfirechat-parent</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>0.94</version> <version>0.97</version>
<name>Wildfire Chat</name> <name>Wildfire Chat</name>
<description>Wildfire Chat Server</description> <description>Wildfire Chat Server</description>
<inceptionYear>2019</inceptionYear> <inceptionYear>2019</inceptionYear>
+3 -5
View File
@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>wildfirechat-parent</artifactId> <artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId> <groupId>cn.wildfirechat</groupId>
<version>0.94</version> <version>0.97</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -73,7 +71,7 @@
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>1.10</version> <version>1.13</version>
</dependency> </dependency>
<dependency> <dependency>
-1
View File
@@ -28,7 +28,6 @@
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>1.11</version> <version>1.11</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -31,6 +31,15 @@ public class ConferenceAdmin {
return AdminHttpUtils.httpJsonPost(path, create, Void.class); return AdminHttpUtils.httpJsonPost(path, create, Void.class);
} }
public static IMResult<Void> enableRecording(String roomId, boolean advance, boolean recording) throws Exception {
String path = APIPath.Conference_Recording;
PojoConferenceRecording create = new PojoConferenceRecording();
create.roomId = roomId;
create.recording = recording;
create.advance = advance;
return AdminHttpUtils.httpJsonPost(path, create, Void.class);
}
public static IMResult<Void> destroy(String roomId, boolean advance) throws Exception { public static IMResult<Void> destroy(String roomId, boolean advance) throws Exception {
String path = APIPath.Conference_Destroy; String path = APIPath.Conference_Destroy;
PojoConferenceRoomId conferenceRoomId = new PojoConferenceRoomId(roomId, advance); PojoConferenceRoomId conferenceRoomId = new PojoConferenceRoomId(roomId, advance);
@@ -1454,9 +1454,9 @@ public class Main {
System.exit(-1); System.exit(-1);
} }
ArticleContent articleContent = new ArticleContent("article1", "https://media.wfcoss.cn/channel-assets/20220816/2dd76540daa9444dae44e942aa1c2bbc.png", "这是一个测试文章", "https://mp.weixin.qq.com/s/W6tanLbALd3qqZM8r3MTgA", true); ArticleContent articleContent = new ArticleContent("article1", "https://media.wfcoss.cn/channel-assets/20220816/2dd76540daa9444dae44e942aa1c2bbc.png", "这是一个测试文章", "测试一下文章的功能", "https://mp.weixin.qq.com/s/W6tanLbALd3qqZM8r3MTgA", true);
articleContent.addSubArticle("article2", "https://media.wfcoss.cn/channel-assets/20220816/2dd76540daa9444dae44e942aa1c2bbc.png", "这是第二个测试文章", "https://mp.weixin.qq.com/s/W6tanLbALd3qqZM8r3MTgA", false); articleContent.addSubArticle("article2", "https://media.wfcoss.cn/channel-assets/20220816/2dd76540daa9444dae44e942aa1c2bbc.png", "这是第二个测试文章", "测试一下文章的功能", "https://mp.weixin.qq.com/s/W6tanLbALd3qqZM8r3MTgA", false);
articleContent.addSubArticle("article3", "https://media.wfcoss.cn/channel-assets/20220816/2dd76540daa9444dae44e942aa1c2bbc.png", "这是第三个测试文章", "https://mp.weixin.qq.com/s/W6tanLbALd3qqZM8r3MTgA", false); articleContent.addSubArticle("article3", "https://media.wfcoss.cn/channel-assets/20220816/2dd76540daa9444dae44e942aa1c2bbc.png", "这是第三个测试文章", "测试一下文章的功能", "https://mp.weixin.qq.com/s/W6tanLbALd3qqZM8r3MTgA", false);
payload = articleContent.toPayload(); payload = articleContent.toPayload();
resultSendMessage = channelServiceApi.sendMessage(0, null, payload); resultSendMessage = channelServiceApi.sendMessage(0, null, payload);
@@ -1642,22 +1642,30 @@ public class Main {
System.out.println("conference list " + listResult.getResult().conferenceInfoList); System.out.println("conference list " + listResult.getResult().conferenceInfoList);
} }
IMResult<Void> createResult = ConferenceAdmin.createRoom("helloroomid", "hello room description", "123456", 9, false, 0, false, true); IMResult<Void> voidIMResult = ConferenceAdmin.createRoom("helloroomid", "hello room description", "123456", 9, false, 0, false, true);
if(createResult == null || createResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) { if(voidIMResult == null || voidIMResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
System.out.println("create conference failure"); System.out.println("create conference failure");
System.exit(-1); System.exit(-1);
} else { } else {
System.out.println("create conference"); System.out.println("create conference");
} }
createResult = ConferenceAdmin.createRoom("helloroomid2", "hello room description advanced", "123456", 20, true, 0, false, true); voidIMResult = ConferenceAdmin.createRoom("helloroomid2", "hello room description advanced", "123456", 20, true, 0, false, true);
if(createResult == null || createResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) { if(voidIMResult == null || voidIMResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
System.out.println("create conference failure"); System.out.println("create conference failure");
System.exit(-1); System.exit(-1);
} else { } else {
System.out.println("create conference"); System.out.println("create conference");
} }
voidIMResult = ConferenceAdmin.enableRecording("helloroomid2", true, true);
if(voidIMResult == null || voidIMResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
System.out.println("recording conference failure");
System.exit(-1);
} else {
System.out.println("recording conference success");
}
listResult = ConferenceAdmin.listConferences(); listResult = ConferenceAdmin.listConferences();
if(listResult == null || listResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) { if(listResult == null || listResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
System.out.println("get conference list failure"); System.out.println("get conference list failure");
@@ -33,6 +33,7 @@ public class MessageAdmin {
return AdminHttpUtils.httpJsonPost(path, messageData, Void.class); return AdminHttpUtils.httpJsonPost(path, messageData, Void.class);
} }
//仅专业版支持
public static IMResult<Void> deleteMessage(long messageUid) throws Exception { public static IMResult<Void> deleteMessage(long messageUid) throws Exception {
String path = APIPath.Msg_Delete; String path = APIPath.Msg_Delete;
DeleteMessageData deleteMessageData = new DeleteMessageData(); DeleteMessageData deleteMessageData = new DeleteMessageData();
@@ -40,6 +41,14 @@ public class MessageAdmin {
return AdminHttpUtils.httpJsonPost(path, deleteMessageData, Void.class); return AdminHttpUtils.httpJsonPost(path, deleteMessageData, Void.class);
} }
//仅专业版支持
public static IMResult<Void> clearUserMessages(String userId, Conversation conversation, long fromTime, long toTime) throws Exception {
String path = APIPath.Msg_Clear_By_User;
InputClearUserMessages clearUserMessages = new InputClearUserMessages(userId, conversation, fromTime, toTime);
return AdminHttpUtils.httpJsonPost(path, clearUserMessages, Void.class);
}
//仅专业版支持
public static IMResult<Void> updateMessageContent(String operator, long messageUid, MessagePayload payload, boolean distribute) throws Exception { public static IMResult<Void> updateMessageContent(String operator, long messageUid, MessagePayload payload, boolean distribute) throws Exception {
String path = APIPath.Msg_Update; String path = APIPath.Msg_Update;
UpdateMessageContentData updateMessageContentData = new UpdateMessageContentData(); UpdateMessageContentData updateMessageContentData = new UpdateMessageContentData();
@@ -51,6 +60,7 @@ public class MessageAdmin {
return AdminHttpUtils.httpJsonPost(path, updateMessageContentData, Void.class); return AdminHttpUtils.httpJsonPost(path, updateMessageContentData, Void.class);
} }
//仅专业版支持
public static IMResult<Void> clearConversation(String userId, Conversation conversation) throws Exception { public static IMResult<Void> clearConversation(String userId, Conversation conversation) throws Exception {
String path = APIPath.Conversation_Delete; String path = APIPath.Conversation_Delete;
InputUserConversation input = new InputUserConversation(); InputUserConversation input = new InputUserConversation();
+72
View File
@@ -0,0 +1,72 @@
# Linux Service 方式运行
除了命令行方式直接执行IM服务外,还可以以linux systemd service方式来运行,注意以这种方式运行,im服务的配置还是需要按照常规方法来配置。
## 获取软件包
如果是社区版可以下载野火release或则会自己源码编译,得到软件压缩包```distribution-bundle-tar.tar.gz```、```im-server.deb```和```im-server.rpm```。如果是专业版使用专业版邮件里的链接下载软件压缩包,下载后先解压一次,得到```distribution-bundle-tar.tar.gz```、```im-server.deb```和```im-server.rpm```。
> ```im-server.deb```和```im-server.rpm```文件可能带有版本号,下面使用过程中,请注意修正为实际的文件名称。
## 手动部署
### 依赖
野火IM依赖JRE1.8手动部署需要手动安装JRE1.8,确保命令:```java -version```能看到正确的java版本信息才行。
### 部署软件包
创建```/opt/im-server```目录,把软件包```distribution-bundle-tar.tar.gz```解压到这个目录下。解压后这个目录下有```bin```、```config```、```lib```、```systemd```等目录。
### 放置systemd server file
把```systemd```目录下的```im-server.service```文件放到```/usr/lib/systemd/system/```目录下。然后执行命令```sudo systemctl daemon-reload```。
### 目录结构
所有目录都在```/opt/im-server```目录下。包括日志目录、配置目录等。
### 测试
根据下面管理服务的说明,启动服务,查看控制台日志,确认启动没有异常,服务器本地执行 ```curl -v http://127.0.0.1/api/version``` 能够返回版本的JSON信息。
## 安装部署
### 依赖
安装包安装将会自动安装依赖,不需要手动安装java。如果服务器上有其他版本的Java,请注意可能的冲突问题。
### 部署软件包
可以直接安装```deb```和```rpm```格式的安装包,在debian系的linux系统(Ubuntu等使用```apt```命令安装软件的系统)中,使用命令:
```shell
sudo apt install ./im-server.deb
```
在红帽系的linux系统(Centos等使用```yum```命令安装软件的系统)中,使用命令:
```shell
sudo yum install ./im-server.rpm
```
注意在上述两个命令中,都使用的是本地安装,注意安装包名前的```./```路径。如果使用```dpkg -i ./im-server.deb```命令将不会安装依赖。
### 目录结构
* /etc/im-server/config 配置文件目录
* /opt/im-server 程序目录
* /var/log/im-server 日志目录
* /var/lib/im-server/h2db H2数据库目录,如果使用mysql则不会使用
* /var/lib/im-server/media 内置对象存储数据目录,如果使用非内置,则目录不会使用。
### 测试
根据下面管理服务的说明,启动服务,查看控制台日志,确认启动没有异常,服务器本地执行 ```curl -v http://127.0.0.1/api/version``` 能够返回版本的JSON信息。
## 管理服务
* 刷新配置,当安装或者更新后需要执行: ```sudo systemctl daemon-reload```
* 启动服务: ```sudo systemctl start im-server```
* 停止服务: ```sudo systemctl stop im-server```
* 重启服务: ```sudo systemctl restart im-server```
* 查看服务状态:```sudo systemctl status im-server```
* 设置开机自启动:```sudo systemctl enable im-server```
* 禁止开机自启动:```sudo systemctl disable im-server```
* 查看控制台日志: ```journalctl -f -u im-server```
## 修改服务内存大小
修改```/opt/im-server/bin/wildfirechat.sh```文件的倒数3、4行。打开Xmx和Xms配置,设置为合适的内存大小。
## 配置
需要对IM服务配置来达到最好的执行效果,手动部署配置文件在````/opt/im-server/config````目录下,安装部署的配置文件在````/etc/im-server/config````目录下。
## 日志
手动部署的日志文件在```/opt/im-server/logs```目录下,安装部署的日志在```/var/log/im-server```目录下。如果需要提供日志给野火官方,请把这个目录下的日志和制台日志(```journalctl -f -u im-server```)一起发给野火。
手动部署也可以修改目录,可以修改配置文件目录下的```config/log4j2.xml```修改日志的路径。
+26
View File
@@ -0,0 +1,26 @@
[Unit]
Description=IM Server
Documentation=https://docs.wildfirechat.cn
Wants=network-online.target
After=network-online.target
[Service]
WorkingDirectory=/opt/im-server
ExecStart=/bin/sh bin/wildfirechat.sh 2>&1
# Let systemd restart this service always
Restart=always
RestartSec=5
# Specifies the maximum file descriptor number that can be opened by this process
LimitNOFILE=65536
# Specifies the maximum number of threads this process can create
TasksMax=infinity
# Disable timeout logic and wait until process is stopped
TimeoutStopSec=infinity
SendSIGKILL=no
[Install]
WantedBy=multi-user.target