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上是什么状态。
#### 检索已有问题
除了可以检索已有issue外,还可以去野火论坛查找已有问题,论坛地址为 https://bbs.wildfirechat.cn
#### star 我们
给我们点个star,可以及时接收到我们回复信息,另外也是对我们支持人员辛苦工作的认可,激励我们更好更快地为大家服务。
+58 -36
View File
@@ -4,10 +4,11 @@
## 功能特性
* 极致地硬件利用率,IM服务最低128M内存即可运行,上不封顶。
* 协议先进,采用MQTT+Protobuf组合,流量和性能极致优化。
* 性能强大,专业版IM服务支持百万在线和集群部署。
* 性能强大,专业版IM服务支持百万在线和集群部署,业内顶尖性能水平,详情参考野火性能报告。
* 部署运维简单,依赖服务少,稍加配置一键启动。
* 安全加密。网络连接AES加密。客户端数据库SqlCipher加密。安全无小事。
* 全平台客户端,四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。
* 全平台客户端,支持Android、iOS、Web、小程序、UniApp、Flutter、Windows、Mac OS、Linux等所有常见平台。
* 四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。
* 支持国产化。支持国产化操作系统、国产化芯片和国产操作系统。支持国密加密。
* 客户端使用微信[mars](https://github.com/tencent/mars)连接库,野火IM可能是最适应中国网络国情的即时通讯服务。
* 支持加速点加速,即可用于全球应用,也可用于政企内外双网复杂的网络环境。
@@ -15,38 +16,45 @@
* 音视频多种解决方案,丰俭由人,可自由选择。
* 高级音视频功能强大,支持9人以上的群组视频通话,支持1080P视频,支持会议模式,支持百人以上会议,支持服务器端录制。
* 全私有部署,可不依赖任何第三方服务,完全内网部署。
* 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。Demo成熟完善,基本可以做到开箱即用,也可把SDK嵌入其它应用。
* 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。代码开源率高,二次开发简单。
* 结构设计合理,客户端分功能库、UI库和应用三层。应用成熟完善,开箱即用,也可把SDK嵌入其它应用。
* 拥有应用开放平台,可以开发和创建自建应用,扩展您的工作台。
* API丰富,方便与其它服务系统的对接。
* 拥有机器人和频道功能。
* 代码开源率高,二次开发简单。可以先试用3到6个月,二开搞定后再购买。
* 拥有机器人和公众号功能。
* 社区版IM服务和移动端免费商用,可以免费构建出完备的移动互联网应用。
* 收费项目非常便宜,野火独有的试用模式,可以先部署到客户环境试用6个月,试用满意后再购买,避免上当受骗。
* 技术支持专业和高效,核心研发人员及时处理issue和论坛回复。
## 野火开源项目
主要包括以下项目:
| [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相关业务。 |
| [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) | 机器人服务,演示野火机器人对接其它系统的方法。 |
| [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-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) | 野火开放平台服务。 |
| [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源码。
| [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS 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)。 |
| [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](https://web.wildfirechat.cn) |
| [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台) |
| [uni-chat](https://github.com/wildfirechat/uni-chat) | [uni-chat](https://gitee.com/wfchat/uni-chat) | UniApp平台移动端应用(使用原生插件) |
| [uni-wfc-client](https://github.com/wildfirechat/uni-wfc-client) | [uni-wfc-client](https://gitee.com/wfchat/uni-wfc-client) | UniApp平台原生插件 |
| [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/) |
| [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应用服务,模拟客户的应用服登陆处理逻辑及部分二次开发示例。 |
| [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) | 推送服务器,可以对接所有的系统厂商推送服务或者第三方推送服务。 |
| [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 ) | 高级音视频媒体服务。 |
| [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服务。 |
| [channel-platform](https://github.com/wildfirechat/channel-platform) | [channel-platform](https://gitee.com/wfchat/channel-platform) | 野火频道(公众号)平台服务。 |
| [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码。 |
| [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS 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)。 |
| [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](https://web.wildfirechat.cn)。 |
| [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台)。 |
| [uni-chat](https://github.com/wildfirechat/uni-chat) | [uni-chat](https://gitee.com/wfchat/uni-chat) | UniApp平台移动端应用(使用原生插件)。 |
| [uni-wfc-client](https://github.com/wildfirechat/uni-wfc-client) | [uni-wfc-client](https://gitee.com/wfchat/uni-wfc-client) | UniApp平台原生插件。 |
| [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/)
## 野火性能测试报告及测试方法
[Github](https://github.com/wildfirechat/Performance_Test), [码云](https://gitee.com/wfchat/Performance_Test)。
## 野火Demo
请使用微信扫码下载安装体验野火IM移动客户端
@@ -60,6 +68,10 @@ PC客户端点[这里](https://github.com/wildfirechat/vue-pc-chat/releases)下
![野火IM](http://static.wildfirechat.net/wx.jpg)
## 快速使用
可以按照[快速开始](https://docs.wildfirechat.cn/quick_start/)来部署应用服务和IM服务,然后打包移动端,就可以完成聊天和通话功能。
之后再部署开放平台服务、频道(公众号)服务、机器人服务、推送服务、Turn服务(音视频服务)来实现完整的应用功能,详情可以详细阅读文档。
## 应用截图
![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```
> 由于使用了一个git的maven插件,如果本地没有git信息就会编译出错,请使用```git clone```的方法下载代码,或者下载压缩包解压后在根目录创建```.git```的空目录。建议用```git clone```的方式下载代码。
由于```distribution/pom.xml```中使用了生成RPM包的plugin,所以需要本地安装有rpm。如果不需要RPM包,可以删除掉```distribution/pom.xml```文件中的RPM plugin。
在安装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```。修改如下部分:
1. 修改```server.ip```为您的服务器的公网IP,或者域名。如果用域名需要做好域名解析。
2. 可以阅读配置文件注释和文档,对其它配置项进行调整。
解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```,可以阅读配置文件注释和文档,对其它配置项进行调整。
## 运行
在程序目录运行如下命令:
在解压```distribution-xxxx-bundle-tar.tar.gz```后的目录下运行如下命令:
```
./bin/wildfirechat.sh
```
## 验证
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改成您期望的,然后重启。
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版本之外的版本不会出现此问题。
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';```来修复。
4. 从0.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。
2. 0.50版本添加了是否允许客户端发送群操作通知的配置。如果您在客户端自定义群通知,需要在服务器端配置允许,没有使用自定义群操作通知的不受影响。***
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.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/)进行交流学习
3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。
<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相关业务。
-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)。
db.auto_clean_history_messages true
## h2db数据库的路径,默认为程序目录下的,可以指定其他目录。如果使用MySQL,可以忽略此配置
h2db.path ./h2db/wfchat
##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。
http.admin.secret_key 123456
@@ -139,12 +142,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000
@@ -190,11 +187,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#*********************************************************************
# 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"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<relativePath>../</relativePath>
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.94</version>
<version>0.97</version>
</parent>
<artifactId>moquette-broker</artifactId>
@@ -120,7 +120,7 @@
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
<version>1.13</version>
</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) {
ServerAPIHelper.sendRequest(fromUser, clientId, topic, message, new ServerAPIHelper.Callback() {
ServerAPIHelper.sendRequest(fromUser, clientId, topic, message, callback == null ? null : new ServerAPIHelper.Callback() {
@Override
public void onSuccess(byte[] result) {
if(callback != null) {
@@ -81,18 +81,6 @@ public class Request {
// request URI parameters
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
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 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";
@@ -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_Reject_Request_Duration = "friend.reject_request_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_Rejoin_When_Active = "chatroom.rejoin_when_active";
@@ -10,12 +10,10 @@ package io.moquette.imhandler;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf;
import cn.wildfirechat.common.ErrorCode;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.I18n;
import win.liyufan.im.IMTopic;
import win.liyufan.im.MessageShardingUtil;
@@ -43,17 +41,8 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
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();
if (m_messagesStore.isNewFriendWelcomeMessage()) {
contentBuilder.setType(92);
} else {
contentBuilder.setType(90).setContent(I18n.getString(language, "Above_Greeting_Message"));
}
contentBuilder.setType(92);
builder = WFCMessage.Message.newBuilder();
builder.setFromUser(request.getTargetUid());
@@ -64,13 +53,7 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId);
saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType);
if (m_messagesStore.isNewFriendWelcomeMessage()) {
contentBuilder.setType(93);
} else {
contentBuilder.setContent(I18n.getString(language, "Friend_Can_Start_Chat"));
}
contentBuilder.setType(93);
builder.setContent(contentBuilder);
messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId);
@@ -72,7 +72,7 @@ public class InterceptConnectMessage extends InterceptAbstractMessage {
}
public byte[] getPassword() {
return msg.payload().password();
return msg.payload().passwordInBytes();
}
public String getWillTopic() {
@@ -138,7 +138,6 @@ public class MemoryMessagesStore implements IMessagesStore {
private long mFriendRequestDuration = 7 * 24 * 60 * 60 * 1000;
private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000;
private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000;
private boolean mFriendNewWelcomeMessage = true;
private boolean mMultiPlatformNotification = false;
private boolean mMobileDefaultSilentWhenPCOnline = true;
@@ -256,15 +255,6 @@ public class MemoryMessagesStore implements IMessagesStore {
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 {
mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active));
} catch (Exception e) {
@@ -539,8 +529,17 @@ public class MemoryMessagesStore implements IMessagesStore {
int pullType = ProtoConstants.PullType.Pull_Normal;
if (type == ProtoConstants.ConversationType.ConversationType_Private) {
notifyReceivers.add(fromUser);
notifyReceivers.add(message.getConversation().getTarget());
if(message.getToCount() > 0) {
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;
} else if (type == ProtoConstants.ConversationType.ConversationType_Group) {
notifyReceivers.add(fromUser);
@@ -3363,10 +3362,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) {
try {
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();
} 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) {
e.printStackTrace();
@@ -3387,10 +3388,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) {
try {
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();
} 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) {
e.printStackTrace();
@@ -3496,11 +3499,6 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_AUTH_FAILURE;
}
@Override
public boolean isNewFriendWelcomeMessage() {
return mFriendNewWelcomeMessage;
}
@Override
public ErrorCode deleteFriend(String userId, String friendUid, long[] head) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
@@ -415,7 +415,7 @@ public class MemorySessionStore implements ISessionsStore {
remove = true;
}
} 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;
}
} else {
@@ -378,7 +378,7 @@ public class Server {
LOG.info("Configuring embedded Hazelcast instance");
serverIp = getServerIp(config);
String hzConfigPath = "config/hazelcast.xml";
String hzConfigPath = System.getProperty("hazelcast.configuration", "config/hazelcast.xml");
if (hzConfigPath != null) {
boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null;
Config hzconfig = isHzConfigOnClasspath
@@ -179,7 +179,6 @@ public interface IMessagesStore {
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);
boolean isNewFriendWelcomeMessage();
ErrorCode deleteFriend(String userId, String friendUid, long[] head);
ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head);
FriendData getFriendData(String fromUser, String targetUserId);
@@ -652,6 +652,8 @@ public class MessagesPublisher {
pushContent = "[文件]";
} else if(type == ProtoConstants.ContentType.Sticker) {
pushContent = "[表情]";
} else if (type == ProtoConstants.ContentType.Name_Card) {
pushContent = "[名片]";
}
}
@@ -308,7 +308,7 @@ public class ProtocolProcessor {
}
byte[] pwd = null;
if (msg.variableHeader().hasPassword()) {
pwd = msg.payload().password();
pwd = msg.payload().passwordInBytes();
MemorySessionStore.Session session = m_sessionsStore.getSession(clientId);
if (session == null) {
@@ -164,7 +164,7 @@ public class Qos1PublishHandler extends QosPublishHandler {
} else {
MemorySessionStore.Session session = m_sessionStore.getSession(clientID);
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();
GZIPOutputStream gzip = null;
try {
+32 -9
View File
@@ -5,7 +5,7 @@
* 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:
*
* 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 is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,13 +16,30 @@
package io.netty.handler.codec.mqtt;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
final class MqttCodecUtil {
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) {
// publish topic name must not contain any wildcard
@@ -38,20 +55,20 @@ final class MqttCodecUtil {
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) {
return clientId != null && clientId.length() >= MIN_CLIENT_ID_LENGTH &&
clientId.length() <= MAX_CLIENT_ID_LENGTH;
return clientId != null && clientId.length() >= MqttConstant.MIN_CLIENT_ID_LENGTH &&
clientId.length() <= maxClientIdLength;
}
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.
return clientId != null;
}
throw new IllegalArgumentException(mqttVersion + " is unknown mqtt version");
}
static MqttFixedHeader validateFixedHeader(MqttFixedHeader mqttFixedHeader) {
static MqttFixedHeader validateFixedHeader(ChannelHandlerContext ctx, MqttFixedHeader mqttFixedHeader) {
switch (mqttFixedHeader.messageType()) {
case PUBREL:
case SUBSCRIBE:
@@ -59,6 +76,12 @@ final class MqttCodecUtil {
if (mqttFixedHeader.qosLevel() != MqttQoS.AT_LEAST_ONCE) {
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:
return mqttFixedHeader;
}
@@ -75,7 +98,7 @@ final class MqttCodecUtil {
case UNSUBACK:
case PINGREQ:
case PINGRESP:
//case DISCONNECT:
case DISCONNECT:
if (mqttFixedHeader.isDup() ||
mqttFixedHeader.qosLevel() != MqttQoS.AT_MOST_ONCE ||
mqttFixedHeader.isRetain()) {
+3 -3
View File
@@ -5,7 +5,7 @@
* 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:
*
* 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 is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
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 {
@@ -28,7 +28,7 @@ public final class MqttConnAckMessage extends MqttMessage {
public MqttConnAckMessage(MqttFixedHeader mqttFixedHeader, MqttConnAckVariableHeader variableHeader, MqttConnectAckPayload payload) {
super(mqttFixedHeader, variableHeader, payload);
}
@Override
public MqttConnAckVariableHeader variableHeader() {
return (MqttConnAckVariableHeader) super.variableHeader();
+13 -1
View File
@@ -5,7 +5,7 @@
* 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:
*
* 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 is distributed on an "AS IS" BASIS, WITHOUT
@@ -27,9 +27,17 @@ public final class MqttConnAckVariableHeader {
private final boolean sessionPresent;
private final MqttProperties properties;
public MqttConnAckVariableHeader(MqttConnectReturnCode connectReturnCode, boolean sessionPresent) {
this(connectReturnCode, sessionPresent, MqttProperties.NO_PROPERTIES);
}
public MqttConnAckVariableHeader(MqttConnectReturnCode connectReturnCode, boolean sessionPresent,
MqttProperties properties) {
this.connectReturnCode = connectReturnCode;
this.sessionPresent = sessionPresent;
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public MqttConnectReturnCode connectReturnCode() {
@@ -40,6 +48,10 @@ public final class MqttConnAckVariableHeader {
return sessionPresent;
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
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 {
+65 -5
View File
@@ -5,7 +5,7 @@
* 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:
*
* 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 is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,6 +16,9 @@
package io.netty.handler.codec.mqtt;
import java.util.Arrays;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil;
/**
@@ -24,18 +27,55 @@ import io.netty.util.internal.StringUtil;
public final class MqttConnectPayload {
private final String clientIdentifier;
private final MqttProperties willProperties;
private final String willTopic;
private final String willMessage;
private final byte[] willMessage;
private final String userName;
private final byte[] password;
/**
* @deprecated use {@link MqttConnectPayload#MqttConnectPayload(String,
* MqttProperties, String, byte[], String, byte[])} instead
*/
@Deprecated
public MqttConnectPayload(
String clientIdentifier,
String willTopic,
String willMessage,
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) {
this.clientIdentifier = clientIdentifier;
this.willProperties = MqttProperties.withEmptyDefaults(willProperties);
this.willTopic = willTopic;
this.willMessage = willMessage;
this.userName = userName;
@@ -46,11 +86,23 @@ public final class MqttConnectPayload {
return clientIdentifier;
}
public MqttProperties willProperties() {
return willProperties;
}
public String willTopic() {
return willTopic;
}
/**
* @deprecated use {@link MqttConnectPayload#willMessageInBytes()} instead
*/
@Deprecated
public String willMessage() {
return willMessage == null ? null : new String(willMessage, CharsetUtil.UTF_8);
}
public byte[] willMessageInBytes() {
return willMessage;
}
@@ -58,7 +110,15 @@ public final class MqttConnectPayload {
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;
}
@@ -68,9 +128,9 @@ public final class MqttConnectPayload {
.append('[')
.append("clientIdentifier=").append(clientIdentifier)
.append(", willTopic=").append(willTopic)
.append(", willMessage=").append(willMessage)
.append(", willMessage=").append(Arrays.toString(willMessage))
.append(", userName=").append(userName)
.append(", password=").append(password)
.append(", password=").append(Arrays.toString(password))
.append(']')
.toString();
}
+42 -14
View File
@@ -5,7 +5,7 @@
* 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:
*
* 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 is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,31 +16,52 @@
package io.netty.handler.codec.mqtt;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Return Code of {@link MqttConnAckMessage}
*/
public enum MqttConnectReturnCode {
CONNECTION_ACCEPTED((byte) 0x00),
//MQTT 3 codes
CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION((byte) 0X01),
CONNECTION_REFUSED_IDENTIFIER_REJECTED((byte) 0x02),
CONNECTION_REFUSED_SERVER_UNAVAILABLE((byte) 0x03),
CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD((byte) 0x04),
CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05),
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 {
final Map<Byte, MqttConnectReturnCode> valueMap = new HashMap<Byte, MqttConnectReturnCode>();
for (MqttConnectReturnCode code: values()) {
valueMap.put(code.byteValue, code);
MqttConnectReturnCode[] values = values();
VALUES = new MqttConnectReturnCode[160];
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;
@@ -54,9 +75,16 @@ public enum MqttConnectReturnCode {
}
public static MqttConnectReturnCode valueOf(byte b) {
if (VALUE_TO_CODE_MAP.containsKey(b)) {
return VALUE_TO_CODE_MAP.get(b);
final int unsignedByte = b & 0xFF;
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
* 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
* 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 isCleanSession;
private final int keepAliveTimeSeconds;
private final MqttProperties properties;
public MqttConnectVariableHeader(
String name,
@@ -43,6 +44,29 @@ public final class MqttConnectVariableHeader {
boolean isWillFlag,
boolean isCleanSession,
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.version = version;
this.hasUserName = hasUserName;
@@ -52,6 +76,7 @@ public final class MqttConnectVariableHeader {
this.isWillFlag = isWillFlag;
this.isCleanSession = isCleanSession;
this.keepAliveTimeSeconds = keepAliveTimeSeconds;
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public String name() {
@@ -90,6 +115,10 @@ public final class MqttConnectVariableHeader {
return keepAliveTimeSeconds;
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
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
* 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
* 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.handler.codec.DecoderException;
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.internal.ObjectUtil;
import java.util.ArrayList;
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
* <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">
* the MQTT protocol specification v3.1</a>
* the MQTT protocol specification
* <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> {
private static final int DEFAULT_MAX_BYTES_IN_MESSAGE = 262144; //256K
public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState> {
/**
* States of the decoder.
@@ -58,31 +54,42 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private int bytesRemainingInVariablePart;
private final int maxBytesInMessage;
private final int maxClientIdLength;
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) {
this(maxBytesInMessage, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH);
}
public MqttDecoder(int maxBytesInMessage, int maxClientIdLength) {
super(DecoderState.READ_FIXED_HEADER);
this.maxBytesInMessage = maxBytesInMessage;
this.maxBytesInMessage = ObjectUtil.checkPositive(maxBytesInMessage, "maxBytesInMessage");
this.maxClientIdLength = ObjectUtil.checkPositive(maxClientIdLength, "maxClientIdLength");
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
switch (state()) {
case READ_FIXED_HEADER:
mqttFixedHeader = decodeFixedHeader(buffer);
case READ_FIXED_HEADER: try {
mqttFixedHeader = decodeFixedHeader(ctx, buffer);
bytesRemainingInVariablePart = mqttFixedHeader.remainingLength();
checkpoint(DecoderState.READ_VARIABLE_HEADER);
// fall through
} catch (Exception cause) {
out.add(invalidMessage(cause));
return;
}
case READ_VARIABLE_HEADER: try {
if (bytesRemainingInVariablePart > maxBytesInMessage) {
throw new DecoderException("too large message: " + bytesRemainingInVariablePart + " bytes");
}
final Result<?> decodedVariableHeader = decodeVariableHeader(buffer, mqttFixedHeader);
final Result<?> decodedVariableHeader = decodeVariableHeader(ctx, buffer, mqttFixedHeader);
variableHeader = decodedVariableHeader.value;
if (bytesRemainingInVariablePart > maxBytesInMessage) {
buffer.skipBytes(actualReadableBytes());
throw new TooLongFrameException("too large message: " + bytesRemainingInVariablePart + " bytes");
}
bytesRemainingInVariablePart -= decodedVariableHeader.numberOfBytesConsumed;
checkpoint(DecoderState.READ_PAYLOAD);
// fall through
@@ -94,9 +101,11 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
case READ_PAYLOAD: try {
final Result<?> decodedPayload =
decodePayload(
ctx,
buffer,
mqttFixedHeader.messageType(),
bytesRemainingInVariablePart,
maxClientIdLength,
variableHeader);
bytesRemainingInVariablePart -= decodedPayload.numberOfBytesConsumed;
if (bytesRemainingInVariablePart != 0) {
@@ -129,16 +138,20 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private MqttMessage invalidMessage(Throwable cause) {
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
* @return the fixed header
*/
private static MqttFixedHeader decodeFixedHeader(ByteBuf buffer) {
private static MqttFixedHeader decodeFixedHeader(ChannelHandlerContext ctx, ByteBuf buffer) {
short b1 = buffer.readUnsignedByte();
MqttMessageType messageType = MqttMessageType.valueOf(b1 >> 4);
@@ -146,6 +159,59 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
int qosLevel = (b1 & 0x06) >> 1;
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 multiplier = 1;
short digit;
@@ -163,7 +229,7 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
}
MqttFixedHeader decodedFixedHeader =
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
* @return the variable header
*/
private static Result<?> decodeVariableHeader(ByteBuf buffer, MqttFixedHeader mqttFixedHeader) {
private Result<?> decodeVariableHeader(ChannelHandlerContext ctx, ByteBuf buffer, MqttFixedHeader mqttFixedHeader) {
switch (mqttFixedHeader.messageType()) {
case CONNECT:
return decodeConnectionVariableHeader(buffer);
return decodeConnectionVariableHeader(ctx, buffer);
case CONNACK:
return decodeConnAckVariableHeader(buffer);
return decodeConnAckVariableHeader(ctx, buffer);
case SUBSCRIBE:
case UNSUBSCRIBE:
case SUBSCRIBE:
case SUBACK:
case UNSUBACK:
return decodeMessageIdAndPropertiesVariableHeader(ctx, buffer);
case PUBACK:
case PUBREC:
case PUBCOMP:
case PUBREL:
return decodeMessageIdVariableHeader(buffer);
return decodePubReplyMessage(buffer);
case PUBLISH:
return decodePublishVariableHeader(buffer, mqttFixedHeader);
return decodePublishVariableHeader(ctx, buffer, mqttFixedHeader);
case DISCONNECT:
case AUTH:
return decodeReasonCodeAndPropertiesVariableHeader(buffer);
case PINGREQ:
case PINGRESP:
case DISCONNECT:
// Empty variable header
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);
int numberOfBytesConsumed = protoString.numberOfBytesConsumed;
final byte protocolLevel = buffer.readByte();
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();
numberOfBytesConsumed += 1;
final Result<Integer> keepAlive = decodeMsbLsb(buffer);
numberOfBytesConsumed += keepAlive.numberOfBytesConsumed;
final int keepAlive = decodeMsbLsb(buffer);
numberOfBytesConsumed += 2;
final boolean hasUserName = (b1 & 0x80) == 0x80;
final boolean hasPassword = (b1 & 0x40) == 0x40;
@@ -223,69 +299,178 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
final int willQos = (b1 & 0x18) >> 3;
final boolean willFlag = (b1 & 0x04) == 0x04;
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;
if (!zeroReservedFlag) {
// 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.
// 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");
}
}
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(
mqttVersion.protocolName(),
mqttVersion.protocolLevel(),
version.protocolName(),
version.protocolLevel(),
hasUserName,
hasPassword,
willRetain,
willQos,
willFlag,
cleanSession,
keepAlive.value);
keepAlive,
properties);
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;
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 =
new MqttConnAckVariableHeader(MqttConnectReturnCode.valueOf(returnCode), sessionPresent);
new MqttConnAckVariableHeader(MqttConnectReturnCode.valueOf(returnCode), sessionPresent, properties);
return new Result<MqttConnAckVariableHeader>(mqttConnAckVariableHeader, numberOfBytesConsumed);
}
private static Result<MqttMessageIdVariableHeader> decodeMessageIdVariableHeader(ByteBuf buffer) {
final Result<Integer> messageId = decodeMessageId(buffer);
return new Result<MqttMessageIdVariableHeader>(
MqttMessageIdVariableHeader.from(messageId.value),
messageId.numberOfBytesConsumed);
private static Result<MqttMessageIdAndPropertiesVariableHeader> decodeMessageIdAndPropertiesVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer) {
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
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,
MqttFixedHeader mqttFixedHeader) {
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
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)");
}
int numberOfBytesConsumed = decodedTopic.numberOfBytesConsumed;
int messageId = -1;
if (mqttFixedHeader.qosLevel().value() > 0) {
final Result<Integer> decodedMessageId = decodeMessageId(buffer);
messageId = decodedMessageId.value;
numberOfBytesConsumed += decodedMessageId.numberOfBytesConsumed;
messageId = decodeMessageId(buffer);
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 MqttPublishVariableHeader mqttPublishVariableHeader =
new MqttPublishVariableHeader(decodedTopic.value, messageId);
new MqttPublishVariableHeader(decodedTopic.value, messageId, properties);
return new Result<MqttPublishVariableHeader>(mqttPublishVariableHeader, numberOfBytesConsumed);
}
private static Result<Integer> decodeMessageId(ByteBuf buffer) {
final Result<Integer> messageId = decodeMsbLsb(buffer);
if (!isValidMessageId(messageId.value)) {
throw new DecoderException("invalid messageId: " + messageId.value);
/**
* @return messageId with numberOfBytesConsumed is 2
*/
private static int decodeMessageId(ByteBuf buffer) {
final int messageId = decodeMsbLsb(buffer);
if (!MqttCodecUtil.isValidMessageId(messageId)) {
throw new DecoderException("invalid messageId: " + messageId);
}
return messageId;
}
@@ -300,13 +485,15 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
* @return the payload
*/
private static Result<?> decodePayload(
ChannelHandlerContext ctx,
ByteBuf buffer,
MqttMessageType messageType,
int bytesRemainingInVariablePart,
int maxClientIdLength,
Object variableHeader) {
switch (messageType) {
case CONNECT:
return decodeConnectionPayload(buffer, (MqttConnectVariableHeader) variableHeader);
return decodeConnectionPayload(buffer, maxClientIdLength, (MqttConnectVariableHeader) variableHeader);
case SUBSCRIBE:
return decodeSubscribePayload(buffer, bytesRemainingInVariablePart);
@@ -317,6 +504,9 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
case UNSUBSCRIBE:
return decodeUnsubscribePayload(buffer, bytesRemainingInVariablePart);
case UNSUBACK:
return decodeUnsubAckPayload(ctx, buffer, bytesRemainingInVariablePart);
case PUBLISH:
return decodePublishPayload(buffer, bytesRemainingInVariablePart);
@@ -328,42 +518,55 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private static Result<MqttConnectPayload> decodeConnectionPayload(
ByteBuf buffer,
int maxClientIdLength,
MqttConnectVariableHeader mqttConnectVariableHeader) {
final Result<String> decodedClientId = decodeString(buffer);
final String decodedClientIdValue = decodedClientId.value;
final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(mqttConnectVariableHeader.name(),
(byte) mqttConnectVariableHeader.version());
if (!isValidClientId(mqttVersion, decodedClientIdValue)) {
if (!MqttCodecUtil.isValidClientId(mqttVersion, maxClientIdLength, decodedClientIdValue)) {
throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + decodedClientIdValue);
}
int numberOfBytesConsumed = decodedClientId.numberOfBytesConsumed;
Result<String> decodedWillTopic = null;
Result<String> decodedWillMessage = null;
byte[] decodedWillMessage = null;
final MqttProperties willProperties;
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);
numberOfBytesConsumed += decodedWillTopic.numberOfBytesConsumed;
decodedWillMessage = decodeAsciiString(buffer);
numberOfBytesConsumed += decodedWillMessage.numberOfBytesConsumed;
decodedWillMessage = decodeByteArray(buffer);
numberOfBytesConsumed += decodedWillMessage.length + 2;
} else {
willProperties = MqttProperties.NO_PROPERTIES;
}
Result<String> decodedUserName = null;
Result<byte[]> decodedPassword = null;
byte[] decodedPassword = null;
if (mqttConnectVariableHeader.hasUserName()) {
decodedUserName = decodeString(buffer);
numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed;
}
if (mqttConnectVariableHeader.hasPassword()) {
decodedPassword = decodeByte(buffer);
numberOfBytesConsumed += decodedPassword.numberOfBytesConsumed;
decodedPassword = decodeByteArray(buffer);
numberOfBytesConsumed += decodedPassword.length + 2;
}
final MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload(
decodedClientId.value,
willProperties,
decodedWillTopic != null ? decodedWillTopic.value : null,
decodedWillMessage != null ? decodedWillMessage.value : null,
decodedWillMessage,
decodedUserName != null ? decodedUserName.value : null,
decodedPassword != null ? decodedPassword.value : null);
decodedPassword);
return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed);
}
@@ -375,9 +578,21 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
final Result<String> decodedTopicName = decodeString(buffer);
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++;
subscribeTopics.add(new MqttTopicSubscription(decodedTopicName.value, MqttQoS.valueOf(qos)));
subscribeTopics.add(new MqttTopicSubscription(decodedTopicName.value, subscriptionOption));
}
return new Result<MqttSubscribePayload>(new MqttSubscribePayload(subscribeTopics), numberOfBytesConsumed);
}
@@ -385,16 +600,30 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private static Result<MqttSubAckPayload> decodeSubackPayload(
ByteBuf buffer,
int bytesRemainingInVariablePart) {
final List<Integer> grantedQos = new ArrayList<Integer>();
final List<Integer> grantedQos = new ArrayList<Integer>(bytesRemainingInVariablePart);
int numberOfBytesConsumed = 0;
while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
int qos = buffer.readUnsignedByte() & 0x03;
int reasonCode = buffer.readUnsignedByte();
numberOfBytesConsumed++;
grantedQos.add(qos);
grantedQos.add(reasonCode);
}
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(
ByteBuf buffer,
int bytesRemainingInVariablePart) {
@@ -419,25 +648,9 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
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) {
final Result<Integer> decodedSize = decodeMsbLsb(buffer);
int size = decodedSize.value;
int numberOfBytesConsumed = decodedSize.numberOfBytesConsumed;
int size = decodeMsbLsb(buffer);
int numberOfBytesConsumed = 2;
if (size < minBytes || size > maxBytes) {
buffer.skipBytes(size);
numberOfBytesConsumed += size;
@@ -449,35 +662,68 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
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;
int numberOfBytesConsumed = decodedSize.numberOfBytesConsumed;
if (size < minBytes || size > maxBytes) {
buffer.skipBytes(size);
numberOfBytesConsumed += size;
return new Result<>(null, numberOfBytesConsumed);
}
byte[] s = new byte[size];
buffer.getBytes(buffer.readerIndex(), s, 0, size);
buffer.skipBytes(size);
numberOfBytesConsumed += size;
return new Result<>(s, numberOfBytesConsumed);
/**
*
* @return the decoded byte[], numberOfBytesConsumed = byte[].length + 2
*/
private static byte[] decodeByteArray(ByteBuf buffer) {
int size = decodeMsbLsb(buffer);
byte[] bytes = new byte[size];
buffer.readBytes(bytes);
return bytes;
}
private static Result<Integer> decodeMsbLsb(ByteBuf buffer) {
return decodeMsbLsb(buffer, 0, 65535);
// packing utils to reduce the amount of garbage while decoding ints
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 lsbSize = buffer.readUnsignedByte();
final int numberOfBytesConsumed = 2;
int result = msbSize << 8 | lsbSize;
if (result < min || result > max) {
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> {
@@ -490,4 +736,82 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
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
* 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
* 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.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.EncoderException;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.EmptyArrays;
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
* 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
public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
@@ -41,49 +44,57 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
@Override
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.
* It's only visible for testing.
*
* @param byteBufAllocator Allocates ByteBuf
* @param message MQTT message to encode
* @return ByteBuf with encoded bytes
*/
static ByteBuf doEncode(ByteBufAllocator byteBufAllocator, MqttMessage message) {
static ByteBuf doEncode(ChannelHandlerContext ctx,
MqttMessage message) {
switch (message.fixedHeader().messageType()) {
case CONNECT:
return encodeConnectMessage(byteBufAllocator, (MqttConnectMessage) message);
return encodeConnectMessage(ctx, (MqttConnectMessage) message);
case CONNACK:
return encodeConnAckMessage(byteBufAllocator, (MqttConnAckMessage) message);
return encodeConnAckMessage(ctx, (MqttConnAckMessage) message);
case PUBLISH:
return encodePublishMessage(byteBufAllocator, (MqttPublishMessage) message);
return encodePublishMessage(ctx, (MqttPublishMessage) message);
case SUBSCRIBE:
return encodeSubscribeMessage(byteBufAllocator, (MqttSubscribeMessage) message);
return encodeSubscribeMessage(ctx, (MqttSubscribeMessage) message);
case UNSUBSCRIBE:
return encodeUnsubscribeMessage(byteBufAllocator, (MqttUnsubscribeMessage) message);
return encodeUnsubscribeMessage(ctx, (MqttUnsubscribeMessage) message);
case SUBACK:
return encodeSubAckMessage(byteBufAllocator, (MqttSubAckMessage) message);
return encodeSubAckMessage(ctx, (MqttSubAckMessage) message);
case UNSUBACK:
if (message instanceof MqttUnsubAckMessage) {
return encodeUnsubAckMessage(ctx, (MqttUnsubAckMessage) message);
}
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
case PUBACK:
case PUBREC:
case PUBREL:
case PUBCOMP:
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(byteBufAllocator, message);
return encodePubReplyMessage(ctx, message);
case DISCONNECT:
case AUTH:
return encodeReasonCodePlusPropertiesMessage(ctx, message);
case PINGREQ:
case PINGRESP:
case DISCONNECT:
return encodeMessageWithOnlySingleByteFixedHeader(byteBufAllocator, message);
return encodeMessageWithOnlySingleByteFixedHeader(ctx.alloc(), message);
default:
throw new IllegalArgumentException(
@@ -92,7 +103,7 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
}
private static ByteBuf encodeConnectMessage(
ByteBufAllocator byteBufAllocator,
ChannelHandlerContext ctx,
MqttConnectMessage message) {
int payloadBufferSize = 0;
@@ -101,70 +112,96 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
MqttConnectPayload payload = message.payload();
MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(variableHeader.name(),
(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
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);
}
byte[] clientIdentifierBytes = encodeStringUtf8(clientIdentifier);
payloadBufferSize += 2 + clientIdentifierBytes.length;
int clientIdentifierBytes = utf8Bytes(clientIdentifier);
payloadBufferSize += 2 + clientIdentifierBytes;
// Will topic and message
String willTopic = payload.willTopic();
byte[] willTopicBytes = willTopic != null ? encodeStringUtf8(willTopic) : EmptyArrays.EMPTY_BYTES;
String willMessage = payload.willMessage();
byte[] willMessageBytes = willMessage != null ? encodeStringUtf8(willMessage) : EmptyArrays.EMPTY_BYTES;
int willTopicBytes = nullableUtf8Bytes(willTopic);
byte[] willMessage = payload.willMessageInBytes();
byte[] willMessageBytes = willMessage != null ? willMessage : EmptyArrays.EMPTY_BYTES;
if (variableHeader.isWillFlag()) {
payloadBufferSize += 2 + willTopicBytes.length;
payloadBufferSize += 2 + willTopicBytes;
payloadBufferSize += 2 + willMessageBytes.length;
}
String userName = payload.userName();
byte[] userNameBytes = userName != null ? encodeStringUtf8(userName) : EmptyArrays.EMPTY_BYTES;
int userNameBytes = nullableUtf8Bytes(userName);
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()) {
payloadBufferSize += 2 + passwordBytes.length;
}
// Fixed header
// Fixed and variable header
byte[] protocolNameBytes = mqttVersion.protocolNameBytes();
int variableHeaderBufferSize = 2 + protocolNameBytes.length + 4;
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
ByteBuf propertiesBuf = encodePropertiesIfNeeded(
mqttVersion,
ctx.alloc(),
message.variableHeader().properties());
try {
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);
buf.writeBytes(protocolNameBytes);
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
buf.writeByte(variableHeader.version());
buf.writeByte(getConnVariableHeaderFlag(variableHeader));
buf.writeShort(variableHeader.keepAliveTimeSeconds());
buf.writeShort(protocolNameBytes.length);
buf.writeBytes(protocolNameBytes);
// Payload
buf.writeShort(clientIdentifierBytes.length);
buf.writeBytes(clientIdentifierBytes, 0, clientIdentifierBytes.length);
if (variableHeader.isWillFlag()) {
buf.writeShort(willTopicBytes.length);
buf.writeBytes(willTopicBytes, 0, willTopicBytes.length);
buf.writeShort(willMessageBytes.length);
buf.writeBytes(willMessageBytes, 0, willMessageBytes.length);
buf.writeByte(variableHeader.version());
buf.writeByte(getConnVariableHeaderFlag(variableHeader));
buf.writeShort(variableHeader.keepAliveTimeSeconds());
buf.writeBytes(propertiesBuf);
// Payload
writeExactUTF8String(buf, clientIdentifier, clientIdentifierBytes);
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) {
@@ -189,149 +226,285 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
}
private static ByteBuf encodeConnAckMessage(
ByteBufAllocator byteBufAllocator,
ChannelHandlerContext ctx,
MqttConnAckMessage message) {
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 = 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());
}
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.variableHeader().properties());
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(
ByteBufAllocator byteBufAllocator,
ChannelHandlerContext ctx,
MqttSubscribeMessage message) {
int variableHeaderBufferSize = 2;
int payloadBufferSize = 0;
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttMessageIdVariableHeader variableHeader = message.variableHeader();
MqttSubscribePayload payload = message.payload();
try {
final int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
int payloadBufferSize = 0;
for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
String topicName = topic.topicName();
byte[] topicNameBytes = encodeStringUtf8(topicName);
payloadBufferSize += 2 + topicNameBytes.length;
payloadBufferSize += 1;
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttMessageIdVariableHeader variableHeader = message.variableHeader();
MqttSubscribePayload payload = message.payload();
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(
ByteBufAllocator byteBufAllocator,
ChannelHandlerContext ctx,
MqttUnsubscribeMessage message) {
int variableHeaderBufferSize = 2;
int payloadBufferSize = 0;
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttMessageIdVariableHeader variableHeader = message.variableHeader();
MqttUnsubscribePayload payload = message.payload();
try {
final int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
int payloadBufferSize = 0;
for (String topicName : payload.topics()) {
byte[] topicNameBytes = encodeStringUtf8(topicName);
payloadBufferSize += 2 + topicNameBytes.length;
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttMessageIdVariableHeader variableHeader = message.variableHeader();
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(
ByteBufAllocator byteBufAllocator,
ChannelHandlerContext ctx,
MqttSubAckMessage message) {
int variableHeaderBufferSize = 2;
int payloadBufferSize = message.payload().grantedQoSLevels().size();
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, variablePartSize);
buf.writeShort(message.variableHeader().messageId());
for (int qos : message.payload().grantedQoSLevels()) {
buf.writeByte(qos);
}
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
try {
int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
int payloadBufferSize = message.payload().grantedQoSLevels().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);
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(
ByteBufAllocator byteBufAllocator,
ChannelHandlerContext ctx,
MqttPublishMessage message) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttPublishVariableHeader variableHeader = message.variableHeader();
ByteBuf payload = message.payload().duplicate();
String topicName = variableHeader.topicName();
byte[] topicNameBytes = encodeStringUtf8(topicName);
int topicNameBytes = utf8Bytes(topicName);
int variableHeaderBufferSize = 2 + topicNameBytes.length +
(mqttFixedHeader.qosLevel().value() > 0 ? 2 : 0);
int payloadBufferSize = payload.readableBytes();
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.variableHeader().properties());
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
buf.writeShort(topicNameBytes.length);
buf.writeBytes(topicNameBytes);
if (mqttFixedHeader.qosLevel().value() > 0) {
buf.writeShort(variableHeader.messageId());
try {
int variableHeaderBufferSize = 2 + topicNameBytes +
(mqttFixedHeader.qosLevel().value() > 0 ? 2 : 0) + propertiesBuf.readableBytes();
int payloadBufferSize = payload.readableBytes();
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
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(
@@ -342,12 +515,11 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
int msgId = variableHeader.messageId();
int variableHeaderBufferSize = 2; // variable part only has a message id
ByteBuf payload = null;
if (message.payload() != null) {
payload = ((ByteBuf)message.payload()).duplicate();
variableHeaderBufferSize += payload.readableBytes();
}
}
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
@@ -355,11 +527,53 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
buf.writeShort(msgId);
if (payload != null) {
buf.writeBytes(payload);
}
}
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(
ByteBufAllocator byteBufAllocator,
MqttMessage message) {
@@ -371,6 +585,104 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
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) {
int ret = 0;
ret |= header.messageType().value() << 4;
@@ -395,6 +707,42 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} 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) {
int count = 0;
do {
@@ -404,7 +752,4 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
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
* 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
* 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;
/**
* 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>
*/
public final class MqttFixedHeader {
@@ -5,7 +5,7 @@
* 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:
*
* 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 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
* 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
* 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 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) {
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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -15,10 +15,14 @@
*/
package io.netty.handler.codec.mqtt;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class MqttMessageBuilders {
@@ -29,6 +33,7 @@ public final class MqttMessageBuilders {
private MqttQoS qos;
private ByteBuf payload;
private int messageId;
private MqttProperties mqttProperties;
PublishBuilder() {
}
@@ -58,9 +63,15 @@ public final class MqttMessageBuilders {
return this;
}
public PublishBuilder properties(MqttProperties properties) {
this.mqttProperties = properties;
return this;
}
public MqttPublishMessage build() {
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));
}
}
@@ -73,13 +84,15 @@ public final class MqttMessageBuilders {
private boolean hasUser;
private boolean hasPassword;
private int keepAliveSecs;
private MqttProperties willProperties = MqttProperties.NO_PROPERTIES;
private boolean willFlag;
private boolean willRetain;
private MqttQoS willQos = MqttQoS.AT_MOST_ONCE;
private String willTopic;
private String willMessage;
private byte[] willMessage;
private String username;
private byte[] password;
private MqttProperties properties = MqttProperties.NO_PROPERTIES;
ConnectBuilder() {
}
@@ -119,7 +132,16 @@ public final class MqttMessageBuilders {
return this;
}
/**
* @deprecated use {@link ConnectBuilder#willMessage(byte[])} instead
*/
@Deprecated
public ConnectBuilder willMessage(String willMessage) {
willMessage(willMessage == null ? null : willMessage.getBytes(CharsetUtil.UTF_8));
return this;
}
public ConnectBuilder willMessage(byte[] willMessage) {
this.willMessage = willMessage;
return this;
}
@@ -129,6 +151,11 @@ public final class MqttMessageBuilders {
return this;
}
public ConnectBuilder willProperties(MqttProperties willProperties) {
this.willProperties = willProperties;
return this;
}
public ConnectBuilder hasUser(boolean value) {
this.hasUser = value;
return this;
@@ -140,17 +167,31 @@ public final class MqttMessageBuilders {
}
public ConnectBuilder username(String username) {
this.hasUser = true;
this.hasUser = username != null;
this.username = username;
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) {
this.hasPassword = true;
this.hasPassword = password != null;
this.password = password;
return this;
}
public ConnectBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttConnectMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
@@ -164,9 +205,10 @@ public final class MqttMessageBuilders {
willQos.value(),
willFlag,
cleanSession,
keepAliveSecs);
keepAliveSecs,
properties);
MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload(clientId, willTopic, willMessage, username, password);
new MqttConnectPayload(clientId, willProperties, willTopic, willMessage, username, password);
return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload);
}
}
@@ -175,36 +217,54 @@ public final class MqttMessageBuilders {
private List<MqttTopicSubscription> subscriptions;
private int messageId;
private MqttProperties properties;
SubscribeBuilder() {
}
public SubscribeBuilder addSubscription(MqttQoS qos, String topic) {
if (subscriptions == null) {
subscriptions = new ArrayList<MqttTopicSubscription>(5);
}
ensureSubscriptionsExist();
subscriptions.add(new MqttTopicSubscription(topic, qos));
return this;
}
public SubscribeBuilder addSubscription(String topic, MqttSubscriptionOption option) {
ensureSubscriptionsExist();
subscriptions.add(new MqttTopicSubscription(topic, option));
return this;
}
public SubscribeBuilder messageId(int messageId) {
this.messageId = messageId;
return this;
}
public SubscribeBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttSubscribeMessage build() {
MqttFixedHeader mqttFixedHeader =
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);
return new MqttSubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
}
private void ensureSubscriptionsExist() {
if (subscriptions == null) {
subscriptions = new ArrayList<MqttTopicSubscription>(5);
}
}
}
public static final class UnsubscribeBuilder {
private List<String> topicFilters;
private int messageId;
private MqttProperties properties;
UnsubscribeBuilder() {
}
@@ -222,21 +282,33 @@ public final class MqttMessageBuilders {
return this;
}
public UnsubscribeBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttUnsubscribeMessage build() {
MqttFixedHeader mqttFixedHeader =
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);
return new MqttUnsubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
}
}
public interface PropertiesInitializer<T> {
void apply(T builder);
}
public static final class ConnAckBuilder {
private MqttConnectReturnCode returnCode;
private boolean sessionPresent;
private MqttProperties properties = MqttProperties.NO_PROPERTIES;
private ConnAckPropertiesBuilder propsBuilder;
ConnAckBuilder() {
private ConnAckBuilder() {
}
public ConnAckBuilder returnCode(MqttConnectReturnCode returnCode) {
@@ -249,15 +321,402 @@ public final class MqttMessageBuilders {
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() {
if (propsBuilder != null) {
properties = propsBuilder.build();
}
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttConnAckVariableHeader mqttConnAckVariableHeader =
new MqttConnAckVariableHeader(returnCode, sessionPresent);
new MqttConnAckVariableHeader(returnCode, sessionPresent, properties);
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() {
return new ConnectBuilder();
}
@@ -278,6 +737,26 @@ public final class MqttMessageBuilders {
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() {
}
}
+16 -3
View File
@@ -5,7 +5,7 @@
* 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:
*
* 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 is distributed on an "AS IS" BASIS, WITHOUT
@@ -50,7 +50,8 @@ public final class MqttMessageFactory {
case UNSUBACK:
return new MqttUnsubAckMessage(
mqttFixedHeader,
(MqttMessageIdVariableHeader) variableHeader);
(MqttMessageIdVariableHeader) variableHeader,
(MqttUnsubAckPayload) payload);
case UNSUBSCRIBE:
return new MqttUnsubscribeMessage(
@@ -65,17 +66,24 @@ public final class MqttMessageFactory {
(ByteBuf) payload);
case PUBACK:
//Having MqttPubReplyMessageVariableHeader or MqttMessageIdVariableHeader
return new MqttPubAckMessage(mqttFixedHeader, (MqttMessageIdVariableHeader) variableHeader);
case PUBREC:
case PUBREL:
case PUBCOMP:
//Having MqttPubReplyMessageVariableHeader or MqttMessageIdVariableHeader
return new MqttMessage(mqttFixedHeader, variableHeader);
case PINGREQ:
case PINGRESP:
case DISCONNECT:
return new MqttMessage(mqttFixedHeader);
case DISCONNECT:
case AUTH:
//Having MqttReasonCodeAndPropertiesVariableHeader
return new MqttMessage(mqttFixedHeader,
variableHeader);
default:
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));
}
public static MqttMessage newInvalidMessage(MqttFixedHeader mqttFixedHeader, Object variableHeader,
Throwable cause) {
return new MqttMessage(mqttFixedHeader, variableHeader, null, DecoderResult.failure(cause));
}
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
* 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
* 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
* 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;
@@ -33,7 +33,7 @@ public final class MqttMessageIdVariableHeader {
return new MqttMessageIdVariableHeader(messageId);
}
private MqttMessageIdVariableHeader(int messageId) {
protected MqttMessageIdVariableHeader(int messageId) {
this.messageId = messageId;
}
@@ -49,4 +49,12 @@ public final class MqttMessageIdVariableHeader {
.append(']')
.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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -33,7 +33,24 @@ public enum MqttMessageType {
UNSUBACK(11),
PINGREQ(12),
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;
@@ -46,12 +63,10 @@ public enum MqttMessageType {
}
public static MqttMessageType valueOf(int type) {
for (MqttMessageType t : values()) {
if (t.value == type) {
return t;
}
if (type <= 0 || type >= VALUES.length) {
throw new IllegalArgumentException("unknown message type: " + type);
}
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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
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 {
@@ -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
* 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
* 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.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 {
@@ -44,11 +44,7 @@ public class MqttPublishMessage extends MqttMessage implements ByteBufHolder {
@Override
public ByteBuf content() {
final ByteBuf data = (ByteBuf) super.payload();
if (data.refCnt() <= 0) {
throw new IllegalReferenceCountException(data.refCnt());
}
return data;
return ByteBufUtil.ensureAccessible((ByteBuf) super.payload());
}
@Override
+16 -2
View File
@@ -5,7 +5,7 @@
* 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:
*
* 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 is distributed on an "AS IS" BASIS, WITHOUT
@@ -24,11 +24,17 @@ import io.netty.util.internal.StringUtil;
public final class MqttPublishVariableHeader {
private final String topicName;
private final int packetId;
private int packetId;
private final MqttProperties properties;
public MqttPublishVariableHeader(String topicName, int packetId) {
this(topicName, packetId, MqttProperties.NO_PROPERTIES);
}
public MqttPublishVariableHeader(String topicName, int packetId, MqttProperties properties) {
this.topicName = topicName;
this.packetId = packetId;
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public String topicName() {
@@ -47,6 +53,14 @@ public final class MqttPublishVariableHeader {
return packetId;
}
public MqttProperties properties() {
return properties;
}
public void setPacketId(int packetId) {
this.packetId = packetId;
}
@Override
public String toString() {
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
* 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
* 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) {
for (MqttQoS q: values()) {
if (q.value == value) {
return q;
}
switch (value) {
case 0:
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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,22 +17,33 @@
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 MqttSubAckMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttSubAckPayload payload) {
super(mqttFixedHeader, variableHeader, payload);
}
public MqttSubAckMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttSubAckPayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override
public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader();
}
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override
public MqttSubAckPayload 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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,6 +16,7 @@
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList;
@@ -27,43 +28,51 @@ import java.util.List;
*/
public class MqttSubAckPayload {
private final List<Integer> grantedQoSLevels;
private final List<Integer> reasonCodes;
public MqttSubAckPayload(int... grantedQoSLevels) {
if (grantedQoSLevels == null) {
throw new NullPointerException("grantedQoSLevels");
}
public MqttSubAckPayload(int... reasonCodes) {
ObjectUtil.checkNotNull(reasonCodes, "reasonCodes");
List<Integer> list = new ArrayList<Integer>(grantedQoSLevels.length);
for (int v: grantedQoSLevels) {
List<Integer> list = new ArrayList<Integer>(reasonCodes.length);
for (int v: reasonCodes) {
list.add(v);
}
this.grantedQoSLevels = Collections.unmodifiableList(list);
this.reasonCodes = Collections.unmodifiableList(list);
}
public MqttSubAckPayload(Iterable<Integer> grantedQoSLevels) {
if (grantedQoSLevels == null) {
throw new NullPointerException("grantedQoSLevels");
}
public MqttSubAckPayload(Iterable<Integer> reasonCodes) {
ObjectUtil.checkNotNull(reasonCodes, "reasonCodes");
List<Integer> list = new ArrayList<Integer>();
for (Integer v: grantedQoSLevels) {
for (Integer v: reasonCodes) {
if (v == null) {
break;
}
list.add(v);
}
this.grantedQoSLevels = Collections.unmodifiableList(list);
this.reasonCodes = Collections.unmodifiableList(list);
}
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
public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this))
.append('[')
.append("grantedQoSLevels=").append(grantedQoSLevels)
.append("reasonCodes=").append(reasonCodes)
.append(']')
.toString();
}
+14 -3
View File
@@ -5,7 +5,7 @@
* 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:
*
* 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 is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,23 +17,34 @@
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>
*/
public final class MqttSubscribeMessage extends MqttMessage {
public MqttSubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttSubscribePayload payload) {
super(mqttFixedHeader, variableHeader, payload);
}
public MqttSubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttSubscribePayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override
public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader();
}
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override
public MqttSubscribePayload 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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -39,11 +39,12 @@ public final class MqttSubscribePayload {
@Override
public String toString() {
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(topicSubscriptions.size() - 1));
builder.append(']');
return builder.toString();
if (!topicSubscriptions.isEmpty()) {
builder.setLength(builder.length() - 2);
}
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
* 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
* 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 {
private final String topicFilter;
private final MqttQoS qualityOfService;
private final MqttSubscriptionOption option;
public MqttTopicSubscription(String topicFilter, MqttQoS qualityOfService) {
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() {
@@ -37,7 +42,11 @@ public final class MqttTopicSubscription {
}
public MqttQoS qualityOfService() {
return qualityOfService;
return option.qos();
}
public MqttSubscriptionOption option() {
return option;
}
@Override
@@ -45,7 +54,7 @@ public final class MqttTopicSubscription {
return new StringBuilder(StringUtil.simpleClassName(this))
.append('[')
.append("topicFilter=").append(topicFilter)
.append(", qualityOfService=").append(qualityOfService)
.append(", option=").append(this.option)
.append(']')
.toString();
}
@@ -5,7 +5,7 @@
* 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:
*
* 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 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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,16 +17,46 @@
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 MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader, MqttMessageIdVariableHeader variableHeader) {
super(mqttFixedHeader, variableHeader, null);
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
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
public MqttMessageIdVariableHeader 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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,23 +17,34 @@
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>
*/
public final class MqttUnsubscribeMessage extends MqttMessage {
public MqttUnsubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttUnsubscribePayload payload) {
super(mqttFixedHeader, variableHeader, payload);
}
public MqttUnsubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttUnsubscribePayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override
public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader();
}
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override
public MqttUnsubscribePayload 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
* 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
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -39,11 +39,12 @@ public final class MqttUnsubscribePayload {
@Override
public String toString() {
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(topics.size() - 1))
.append(']');
return builder.toString();
if (!topics.isEmpty()) {
builder.setLength(builder.length() - 2);
}
return builder.append("]").toString();
}
}
+25 -15
View File
@@ -5,7 +5,7 @@
* 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:
*
* 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 is distributed on an "AS IS" BASIS, WITHOUT
@@ -25,7 +25,7 @@ import io.netty.util.internal.ObjectUtil;
public enum MqttVersion {
MQTT_3_1("MQIsdp", (byte) 3),
MQTT_3_1_1("MQTT", (byte) 4),
Wildfire_1("MQTT", (byte) 5),
MQTT_5("MQTT", (byte) 5),
Wildfire_2("MQTT", (byte) 6),
Wildfire_Future("MQTT", (byte) 7);
@@ -50,21 +50,31 @@ public enum MqttVersion {
}
public static MqttVersion fromProtocolNameAndLevel(String protocolName, byte protocolLevel) {
for (MqttVersion mv : values()) {
if (mv.name.equals(protocolName)) {
if (mv.level == protocolLevel) {
return mv;
} else {
continue;
}
}
MqttVersion mv = null;
switch (protocolLevel) {
case 3:
mv = MQTT_3_1;
break;
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;
}
throw new MqttUnacceptableProtocolVersionException(protocolName + "is unknown protocol name");
}
return mv;
}
public static MqttVersion fromProtocolLevel(int protocolLevel) {
for (MqttVersion mv : values()) {
if (mv.level == (byte )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
* 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
* 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.server.config.IConfig;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,6 +49,7 @@ public class DBUtil {
public static void init(IConfig config) {
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));
if (embedDB != null && embedDB.equals("1")) {
IsEmbedDB = true;
@@ -68,7 +68,7 @@ public class DBUtil {
migrateLocation = "filesystem:./migrate/h2";
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.setPassword("SA");
comboPooledDataSource.setMinPoolSize(5);
@@ -68,7 +68,9 @@ public class GitRepositoryState {
public static GitRepositoryState getGitRepositoryState() throws IOException {
Properties properties = new Properties();
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));
properties.load(bf);
} catch (IOException e) {
@@ -87,8 +87,8 @@ public class HttpUtils {
}
System.out.println(logText);
e.printStackTrace();
LOG.info(logText);
LOG.info("POST to {} with data {} failure", url, jsonStr);
LOG.error(logText);
LOG.error("POST to {} with data {} failure", url, jsonStr);
if(httpCallback != null) {
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>
<!--自定义一些常量,之后使用${变量名}引用-->
<Property name="MSG_LOG_HOME">./logs</Property>
<Property name="logFileName">test.log</Property>
<property name="charset">UTF-8</property>
<!--自定义的输出格式-->
<property name="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.94</version>
<version>0.97</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -60,6 +60,7 @@ public interface APIPath {
String Msg_ConvRead = "/admin/message/conv_read";
String Msg_Delivery = "/admin/message/delivery";
String Conversation_Delete = "/admin/conversation/delete";
String Msg_Clear_By_User = "/admin/message/clear_by_user";
String Create_Group = "/admin/group/create";
String Group_Dismiss = "/admin/group/del";
@@ -91,6 +92,7 @@ public interface APIPath {
String Conference_List_Participant = "/admin/conference/list_participant";
String Conference_Create = "/admin/conference/create";
String Conference_Destroy = "/admin/conference/destroy";
String Conference_Recording = "/admin/conference/recording";
String Channel_User_Info = "/channel/user_info";
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 cover;
public String title;
public String digest;
public String url;
public boolean rr;
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.cover = cover;
this.title = title;
this.digest = digest;
this.url = url;
this.rr = rr;
}
@@ -33,14 +35,14 @@ public class ArticleContent {
public ArticleContent() {
}
public ArticleContent(String id, String cover, String title, String url, boolean rr) {
this.top = new Article(id, cover, title, url, rr);
public ArticleContent(String id, String cover, String title, String digest, String url, boolean 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)
subArticles = new ArrayList<>();
subArticles.add(new Article(id, cover, title, url, rr));
subArticles.add(new Article(id, cover, title, digest, url, rr));
return this;
}
@@ -0,0 +1,26 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <heavyrain.lee@gmail.com>
*
* 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 <heavyrain.lee@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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 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>-->
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.94</version>
<version>0.97</version>
</parent>
<artifactId>distribution</artifactId>
@@ -93,7 +93,112 @@
</execution>
</executions>
</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>
</build>
</project>
+4 -4
View File
@@ -21,13 +21,13 @@
</fileSet>
<fileSet>
<directory>../broker/config/i18n/</directory>
<outputDirectory>/config/i18n</outputDirectory>
<directory>src/main/resources/</directory>
<outputDirectory>config/</outputDirectory>
</fileSet>
<fileSet>
<directory>src/main/resources/</directory>
<outputDirectory>config/</outputDirectory>
<directory>../systemd/</directory>
<outputDirectory>/systemd</outputDirectory>
</fileSet>
</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版。 ***
7. *** 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端) ***
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:
1. Server SDK添加删除会话接口
+2 -3
View File
@@ -4,11 +4,10 @@
<!--monitorInterval="30" :间隔秒数,自动检测配置文件的变更和重新配置本身-->
<configuration status="WARN" monitorInterval="60">
<Properties>
<!--自定义一些常量,之后使用${变量名}引用-->
<!-- 日志路径-->
<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="pattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</property>
</Properties>
<!--appenders:定义输出内容,输出格式,输出方式,日志保存策略等,常用其下三种标签[console,File,RollingFile]-->
@@ -31,6 +31,7 @@ local_port 80
## 是否使用内置DB。0使用mysql;1使用h2db。
## MySQL需要把事物隔离级别改成"Read committed",使用命令来修改"set global transaction_isolation='read-committed';",其他数据库默认已经是这个级别不用修改
## 如果使用MySQL,请在c3p0.xml文件中配置JDBC信息。
embed.db 1
## 是否自动清理历史消息记录,当为true时,IM服务会定时扫描t_messages_x和t_user_messages_x表,清除掉3年前的历史消息。
@@ -39,6 +40,9 @@ embed.db 1
## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。
db.auto_clean_history_messages true
## h2db数据库的路径,默认为程序目录下的,可以指定其他目录。如果使用MySQL,可以忽略此配置
h2db.path ./h2db/wfchat
##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。
http.admin.secret_key 123456
@@ -135,12 +139,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000
@@ -184,11 +182,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#*********************************************************************
# 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
+82 -82
View File
@@ -1,82 +1,82 @@
@ECHO OFF
set "CURRENT_DIR=%cd%"
set "WILDFIRECHAT_HOME=%CURRENT_DIR%"
if exist "%WILDFIRECHAT_HOME%\bin\wildfirechat.bat" goto okHome
cd ..
set "WILDFIRECHAT_HOME=%cd%"
set "CURRENT_DIR=%cd%"
:gotHome
if exist "%WILDFIRECHAT_HOME%\bin\wildfirechat.bat" goto okHome
echo The WILDFIRECHAT_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome
rem Set JavaHome if it exists
if exist { "%JAVA_HOME%\bin\java" } (
set "JAVA="%JAVA_HOME%\bin\java""
) else {
set "JAVA="java""
}
echo Using JAVA_HOME: "%JAVA_HOME%"
echo Using WILDFIRECHAT_HOME: "%WILDFIRECHAT_HOME%"
rem set LOG_CONSOLE_LEVEL=info
rem set LOG_FILE_LEVEL=fine
set JAVA_OPTS=
set JAVA_OPTS_SCRIPT=-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true
set WILDFIRECHAT_PATH=%WILDFIRECHAT_HOME%
set LOG_FILE=%WILDFIRECHAT_HOME%\config\log4j2.xml
set HZ_CONF_FILE=%WILDFIRECHAT_HOME%\config\hazelcast.xml
set C3P0_CONF_FILE=%WILDFIRECHAT_HOME%\config\c3p0-config.xml
rem Use the Hotspot garbage-first collector.
set JAVA_OPTS=%JAVA_OPTS% -XX:+UseG1GC
rem Have the JVM do less remembered set work during STW, instead
rem preferring concurrent GC. Reduces p99.9 latency.
set JAVA_OPTS=%JAVA_OPTS% -XX:G1RSetUpdatingPauseTimePercent=5
rem Main G1GC tunable: lowering the pause target will lower throughput and vise versa.
rem 200ms is the JVM default and lowest viable setting
rem 1000ms increases throughput. Keep it smaller than the timeouts.
set JAVA_OPTS=%JAVA_OPTS% -XX:MaxGCPauseMillis=500
rem Optional G1 Settings
rem Save CPU time on large (>= 16GB) heaps by delaying region scanning
rem until the heap is 70% full. The default in Hotspot 8u40 is 40%.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:InitiatingHeapOccupancyPercent=70
rem For systems with > 8 cores, the default ParallelGCThreads is 5/8 the number of logical cores.
rem Otherwise equal to the number of cores when 8 or less.
rem Machines with > 10 cores should try setting these to <= full cores.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:ParallelGCThreads=16
rem By default, ConcGCThreads is 1/4 of ParallelGCThreads.
rem Setting both to the same value can reduce STW durations.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:ConcGCThreads=16
rem rem GC logging options -- uncomment to enable
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDetails
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDateStamps
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintHeapAtGC
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintTenuringDistribution
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCApplicationStoppedTime
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintPromotionFailure
rem set JAVA_OPTS=%JAVA_OPTS% -XX:PrintFLSStatistics=1
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+UseGCLogFileRotation
rem set JAVA_OPTS=%JAVA_OPTS% -XX:NumberOfGCLogFiles=10
rem set JAVA_OPTS=%JAVA_OPTS% -XX:GCLogFileSize=10M"
echo
echo 请设置JVM参数Xmx和Xms!!!
echo
rem set JAVA_OPTS=%JAVA_OPTS% -Xmx2G
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
@ECHO OFF
set "CURRENT_DIR=%cd%"
set "WILDFIRECHAT_HOME=%CURRENT_DIR%"
if exist "%WILDFIRECHAT_HOME%\bin\wildfirechat.bat" goto okHome
cd ..
set "WILDFIRECHAT_HOME=%cd%"
set "CURRENT_DIR=%cd%"
:gotHome
if exist "%WILDFIRECHAT_HOME%\bin\wildfirechat.bat" goto okHome
echo The WILDFIRECHAT_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome
rem Set JavaHome if it exists
if exist { "%JAVA_HOME%\bin\java" } (
set "JAVA="%JAVA_HOME%\bin\java""
) else {
set "JAVA="java""
}
echo Using JAVA_HOME: "%JAVA_HOME%"
echo Using WILDFIRECHAT_HOME: "%WILDFIRECHAT_HOME%"
rem set LOG_CONSOLE_LEVEL=info
rem set LOG_FILE_LEVEL=fine
set JAVA_OPTS=
set JAVA_OPTS_SCRIPT=-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true
set WILDFIRECHAT_PATH=%WILDFIRECHAT_HOME%
set LOG_FILE=%WILDFIRECHAT_HOME%\config\log4j2.xml
set HZ_CONF_FILE=%WILDFIRECHAT_HOME%\config\hazelcast.xml
set C3P0_CONF_FILE=%WILDFIRECHAT_HOME%\config\c3p0-config.xml
rem Use the Hotspot garbage-first collector.
set JAVA_OPTS=%JAVA_OPTS% -XX:+UseG1GC
rem Have the JVM do less remembered set work during STW, instead
rem preferring concurrent GC. Reduces p99.9 latency.
set JAVA_OPTS=%JAVA_OPTS% -XX:G1RSetUpdatingPauseTimePercent=5
rem Main G1GC tunable: lowering the pause target will lower throughput and vise versa.
rem 200ms is the JVM default and lowest viable setting
rem 1000ms increases throughput. Keep it smaller than the timeouts.
set JAVA_OPTS=%JAVA_OPTS% -XX:MaxGCPauseMillis=500
rem Optional G1 Settings
rem Save CPU time on large (>= 16GB) heaps by delaying region scanning
rem until the heap is 70% full. The default in Hotspot 8u40 is 40%.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:InitiatingHeapOccupancyPercent=70
rem For systems with > 8 cores, the default ParallelGCThreads is 5/8 the number of logical cores.
rem Otherwise equal to the number of cores when 8 or less.
rem Machines with > 10 cores should try setting these to <= full cores.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:ParallelGCThreads=16
rem By default, ConcGCThreads is 1/4 of ParallelGCThreads.
rem Setting both to the same value can reduce STW durations.
rem set JAVA_OPTS=%JAVA_OPTS% -XX:ConcGCThreads=16
rem rem GC logging options -- uncomment to enable
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDetails
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDateStamps
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintHeapAtGC
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintTenuringDistribution
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCApplicationStoppedTime
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintPromotionFailure
rem set JAVA_OPTS=%JAVA_OPTS% -XX:PrintFLSStatistics=1
rem set JAVA_OPTS=%JAVA_OPTS% -XX:+UseGCLogFileRotation
rem set JAVA_OPTS=%JAVA_OPTS% -XX:NumberOfGCLogFiles=10
rem set JAVA_OPTS=%JAVA_OPTS% -XX:GCLogFileSize=10M"
echo
echo 请设置JVM参数Xmx和Xms!!!
echo
rem set JAVA_OPTS=%JAVA_OPTS% -Xmx2G
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=%HZ_CONF_FILE% -Dwildfirechat.path=%WILDFIRECHAT_PATH% -cp %WILDFIRECHAT_HOME%\lib\* cn.wildfirechat.server.Server
@@ -34,11 +34,13 @@ else
fi
export JAVA
LOG_FILE=$WILDFIRECHAT_HOME/config/log4j2.xml
HZ_CONF_FILE=$WILDFIRECHAT_HOME/config/hazelcast.xml
C3P0_CONF_FILE=$WILDFIRECHAT_HOME/config/c3p0-config.xml
WILDFIRECHAT_CONFIG_PATH=$WILDFIRECHAT_HOME
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_FILE_LEVEL=fine
JAVA_OPTS_SCRIPT="-XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true"
@@ -91,4 +93,4 @@ echo ""
#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/logs
VOLUME /opt/im-server/h2db
VOLUME /opt/im-server/media
EXPOSE 80/tcp 1883/tcp 8083/tcp 8084/tcp 18080/tcp
+13 -3
View File
@@ -8,12 +8,22 @@ mvn clean package
然后进入到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>
<properties>
<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>
<target.version>1.8</target.version>
</properties>
@@ -12,7 +12,7 @@
<artifactId>wildfirechat-parent</artifactId>
<packaging>pom</packaging>
<version>0.94</version>
<version>0.97</version>
<name>Wildfire Chat</name>
<description>Wildfire Chat Server</description>
<inceptionYear>2019</inceptionYear>
+3 -5
View File
@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<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">
<parent>
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.94</version>
<version>0.97</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -73,7 +71,7 @@
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
<version>1.13</version>
</dependency>
<dependency>
-1
View File
@@ -28,7 +28,6 @@
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
<scope>compile</scope>
</dependency>
<dependency>
@@ -31,6 +31,15 @@ public class ConferenceAdmin {
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 {
String path = APIPath.Conference_Destroy;
PojoConferenceRoomId conferenceRoomId = new PojoConferenceRoomId(roomId, advance);
@@ -1454,9 +1454,9 @@ public class Main {
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.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 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("article3", "https://media.wfcoss.cn/channel-assets/20220816/2dd76540daa9444dae44e942aa1c2bbc.png", "这是第三个测试文章", "测试一下文章的功能", "https://mp.weixin.qq.com/s/W6tanLbALd3qqZM8r3MTgA", false);
payload = articleContent.toPayload();
resultSendMessage = channelServiceApi.sendMessage(0, null, payload);
@@ -1642,22 +1642,30 @@ public class Main {
System.out.println("conference list " + listResult.getResult().conferenceInfoList);
}
IMResult<Void> createResult = ConferenceAdmin.createRoom("helloroomid", "hello room description", "123456", 9, false, 0, false, true);
if(createResult == null || createResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
IMResult<Void> voidIMResult = ConferenceAdmin.createRoom("helloroomid", "hello room description", "123456", 9, false, 0, false, true);
if(voidIMResult == null || voidIMResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
System.out.println("create conference failure");
System.exit(-1);
} else {
System.out.println("create conference");
}
createResult = ConferenceAdmin.createRoom("helloroomid2", "hello room description advanced", "123456", 20, true, 0, false, true);
if(createResult == null || createResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
voidIMResult = ConferenceAdmin.createRoom("helloroomid2", "hello room description advanced", "123456", 20, true, 0, false, true);
if(voidIMResult == null || voidIMResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
System.out.println("create conference failure");
System.exit(-1);
} else {
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();
if(listResult == null || listResult.getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS) {
System.out.println("get conference list failure");
@@ -33,6 +33,7 @@ public class MessageAdmin {
return AdminHttpUtils.httpJsonPost(path, messageData, Void.class);
}
//仅专业版支持
public static IMResult<Void> deleteMessage(long messageUid) throws Exception {
String path = APIPath.Msg_Delete;
DeleteMessageData deleteMessageData = new DeleteMessageData();
@@ -40,6 +41,14 @@ public class MessageAdmin {
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 {
String path = APIPath.Msg_Update;
UpdateMessageContentData updateMessageContentData = new UpdateMessageContentData();
@@ -51,6 +60,7 @@ public class MessageAdmin {
return AdminHttpUtils.httpJsonPost(path, updateMessageContentData, Void.class);
}
//仅专业版支持
public static IMResult<Void> clearConversation(String userId, Conversation conversation) throws Exception {
String path = APIPath.Conversation_Delete;
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