Compare commits

...
94 Commits
Author SHA1 Message Date
heavyrian2012 1486b19ce1 更新版本和release note 2022-11-19 09:44:09 +08:00
heavyrian2012 3e1095f745 移动release note文件的位置 2022-11-19 09:38:26 +08:00
heavyrian2012 17039bd3c1 http请求错误时不打印数据内容,防止内容泄露 2022-11-19 09:31:22 +08:00
heavyrian2012 b5d8feab62 客户端连接时检查是否跟获取token的平台一致 2022-11-18 22:10:53 +08:00
heavyrian2012 95b08ff7a3 Revert "mqtt proto version error"
This reverts commit ab8ae9d0c9.
2022-11-18 21:55:49 +08:00
heavyrian2012 ab8ae9d0c9 mqtt proto version error 2022-11-18 21:27:18 +08:00
heavyrian2012 b4d163e144 添加android客户端签名验证功能 2022-11-18 16:01:17 +08:00
heavyrian2012 3ccfd9a29b 解决user setting并发问题,fix #566 2022-11-15 17:12:51 +08:00
heavyrian2012 90504d1305 打包dockerfile 2022-11-14 15:47:42 +08:00
heavyrian2012 88199189a2 当禁止陌生人聊天时,添加例外允许用户id,用于比如管理员、文件助手等场景 2022-11-14 15:23:03 +08:00
heavyrian2012 0b6612eacc 内置对象存储支持绝对路径和相对路径 2022-11-14 15:13:06 +08:00
heavyrian2012 d706f545d2 解决启动脚本中的小错误 2022-11-14 15:04:51 +08:00
heavyrian2012 ae8d63b162 update docker readme 2022-11-14 14:42:04 +08:00
heavyrian2012 77e742e812 添加server sdk检查server api的工具 2022-11-12 19:25:24 +08:00
heavyrian2012 b2ee8b8d94 linux安装包创建日志目录 2022-11-12 19:24:57 +08:00
heavyrian2012 7080d85605 只有server api才能创建组织群 2022-11-12 15:01:57 +08:00
heavyrian2012 1275b2109f fix build error 2022-11-12 14:18:26 +08:00
heavyrian2012 b2eab28cb9 组织群客户端不能自主退出 2022-11-12 14:17:15 +08:00
heavyrian2012 67234ceaa6 添加新的群组类型:组织群 2022-11-12 14:00:24 +08:00
heavyrian2012 45f9e20e97 支持转发消息include/exclude类型,支持配置消息区间 2022-11-12 12:51:25 +08:00
heavyrian2012 6b7edc713a API获取用户信息时,过滤掉被删除的用户 2022-11-09 10:36:52 +08:00
heavyrian2012 1ef8f94d67 忽略掉无效的请求和日志 2022-11-05 17:05:31 +08:00
heavyrian2012 9b0b644cd1 update version and release note 2022-11-01 08:47:34 +08:00
heavyrian2012 b233c49d73 解决字符编码错误问题 2022-11-01 08:40:07 +08:00
heavyrian2012 b8d8d2f8ed 群组成员表添加成员索引和获取2个人共同群组的接口 2022-10-28 21:56:20 +08:00
heavyrian2012 731020ba0f update readme 2022-10-27 08:40:47 +08:00
heavyrian2012 4c00955639 更新readme 2022-10-27 08:33:09 +08:00
heavyrian2012 cbc150905f update screenshots 2022-10-26 10:32:48 +08:00
heavyrian2012 a71b8cef73 add more screenshots 2022-10-26 10:27:03 +08:00
heavyrian2012 66c67fa8d1 pc 截图大小 2022-10-26 09:29:36 +08:00
heavyrian2012 3ca7c70faa add screenshots 2022-10-26 09:26:43 +08:00
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
heavyrian2012 f672f4550e update version and release note 2022-08-31 20:30:03 +08:00
heavyrian2012 04c89d1fc1 gson单例化,另外关掉gson html转义功能 2022-08-31 20:24:46 +08:00
heavyrian2012 97540bdd1b IM exception add node_id 2022-08-26 23:13:04 +08:00
heavyrain2012 8514d808b3 remove useless file 2022-08-26 19:31:57 +08:00
heavyrain2012 e8e3d91dbc update 2022-08-26 19:18:16 +08:00
heavyrain2012 b64316c67d 添加超频限制白名单,某些命令不受超频限制 2022-08-26 19:11:52 +08:00
heavyrian2012 650ffa14e2 添加检查是否订阅频道server api 2022-08-26 15:16:48 +08:00
heavyrian2012 9fa33159c8 解决内置存储某些类型文件返回content-type错误问题 2022-08-26 00:22:18 +08:00
heavyrian2012 35794a777b sdk添加删除会话接口 2022-08-26 00:19:57 +08:00
heavyrian2012 bf60ed2d7d Merge branch 'wildfirechat' of github.com:wildfirechat/im-server into wildfirechat 2022-08-25 21:02:55 +08:00
heavyrian2012 0d09aad24f update readme 2022-08-25 21:02:40 +08:00
heavyrain2012 8d31e031bc update version and release 2022-08-25 14:22:58 +08:00
heavyrian2012 9af2bbf3d0 添加敏感词命中回调 2022-08-23 14:17:31 +08:00
heavyrian2012 d391ba386c http client 添加部分参数,解决调用server sdk时可能出现的connection reset问题 2022-08-23 14:11:34 +08:00
heavyrian2012 b2ef3152db 短链接添加head处理 2022-08-22 15:28:24 +08:00
heavyrian2012 05fe5bd766 server api添加订阅接口 2022-08-20 13:43:12 +08:00
heavyrian2012 48c9b10365 添加release note 2022-08-19 08:50:45 +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
170 changed files with 4887 additions and 1019 deletions
+7 -1
View File
@@ -11,7 +11,13 @@
你认为正确的表现应该是什么样的。 你认为正确的表现应该是什么样的。
#### 补充条件 #### 补充条件
是否是毕现的,还是偶现的?是否只有在特殊的网络/设备/平台上出现,还是所有的都出现。还有您用的版本是什么时候的,是不是最新版 是否是必现的,还是偶现的?是否只有在特殊的网络/设备/平台上出现,还是所有的都出现。还有您用的版本是什么时候的,是不是最新版
#### demo对比结果 #### demo对比结果
请用demo对比测试,demo上是什么状态。 请用demo对比测试,demo上是什么状态。
#### 检索已有问题
除了可以检索已有issue外,还可以去野火论坛查找已有问题,论坛地址为 https://bbs.wildfirechat.cn
#### star 我们
给我们点个star,可以及时接收到我们回复信息,另外也是对我们支持人员辛苦工作的认可,激励我们更好更快地为大家服务。
+156 -57
View File
@@ -4,10 +4,11 @@
## 功能特性 ## 功能特性
* 极致地硬件利用率,IM服务最低128M内存即可运行,上不封顶。 * 极致地硬件利用率,IM服务最低128M内存即可运行,上不封顶。
* 协议先进,采用MQTT+Protobuf组合,流量和性能极致优化。 * 协议先进,采用MQTT+Protobuf组合,流量和性能极致优化。
* 性能强大,专业版IM服务支持百万在线和集群部署。 * 性能强大,专业版IM服务支持百万在线和集群部署,业内顶尖性能水平,详情参考野火性能报告。
* 部署运维简单,依赖服务少,稍加配置一键启动。 * 部署运维简单,依赖服务少,稍加配置一键启动。
* 安全加密。网络连接AES加密。客户端数据库SqlCipher加密。安全无小事。 * 安全加密。网络连接AES加密。客户端数据库SqlCipher加密。安全无小事。
* 全平台客户端,四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。 * 全平台客户端,支持Android、iOS、Web、小程序、UniApp、Flutter、Windows、Mac OS、Linux等所有常见平台。
* 四端同时在线(移动端,pc端,web端和小程序端),数据和状态多端完美同步。
* 支持国产化。支持国产化操作系统、国产化芯片和国产操作系统。支持国密加密。 * 支持国产化。支持国产化操作系统、国产化芯片和国产操作系统。支持国密加密。
* 客户端使用微信[mars](https://github.com/tencent/mars)连接库,野火IM可能是最适应中国网络国情的即时通讯服务。 * 客户端使用微信[mars](https://github.com/tencent/mars)连接库,野火IM可能是最适应中国网络国情的即时通讯服务。
* 支持加速点加速,即可用于全球应用,也可用于政企内外双网复杂的网络环境。 * 支持加速点加速,即可用于全球应用,也可用于政企内外双网复杂的网络环境。
@@ -15,37 +16,45 @@
* 音视频多种解决方案,丰俭由人,可自由选择。 * 音视频多种解决方案,丰俭由人,可自由选择。
* 高级音视频功能强大,支持9人以上的群组视频通话,支持1080P视频,支持会议模式,支持百人以上会议,支持服务器端录制。 * 高级音视频功能强大,支持9人以上的群组视频通话,支持1080P视频,支持会议模式,支持百人以上会议,支持服务器端录制。
* 全私有部署,可不依赖任何第三方服务,完全内网部署。 * 全私有部署,可不依赖任何第三方服务,完全内网部署。
* 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。Demo成熟完善,基本可以做到开箱即用,也可把SDK嵌入其它应用。 * 功能齐全,涵盖所有常见即时通讯功能。另外具有强大的可扩展能力。代码开源率高,二次开发简单。
* 结构设计合理,客户端分功能库、UI库和应用三层。应用成熟完善,开箱即用,也可把SDK嵌入其它应用。
* 拥有应用开放平台,可以开发和创建自建应用,扩展您的工作台。 * 拥有应用开放平台,可以开发和创建自建应用,扩展您的工作台。
* API丰富,方便与其它服务系统的对接。 * API丰富,方便与其它服务系统的对接。
* 拥有机器人和频道功能。 * 拥有机器人和公众号功能。
* 社区版IM服务和移动端免费商用,可以免费构建出完备的移动互联网应用。
* 收费项目非常便宜,野火独有的试用模式,可以先部署到客户环境试用6个月,试用满意后再购买,避免上当受骗。
* 技术支持专业和高效,核心研发人员及时处理issue和论坛回复。
## 野火开源项目 ## 野火开源项目
主要包括以下项目: 主要包括以下项目:
| [GitHub仓库地址](https://github.com/wildfirechat) | [码云仓库地址](https://gitee.com/wfchat) | 说明 | [GitHub仓库](https://github.com/wildfirechat) | [码云仓库](https://gitee.com/wfchat) | 说明
| ------------------------------------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------- | --------------------------------------------------------------------------
| [im-server](https://github.com/wildfirechat/im-server) | [im-server](https://gitee.com/wfchat/im-server) | 野火社区版IM服务,野火IM的核心服务,处理所有IM相关业务。 | | [im-server](https://github.com/wildfirechat/im-server) | [im-server](https://gitee.com/wfchat/im-server) | 野火社区版IM服务,野火IM的核心服务,处理所有IM相关业务。 |
| [app_server](https://github.com/wildfirechat/app_server) | [app_server](https://gitee.com/wfchat/app_server) | Demo应用服务,模拟客户的应用服登陆处理逻辑及部分二次开发示例。 | | [app-server](https://github.com/wildfirechat/app-server) | [app-server](https://gitee.com/wfchat/app-server) | Demo应用服务,模拟客户的应用服登陆处理逻辑及部分二次开发示例。 |
| [robot_server](https://github.com/wildfirechat/robot_server) | [robot_server](https://gitee.com/wfchat/robot_server) | 机器人服务,演示野火机器人对接其它系统的方法。 | | [robot_server](https://github.com/wildfirechat/robot_server) | [robot_server](https://gitee.com/wfchat/robot_server) | 机器人服务,演示野火机器人对接其它系统的方法。 |
| [push_server](https://github.com/wildfirechat/push_server) | [push_server](https://gitee.com/wfchat/push_server) | 推送服务器,可以对接所有的系统厂商推送服务或者第三方推送服务。 | | [push_server](https://github.com/wildfirechat/push_server) | [push_server](https://gitee.com/wfchat/push_server) | 推送服务器,可以对接所有的系统厂商推送服务或者第三方推送服务。 |
| [wf-minio](https://github.com/wildfirechat/WF-minio) | [wf-minio](https://gitee.com/wfchat/WF-minio) | 私有对象存储服务,用来支持野火IM专业版的文件存储。 | | [wf-minio](https://github.com/wildfirechat/WF-minio) | [wf-minio](https://gitee.com/wfchat/WF-minio) | 私有对象存储服务,用来支持野火IM专业版的文件存储。 |
| [wf-janus](https://github.com/wildfirechat/wf-janus ) | [wf-janus](https://gitee.com/wfchat/wf-janus ) | 高级音视频媒体服务。 | | [wf-janus](https://github.com/wildfirechat/wf-janus ) | [wf-janus](https://gitee.com/wfchat/wf-janus ) | 高级音视频媒体服务。 |
| [open-platform](https://github.com/wildfirechat/open-platform) | [open-platform](https://gitee.com/wfchat/open-platform) | 野火开放平台服务。 | | [open-platform](https://github.com/wildfirechat/open-platform) | [open-platform](https://gitee.com/wfchat/open-platform) | 野火开放平台服务。 |
| [daily-report](https://github.com/wildfirechat/daily-report) | [daily-report](https://github.com/wildfirechat/daily-report) | 野火开放平台日报 demo服务。 | | [daily-report](https://github.com/wildfirechat/daily-report) | [daily-report](https://github.com/wildfirechat/daily-report) | 野火开放平台日报 demo服务。 |
| [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码。 | [channel-platform](https://github.com/wildfirechat/channel-platform) | [channel-platform](https://gitee.com/wfchat/channel-platform) | 野火频道(公众号)平台服务。 |
| [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS SDK源码和App源码。 | [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码。 |
| [pc-chat](https://github.com/wildfirechat/vue-pc-chat) | [pc-chat](https://gitee.com/wfchat/vue-pc-chat) | 基于[Electron](https://electronjs.org/)的PC 端,支持Windows、Mac、Linux(包括国产化linux系统和CPU)。 | | [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS SDK源码和App源码。|
| [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](https://web.wildfirechat.cn) | | [pc-chat](https://github.com/wildfirechat/vue-pc-chat) | [pc-chat](https://gitee.com/wfchat/vue-pc-chat) | 基于[Electron](https://electronjs.org/)的PC 端,支持Windows、Mac、Linux(包括国产化linux系统和CPU)。 |
| [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台) | | [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](https://web.wildfirechat.cn)。 |
| [uni-chat](https://github.com/wildfirechat/uni-chat) | [uni-chat](https://gitee.com/wfchat/uni-chat) | UniApp平台移动端应用(使用原生插件) | | [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台)。 |
| [uni-wfc-client](https://github.com/wildfirechat/uni-wfc-client) | [uni-wfc-client](https://gitee.com/wfchat/uni-wfc-client) | UniApp平台原生插件 | | [uni-chat](https://github.com/wildfirechat/uni-chat) | [uni-chat](https://gitee.com/wfchat/uni-chat) | UniApp平台移动端应用(使用原生插件)。 |
| [flutter_imclient](https://github.com/wildfirechat/flutter_imclient) | [flutter_imclient](https://gitee.com/wfchat/flutter_imclient) | Flutter平台原生插件 | | [uni-wfc-client](https://github.com/wildfirechat/uni-wfc-client) | [uni-wfc-client](https://gitee.com/wfchat/uni-wfc-client) | UniApp平台原生插件。已[商店](https://ext.dcloud.net.cn/plugin?id=7895)上架 |
| [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.wildfirechat.cn/) | | [flutter_imclient](https://github.com/wildfirechat/flutter_imclient) | [flutter_imclient](https://gitee.com/wfchat/flutter_imclient) | Flutter平台原生插件。 |
| [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.wildfirechat.cn/)。 |
## 野火开发文档 ## 野火开发文档
[在线文档](https://docs.wildfirechat.cn/) [在线文档](https://docs.wildfirechat.cn/)
## 野火性能测试报告及测试方法
[Github](https://github.com/wildfirechat/Performance_Test), [码云](https://gitee.com/wfchat/Performance_Test)。
## 野火Demo ## 野火Demo
请使用微信扫码下载安装体验野火IM移动客户端 请使用微信扫码下载安装体验野火IM移动客户端
@@ -59,72 +68,162 @@ PC客户端点[这里](https://github.com/wildfirechat/vue-pc-chat/releases)下
![野火IM](http://static.wildfirechat.net/wx.jpg) ![野火IM](http://static.wildfirechat.net/wx.jpg)
## 快速使用
可以按照[快速开始](https://docs.wildfirechat.cn/quick_start/)来部署应用服务和IM服务,然后打包移动端,就可以完成聊天和通话功能。
## 应用截图 之后再部署开放平台服务、频道(公众号)服务、机器人服务、推送服务、Turn服务(音视频服务)来实现完整的应用功能,详情可以详细阅读文档。
![ios-demo](http://static.wildfirechat.cn/ios-demo.gif)
<img src="http://static.wildfirechat.cn/ios-message-view.png" width = 50% height = 50% /> ## 联系我们
商务合作请微信联系:
<img src="http://static.wildfirechat.cn/ios-contact-view.png" width = 50% height = 50% /> * 微信1:wildfirechat
* 微信2:wfchat
<img src="http://static.wildfirechat.cn/ios-discover-view.png" width = 50% height = 50% /> ## 问题交流
<img src="http://static.wildfirechat.cn/ios-settings-view.png" width = 50% height = 50% /> 1. 如果大家发现bug,请在GitHub或码云提issue;如果有需求也请给我们提issue。
2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习
3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。
<img src="http://static.wildfirechat.cn/ios-messagelist-view.png" width = 50% height = 50% /> <img src="http://static.wildfirechat.cn/wx_wfc_qrcode.jpg" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/ios-chat-setting-view.png" width = 50% height = 50% /> 我们有核心研发工程师轮流值班处理issue和论坛,会及时处理的,疑难Bug的修改和新需求的开发我们也会尽快解决。
<img src="http://static.wildfirechat.cn/ios-takephoto-view.png" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/ios-record-voice-view.png" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/ios-location-view.png" width = 50% height = 50% />
<img src="http://static.wildfirechat.cn/ios-voip-view.png" width = 50% height = 50% />
## 编译 ## 编译
在安装JDK1.8以上及maven的前提下,在命令行中执行```mvn clean package```,生成的目标文件在```./distribution/target/distribution-xxxx-bundle-tar.tar.gz``` ```
> 由于使用了一个git的maven插件,如果本地没有git信息就会编译出错,请使用```git clone```的方法下载代码,或者下载压缩包解压后在根目录创建```.git```的空目录。建议用```git clone```的方式下载代码。 mvn clean package
```
生成的目标文件在```./distribution/target/distribution-xxxx-bundle-tar.tar.gz```
## 配置 ## 配置
解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```。修改如下部分: 解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```,可以阅读配置文件注释和文档,对其它配置项进行调整。
1. 修改```server.ip```为您的服务器的公网IP,或者域名。如果用域名需要做好域名解析。
2. 可以阅读配置文件注释和文档,对其它配置项进行调整。
## 运行 ## 运行
在程序目录运行如下命令: 在解压```distribution-xxxx-bundle-tar.tar.gz```后的目录下运行如下命令:
``` ```
./bin/wildfirechat.sh ./bin/wildfirechat.sh
``` ```
## 验证 ## 验证
1. 在浏览器中输入地址 ```http://${ip}/api/version```可以看到返回一个json文件。 1. 在浏览器中输入地址 ```http://${ip}/api/version```可以看到返回一个json文件。
2. 部署[应用服务](应用服务),配置和编译[Android客户端](https://github.com/wildfirechat/android-chat)和[iOS客户端](https://github.com/wildfirechat/ios-chat)进行验证。详情可参考[快速开始](https://docs.wildfirechat.cn/quick_start/)。 2. 部署[应用服务](https://github.com/wildfirechat/app-server),配置和编译[Android客户端](https://github.com/wildfirechat/android-chat)和[iOS客户端](https://github.com/wildfirechat/ios-chat)进行验证。详情可参考[快速开始](https://docs.wildfirechat.cn/quick_start/)。
## 打包RPM格式
打包会生成Java包和deb安装包,如果需要rpm安装包,请在```distribution/pom.xml```中取消注释生成rpm包的plugin。另外还需要本地安装有rpm,在linux或者mac系统中很容易安装,在windows系统需要安装cygwin并安装rpm,具体信息请百度查询。
修改之后运行编译命令```mvn clean package```,rpm包生成在```distribution/target```目录下。
## 升级说明 ## 升级说明
1. 从0.42 版本增加了群成员数限制,默认为2000。如果您从之前的版本升级到这个版本或以后,需要注意到群成员数的限制。升级之后超出限制的群不受影响,但不能继续加人,如果您想修改默认值,可以在升级版本之后,修改t_setting表,把默认的大小改为您期望的人数。另外修改t_group表,把已经存在的群组max_member_count改成您期望的,然后重启。 1. 从0.42 版本增加了群成员数限制,默认为2000。如果您从之前的版本升级到这个版本或以后,需要注意到群成员数的限制。升级之后超出限制的群不受影响,但不能继续加人,如果您想修改默认值,可以在升级版本之后,修改t_setting表,把默认的大小改为您期望的人数。另外修改t_group表,把已经存在的群组max_member_count改成您期望的,然后重启。
2. 0.46和0.47版本升级到0.48及以后版本时,可能会提示flyway migrate 38错误,请执行 [修复脚本](https://github.com/wildfirechat/server/blob/wildfirechat/flyway_repaire_migrate_38.sql) 进行修复。0.46和0.47版本之外的版本不会出现此问题。 2. 0.50版本添加了是否允许客户端发送群操作通知的配置。如果您在客户端自定义群通知,需要在服务器端配置允许,没有使用自定义群操作通知的不受影响。***
3. 从0.54之前版本升级到0.54及以后版本时,会提示flyway migrate错误。因为0.54版本删除了sql脚本中默认敏感词的内容,flyway checksum失败。请执行```update flyway_schema_history set checksum = 0 where script = 'V17__add_default_sensitive_word.sql';```来修复。 3. 0.46和0.47版本升级到0.48及以后版本时,可能会提示flyway migrate 38错误,请执行 [修复脚本](https://github.com/wildfirechat/server/blob/wildfirechat/flyway_repaire_migrate_38.sql) 进行修复。0.46和0.47版本之外的版本不会出现此问题。
4. 从0.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。 4. 从0.54之前版本升级到0.54及以后版本时,会提示flyway migrate错误。因为0.54版本删除了sql脚本中默认敏感词的内容,flyway checksum失败。请执行```update flyway_schema_history set checksum = 0 where script = 'V17__add_default_sensitive_word.sql';```来修复。
5. 从0.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。
6. 0.62/0.63 版本有严重的问题,请使用0.64及以后版本,或者0.61版。
7. 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端)
8. 从0.78 版本起把MySQL数据库中关键字都改为大小写敏感,另外生成id的方法也做了改变,只生成小写的id,避免出现id重复的问题,建议所有客户都升级
9. 从0.79 版本起把log4j升级到log4j2,因为log4j已经不再维护而且还有已知的漏洞,建议所有客户都升级,升级时注意更新log4j2的配置文件
10. 0.97版本更改了启动脚本```wildfirechat.sh```,如果是升级服务,请注意更新启动脚本。
## 应用截图
登录界面
## 联系我们 <img src="./screenshots/1_login.png" width = 50% height = 50% />
商务合作请使用如下邮箱和微信联系: 会话列表
邮箱: support@wildfirechat.cn 微信1:wfchat 微信2:wildfirechat <img src="./screenshots/2_conversation_list.png" width = 50% height = 50% />
## 问题交流 好友列表
1. 如果大家发现bug,请在GitHub提issue;如果有需求也请给我们提issue。 <img src="./screenshots/3_contacts_list.png" width = 50% height = 50% />
2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习
3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。
<img src="http://static.wildfirechat.cn/wx_wfc_qrcode.jpg" width = 50% height = 50% /> 工作台
> 我们有研发工程师轮流值班处理issue和论坛,一般简单问题几个小时就会回复一遍,疑难Bug的修改和新需求的开发我们也会尽快解决。 <img src="./screenshots/4_workplatform.png" width = 50% height = 50% />
发现
<img src="./screenshots/5_discover.png" width = 50% height = 50% />
我的页面
<img src="./screenshots/6_settings.png" width = 50% height = 50% />
消息聊天
<img src="./screenshots/7_conversation.png" width = 50% height = 50% />
动态表情
<img src="./screenshots/8_conversation_sticker.png" width = 50% height = 50% />
语音消息
<img src="./screenshots/9_conversation_voice_msg.png" width = 50% height = 50% />
位置消息
<img src="./screenshots/10_conversation_location.png" width = 50% height = 50% />
拍摄图片或者小视频
<img src="./screenshots/11_conversation_capture.jpeg" width = 50% height = 50% />
会话设置
<img src="./screenshots/13_conversation_settings.png" width = 50% height = 50% />
朋友圈
<img src="./screenshots/14_moments.png" width = 50% height = 50% />
新好友
<img src="./screenshots/15_new_friends.png" width = 50% height = 50% />
多人视频通话
<img src="./screenshots/16_videocall.png" width = 50% height = 50% />
会议主页
<img src="./screenshots/17_conference_home.png" width = 50% height = 50% />
会议详情
<img src="./screenshots/18_conference_info.png" width = 50% height = 50% />
会议界面
<img src="./screenshots/19_conference.png" width = 50% height = 50% />
PC客户端界面
<img src="./screenshots/20_pc.png"/>
移动客户端、PC客户端和Web客户端三端同框
<img src="./screenshots/21_three_platform.png" />
小程序客户端
<img src="./screenshots/22_micro_app.jpeg" width = 50% height = 50% />
公众号管理后台
<img src="./screenshots/23_channel.png"/>
开放平台管理后台
<img src="./screenshots/24_open_platform.png" />
IM服务管理后台
<img src="./screenshots/25_im_admin.png" />
> 更多详细信息请下载demo进行体验。
## 特别感谢 ## 特别感谢
1. [moquette](https://github.com/moquette-io/moquette) 本项目是基于此项目二次开发而来,处理MQTT相关业务。 1. [moquette](https://github.com/moquette-io/moquette) 本项目是基于此项目二次开发而来,处理MQTT相关业务。
-3
View File
@@ -1,3 +0,0 @@
#native2ascii -encoding UTF-8 messages.properties temp_messages.properties
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
@@ -1,2 +0,0 @@
Above_Greeting_Message=This's the greeting
Friend_Can_Start_Chat=You're already friend, start chat now.
@@ -1,2 +0,0 @@
Above_Greeting_Message=This's the greeting
Friend_Can_Start_Chat=You're already friend, start chat now.
@@ -1,2 +0,0 @@
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
@@ -1,2 +0,0 @@
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
+22 -14
View File
@@ -42,6 +42,9 @@ embed.db 1
## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。 ## 使用mongodb时次开关无效(因为mongodb设置了消息过期时间,会自动清理,仅专业版支持mongodb)。
db.auto_clean_history_messages true db.auto_clean_history_messages true
## h2db数据库的路径,默认为程序目录下的,可以指定其他目录。如果使用MySQL,可以忽略此配置
h2db.path ./h2db/wfchat
##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。 ##服务器管理接口密钥。如果修改此端口需要同步修改使用管理API的地方,比如应用服务。
http.admin.secret_key 123456 http.admin.secret_key 123456
@@ -56,6 +59,12 @@ token.key testim
##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题 ##token的过期时间,单位为毫秒,默认为无限期。如果需要设置无限期,客户端上一定需要加上token过期的处理。token过期的处理请参考文档的常见问题
##token.expire_time 2592000000 ##token.expire_time 2592000000
##客户端(目前只有android)的应用签名(一个应用最取第一个签名),如果有多个签名(多个客户端),用英文逗号分开。获取签名方法请参考文档:https://docs.wildfirechat.cn/blogs/签名验证.html
#connect.client_signature_list eNkezQ1g9OsnhfLSFUY1vzKDzhs=,xykezQ1g9OsnhfLSFUY1vzKDzhs=
##是否拒绝空签名的android客户端, 默认为true。当有旧的客户端需要兼容时改成false,当所有都是新客户端时,改成true。
#connect.reject_empty_signature true
##首次登录,是否接收之前的历史消息。 ##首次登录,是否接收之前的历史消息。
##0 不接收历史消息,只接收${message.compensate_time_limit}毫秒以内的消息,由于服务器没有保存已经收取记录,所以如果有超过${message.compensate_time_limit}毫秒之前未收取的消息也不会收取下拉; ##0 不接收历史消息,只接收${message.compensate_time_limit}毫秒以内的消息,由于服务器没有保存已经收取记录,所以如果有超过${message.compensate_time_limit}毫秒之前未收取的消息也不会收取下拉;
##1接收,会接收 message.max_queue 配置的条数的历史消息 ##1接收,会接收 message.max_queue 配置的条数的历史消息
@@ -76,6 +85,9 @@ message.chatroom_remote_history_message 1
##是否禁止陌生人聊天 ##是否禁止陌生人聊天
message.disable_stranger_chat false message.disable_stranger_chat false
##当禁止陌生人聊天时,允许聊天的用户id,比如管理员或者文件传输助手等。用户id以英文逗号分割。
message.allow_stranger_chat_list admin,FireRobot,wfc_file_transfer
##黑名单策略,0 发送失败,返回被拉黑的错误码;1 发送成功但消息被服务器直接丢弃 ##黑名单策略,0 发送失败,返回被拉黑的错误码;1 发送成功但消息被服务器直接丢弃
message.blacklist.strategy 0 message.blacklist.strategy 0
@@ -139,12 +151,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。 ##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000 friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出 ## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000 chatroom.participant_idle_time 900000
@@ -190,11 +196,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。 ##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100 client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#********************************************************************* #*********************************************************************
# Media server configuration # Media server configuration
#********************************************************************* #*********************************************************************
@@ -301,10 +302,17 @@ push.ios.server.address http://localhost:8085/ios/push
##消息转发地址 ##消息转发地址
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!! ##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.forward.url http://localhost:8888/im_event/message #message.forward.url http://localhost:8888/im_event/message
##需要转发的消息类型,当有多个时以英文逗号分割。如果转发所有消息,请注释掉配置或者设置为空 ##需要转发的消息类型,当有多个时以英文逗号分割,可以用-来指定区间,注意区间不能太大,因为要把区间内的所有数字都放入到Set中。如果转发所有消息,请注释掉配置或者设置为空
#message.forward.types 1,2,3 #message.forward.types 1,2,3,5-10
##需要转发的消息类型,当有多个时以英文逗号分割,可以用-来指定区间,注意区间不能太大,因为要把区间内的所有数字都放入到Set中。
#message.forward.exclude_types 1,2,3,5-10
##提醒消息转发地址,@全体用户和@某个用户消息会回调此地址。此转发不受${message.forward.types}限制。 ##敏感消息转发地址
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.sensitive.forward.url http://localhost:8888/im_event/message
##提醒消息转发地址,@全体用户和@某个用户消息会回调此地址。
##此转发不受${message.forward.types}和${message.forward.exclude_types}限制。
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!! ##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.mentionmsg.forward.url http://localhost:8888/im_event/message #message.mentionmsg.forward.url http://localhost:8888/im_event/message
+1
View File
@@ -0,0 +1 @@
alter table `t_group_member` add INDEX `group_member_mid_index` (`_mid`);
+1
View File
@@ -0,0 +1 @@
alter table `t_group_member` add INDEX `group_member_mid_index` (`_mid`);
+9 -4
View File
@@ -1,11 +1,11 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" <?xml version="1.0" encoding="UTF-8"?>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<relativePath>../</relativePath> <relativePath>../</relativePath>
<artifactId>wildfirechat-parent</artifactId> <artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId> <groupId>cn.wildfirechat</groupId>
<version>0.92</version> <version>0.99</version>
</parent> </parent>
<artifactId>moquette-broker</artifactId> <artifactId>moquette-broker</artifactId>
@@ -120,7 +120,7 @@
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>1.10</version> <version>1.13</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -234,6 +234,11 @@
<version>3.4.1</version> <version>3.4.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>2.4.1</version>
</dependency>
</dependencies> </dependencies>
@@ -11,6 +11,7 @@ package cn.wildfirechat.push;
import cn.wildfirechat.common.IMExceptionEvent; import cn.wildfirechat.common.IMExceptionEvent;
import cn.wildfirechat.proto.ProtoConstants; import cn.wildfirechat.proto.ProtoConstants;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.moquette.persistence.MemorySessionStore; import io.moquette.persistence.MemorySessionStore;
import io.moquette.server.config.IConfig; import io.moquette.server.config.IConfig;
import io.moquette.spi.ISessionsStore; import io.moquette.spi.ISessionsStore;
@@ -29,7 +30,7 @@ import static win.liyufan.im.HttpUtils.HttpPostType.POST_TYPE_Push;
public class PushServer { public class PushServer {
private static final Logger LOG = LoggerFactory.getLogger(PushServer.class); private static final Logger LOG = LoggerFactory.getLogger(PushServer.class);
protected static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
public interface PushMessageType { public interface PushMessageType {
int PUSH_MESSAGE_TYPE_NORMAL = 0; int PUSH_MESSAGE_TYPE_NORMAL = 0;
int PUSH_MESSAGE_TYPE_VOIP_INVITE = 1; int PUSH_MESSAGE_TYPE_VOIP_INVITE = 1;
@@ -100,7 +101,7 @@ public class PushServer {
url = iOSPushServerUrl; url = iOSPushServerUrl;
pushMessage.voipDeviceToken = session.getVoipDeviceToken(); pushMessage.voipDeviceToken = session.getVoipDeviceToken();
} }
HttpUtils.httpJsonPost(url, new Gson().toJson(pushMessage, pushMessage.getClass()), POST_TYPE_Push); HttpUtils.httpJsonPost(url, gson.toJson(pushMessage, pushMessage.getClass()), POST_TYPE_Push);
} else { } else {
LOG.info("Not mobile platform {}", session.getPlatform()); LOG.info("Not mobile platform {}", session.getPlatform());
} }
@@ -9,6 +9,7 @@
package com.xiaoleilu.loServer.action; package com.xiaoleilu.loServer.action;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.xiaoleilu.loServer.RestResult; import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.RequireAuthentication; import com.xiaoleilu.loServer.annotation.RequireAuthentication;
import com.xiaoleilu.loServer.handler.Request; import com.xiaoleilu.loServer.handler.Request;
@@ -39,7 +40,7 @@ import static io.moquette.BrokerConstants.*;
abstract public class Action { abstract public class Action {
public static IMessagesStore messagesStore = null; public static IMessagesStore messagesStore = null;
public static ISessionsStore sessionsStore = null; public static ISessionsStore sessionsStore = null;
protected static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
public ChannelHandlerContext ctx; public ChannelHandlerContext ctx;
protected static RateLimiter adminLimiter = null; protected static RateLimiter adminLimiter = null;
@@ -129,7 +130,7 @@ abstract public class Action {
} }
RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), RestResult.resultOf(errorCode)); RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), RestResult.resultOf(errorCode));
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
response.send(); response.send();
} }
@@ -145,7 +146,7 @@ abstract public class Action {
FullHttpRequest fullHttpRequest = (FullHttpRequest) request; FullHttpRequest fullHttpRequest = (FullHttpRequest) request;
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content()); byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes, StandardCharsets.UTF_8);//contribute by JiaRG from github String content = new String(bytes, StandardCharsets.UTF_8);//contribute by JiaRG from github
Gson gson = new Gson();
T t = gson.fromJson(content, cls); T t = gson.fromJson(content, cls);
return t; return t;
} }
@@ -154,6 +155,6 @@ abstract public class Action {
protected void setResponseContent(RestResult result, Response response) { protected void setResponseContent(RestResult result, Response response) {
response.setStatus(HttpResponseStatus.OK); response.setStatus(HttpResponseStatus.OK);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
} }
} }
@@ -240,7 +240,12 @@ public class UploadFileAction extends Action {
datePath = "fs/" + bucket[0] + "/" + datePath; //add bucket datePath = "fs/" + bucket[0] + "/" + datePath; //add bucket
String dir = "./" + MediaServerConfig.FILE_STROAGE_ROOT + "/" + datePath; String dir;
if(MediaServerConfig.FILE_STROAGE_ROOT.startsWith("/") || MediaServerConfig.FILE_STROAGE_ROOT.startsWith("~/") || MediaServerConfig.FILE_STROAGE_ROOT.startsWith("./")) {
dir = MediaServerConfig.FILE_STROAGE_ROOT + "/" + datePath;
} else {
dir = "./" + MediaServerConfig.FILE_STROAGE_ROOT + "/" + datePath;
}
File dirFile = new File(dir); File dirFile = new File(dir);
boolean bFile = dirFile.exists(); boolean bFile = dirFile.exists();
@@ -34,7 +34,7 @@ public class VersionAction extends Action {
try { try {
response.setContent(Utility.formatJson(new Gson().toJson(GitRepositoryState.getGitRepositoryState()))); response.setContent(Utility.formatJson(gson.toJson(GitRepositoryState.getGitRepositoryState())));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
response.setContent("{\"version\":\"unknown\"}"); response.setContent("{\"version\":\"unknown\"}");
@@ -99,7 +99,7 @@ abstract public class AdminAction extends Action {
} }
RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data); RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
response.send(); response.send();
} }
} }
@@ -112,7 +112,7 @@ abstract public class AdminAction extends Action {
} }
protected void sendApiMessage(Response response, String fromUser, String clientId, String topic, byte[] message, ApiCallback callback, boolean noAdmin) { protected void sendApiMessage(Response response, String fromUser, String clientId, String topic, byte[] message, ApiCallback callback, boolean noAdmin) {
ServerAPIHelper.sendRequest(fromUser, clientId, topic, message, new ServerAPIHelper.Callback() { ServerAPIHelper.sendRequest(fromUser, clientId, topic, message, callback == null ? null : new ServerAPIHelper.Callback() {
@Override @Override
public void onSuccess(byte[] result) { public void onSuccess(byte[] result) {
if(callback != null) { if(callback != null) {
@@ -50,7 +50,7 @@ public class ApplicationGetUserInfoAction extends AdminAction {
} }
response.setStatus(HttpResponseStatus.OK); response.setStatus(HttpResponseStatus.OK);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
} }
return true; return true;
} }
@@ -42,7 +42,7 @@ public class BlockUserAction extends AdminAction {
response.setStatus(HttpResponseStatus.OK); response.setStatus(HttpResponseStatus.OK);
RestResult result; RestResult result;
result = RestResult.resultOf(errorCode); result = RestResult.resultOf(errorCode);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
if (inputUserBlock.getStatus() == 2) { if (inputUserBlock.getStatus() == 2) {
sendApiMessage(null, null, ServerAPIHelper.KICKOFF_USER_REQUEST, inputUserBlock.getUserId().getBytes(), null); sendApiMessage(null, null, ServerAPIHelper.KICKOFF_USER_REQUEST, inputUserBlock.getUserId().getBytes(), null);
@@ -0,0 +1,62 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputChannelSubscribe;
import cn.wildfirechat.pojos.InputCreateChannel;
import cn.wildfirechat.pojos.InputSubscribeChannel;
import cn.wildfirechat.pojos.OutputCreateChannel;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.action.channel.ChannelAction;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.IMTopic;
@Route(APIPath.Subscribe_Channel)
@HttpMethod("POST")
public class ChannelSubscriberAction extends AdminAction {
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputSubscribeChannel inputSubscribeChannel = getRequestBody(request.getNettyRequest(), InputSubscribeChannel.class);
if (inputSubscribeChannel != null
&& !io.netty.util.internal.StringUtil.isNullOrEmpty(inputSubscribeChannel.getChannelId())
&& !io.netty.util.internal.StringUtil.isNullOrEmpty(inputSubscribeChannel.getUserId())) {
WFCMessage.ListenChannel.Builder builder = WFCMessage.ListenChannel.newBuilder().setChannelId(inputSubscribeChannel.getChannelId()).setListen(inputSubscribeChannel.getSubscribe());
sendApiMessage(response, inputSubscribeChannel.getUserId(), IMTopic.ChannelListenTopic, builder.build().toByteArray(), result -> {
ErrorCode errorCode = ErrorCode.fromCode(result[0]);
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
return new Result(ErrorCode.ERROR_CODE_SUCCESS);
} else {
return new Result(errorCode);
}
});
return false;
} else {
setResponseContent(RestResult.resultOf(ErrorCode.INVALID_PARAMETER), response);
}
}
return true;
}
}
@@ -36,7 +36,7 @@ public class CheckUserOnlineAction extends AdminAction {
InputGetUserInfo inputUserId = getRequestBody(request.getNettyRequest(), InputGetUserInfo.class); InputGetUserInfo inputUserId = getRequestBody(request.getNettyRequest(), InputGetUserInfo.class);
if (inputUserId == null || !StringUtil.isNullOrEmpty(inputUserId.getUserId())) { if (inputUserId == null || !StringUtil.isNullOrEmpty(inputUserId.getUserId())) {
sendApiMessage(response, inputUserId.getUserId(), ServerAPIHelper.CHECK_USER_ONLINE_REQUEST, inputUserId.getUserId().getBytes(), res -> { sendApiMessage(response, inputUserId.getUserId(), ServerAPIHelper.CHECK_USER_ONLINE_REQUEST, inputUserId.getUserId().getBytes(), res -> {
OutputCheckUserOnline out = new Gson().fromJson(new String(res), OutputCheckUserOnline.class); OutputCheckUserOnline out = gson.fromJson(new String(res), OutputCheckUserOnline.class);
return new Result(ErrorCode.ERROR_CODE_SUCCESS, out); return new Result(ErrorCode.ERROR_CODE_SUCCESS, out);
}); });
return false; return false;
@@ -0,0 +1,52 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputChannelId;
import cn.wildfirechat.pojos.InputSubscribeChannel;
import cn.wildfirechat.pojos.OutputBooleanValue;
import cn.wildfirechat.pojos.OutputGetChannelInfo;
import cn.wildfirechat.proto.WFCMessage;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.util.internal.StringUtil;
@Route(APIPath.Check_User_Subscribe_Channel)
@HttpMethod("POST")
public class CheckUserSubscribeChannelAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputSubscribeChannel inputSubscribeChannel = getRequestBody(request.getNettyRequest(), InputSubscribeChannel.class);
if (inputSubscribeChannel != null
&& !StringUtil.isNullOrEmpty(inputSubscribeChannel.getChannelId())
&& !StringUtil.isNullOrEmpty(inputSubscribeChannel.getUserId())) {
boolean isInChannel = messagesStore.checkUserInChannel(inputSubscribeChannel.getUserId(), inputSubscribeChannel.getChannelId());
OutputBooleanValue outputBooleanValue = new OutputBooleanValue();
outputBooleanValue.value = isInChannel;
setResponseContent(RestResult.ok(outputBooleanValue), response);
} else {
setResponseContent(RestResult.resultOf(ErrorCode.INVALID_PARAMETER), response);
}
}
return true;
}
}
@@ -39,7 +39,7 @@ public class CreateChatroomAction extends AdminAction {
FullHttpRequest fullHttpRequest = (FullHttpRequest)request.getNettyRequest(); FullHttpRequest fullHttpRequest = (FullHttpRequest)request.getNettyRequest();
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content()); byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes); String content = new String(bytes);
Gson gson = new Gson();
InputCreateChatroom inputCreateChatroom = gson.fromJson(content, InputCreateChatroom.class); InputCreateChatroom inputCreateChatroom = gson.fromJson(content, InputCreateChatroom.class);
if (inputCreateChatroom != null if (inputCreateChatroom != null
&& !StringUtil.isNullOrEmpty(inputCreateChatroom.getTitle())) { && !StringUtil.isNullOrEmpty(inputCreateChatroom.getTitle())) {
@@ -67,7 +67,7 @@ public class CreateRobotAction extends AdminAction {
Utility.printExecption(LOG, e, IMExceptionEvent.EventType.ADMIN_API_Exception); Utility.printExecption(LOG, e, IMExceptionEvent.EventType.ADMIN_API_Exception);
response.setStatus(HttpResponseStatus.OK); response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.ERROR_CODE_SERVER_ERROR, e.getMessage()); RestResult result = RestResult.resultOf(ErrorCode.ERROR_CODE_SERVER_ERROR, e.getMessage());
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
return true; return true;
} }
@@ -37,7 +37,7 @@ public class DestoryChatroomAction extends AdminAction {
FullHttpRequest fullHttpRequest = (FullHttpRequest)request.getNettyRequest(); FullHttpRequest fullHttpRequest = (FullHttpRequest)request.getNettyRequest();
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content()); byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes); String content = new String(bytes);
Gson gson = new Gson();
InputDestoryChatroom inputDestoryChatroom = gson.fromJson(content, InputDestoryChatroom.class); InputDestoryChatroom inputDestoryChatroom = gson.fromJson(content, InputDestoryChatroom.class);
if (inputDestoryChatroom != null if (inputDestoryChatroom != null
&& !StringUtil.isNullOrEmpty(inputDestoryChatroom.getChatroomId())) { && !StringUtil.isNullOrEmpty(inputDestoryChatroom.getChatroomId())) {
@@ -0,0 +1,55 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputUserId;
import cn.wildfirechat.pojos.OutputGroupIds;
import cn.wildfirechat.pojos.StringPairPojo;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList;
import java.util.Set;
@Route(APIPath.Get_Common_Groups)
@HttpMethod("POST")
public class GetCommonGroupsAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
StringPairPojo input = getRequestBody(request.getNettyRequest(), StringPairPojo.class);
if (input != null
&& (!StringUtil.isNullOrEmpty(input.getFirst())) && (!StringUtil.isNullOrEmpty(input.getSecond()))) {
Set<String> groupIds = messagesStore.getCommonGroupIds(input.getFirst(), input.getSecond());
OutputGroupIds outputGroupIds = new OutputGroupIds();
outputGroupIds.setGroupIds(new ArrayList<>(groupIds));
RestResult result = RestResult.ok(outputGroupIds);
setResponseContent(result, response);
} else {
setResponseContent(RestResult.resultOf(ErrorCode.INVALID_PARAMETER), response);
}
}
return true;
}
}
@@ -49,7 +49,7 @@ public class GetUserAction extends AdminAction {
} }
RestResult result; RestResult result;
if (user == null) { if (user == null || user.getDeleted() > 0) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST); result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else { } else {
result = RestResult.ok(InputOutputUserInfo.fromPbUser(user)); result = RestResult.ok(InputOutputUserInfo.fromPbUser(user));
@@ -50,7 +50,7 @@ public class ModifyGroupMemberExtraAction extends AdminAction {
} else { } else {
response.setStatus(HttpResponseStatus.OK); response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER); RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
} }
} }
return true; return true;
@@ -52,7 +52,7 @@ public class ApplicationGetUserInfoAction extends ChannelAction {
} }
response.setStatus(HttpResponseStatus.OK); response.setStatus(HttpResponseStatus.OK);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
} }
return true; return true;
} }
@@ -101,7 +101,7 @@ abstract public class ChannelAction extends Action {
} }
RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data); RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
response.send(); response.send();
} }
@@ -110,7 +110,7 @@ abstract public class ChannelAction extends Action {
FullHttpRequest fullHttpRequest = (FullHttpRequest) request; FullHttpRequest fullHttpRequest = (FullHttpRequest) request;
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content()); byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes, StandardCharsets.UTF_8); String content = new String(bytes, StandardCharsets.UTF_8);
Gson gson = new Gson();
T t = gson.fromJson(content, cls); T t = gson.fromJson(content, cls);
return t; return t;
} }
@@ -44,7 +44,7 @@ public class GetUserAction extends ChannelAction {
RestResult result; RestResult result;
if (user == null || StringUtil.isNullOrEmpty(user.getName())) { if (user == null || StringUtil.isNullOrEmpty(user.getName()) || user.getDeleted() > 0) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST); result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else { } else {
if (channelInfo.getOwner().equals(inputUserId.getUserId()) || if (channelInfo.getOwner().equals(inputUserId.getUserId()) ||
@@ -38,7 +38,7 @@ public class SubscriberChannelAction extends ChannelAction {
if(input.getSubscribe() > 0 && (channelInfo.getStatus() & ProtoConstants.ChannelState.Channel_State_Mask_Active_Subscribe) == 0) { if(input.getSubscribe() > 0 && (channelInfo.getStatus() & ProtoConstants.ChannelState.Channel_State_Mask_Active_Subscribe) == 0) {
response.setStatus(HttpResponseStatus.OK); response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_RIGHT); RestResult result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_RIGHT);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
return true; return true;
} }
WFCMessage.ListenChannel listenChannel = WFCMessage.ListenChannel.newBuilder().setChannelId(channelInfo.getTargetId()).setListen(input.getSubscribe()).build(); WFCMessage.ListenChannel listenChannel = WFCMessage.ListenChannel.newBuilder().setChannelId(channelInfo.getTargetId()).setListen(input.getSubscribe()).build();
@@ -52,7 +52,7 @@ public class ApplicationGetUserInfoAction extends RobotAction {
} }
response.setStatus(HttpResponseStatus.OK); response.setStatus(HttpResponseStatus.OK);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
} }
return true; return true;
} }
@@ -44,7 +44,7 @@ public class GetUserAction extends RobotAction {
} }
RestResult result; RestResult result;
if (user == null) { if (user == null || user.getDeleted() > 0) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST); result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else { } else {
result = RestResult.ok(InputOutputUserInfo.fromPbUser(user)); result = RestResult.ok(InputOutputUserInfo.fromPbUser(user));
@@ -53,7 +53,7 @@ public class ModifyGroupMemberExtraAction extends RobotAction {
} else { } else {
response.setStatus(HttpResponseStatus.OK); response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER); RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
} }
} }
return true; return true;
@@ -102,7 +102,7 @@ abstract public class RobotAction extends Action {
} }
RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data); RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data);
response.setContent(new Gson().toJson(result)); response.setContent(gson.toJson(result));
response.send(); response.send();
} }
@@ -113,7 +113,7 @@ abstract public class RobotAction extends Action {
String content = null; String content = null;
try { try {
content = new String(bytes, "UTF-8"); content = new String(bytes, "UTF-8");
Gson gson = new Gson();
T t = gson.fromJson(content, cls); T t = gson.fromJson(content, cls);
return t; return t;
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
@@ -34,7 +34,6 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception { protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
Logger.info("Http request whit url {}", fullHttpRequest.uri());
final Request request = Request.build(ctx, fullHttpRequest); final Request request = Request.build(ctx, fullHttpRequest);
final Response response = Response.build(ctx, request); final Response response = Response.build(ctx, request);
@@ -114,9 +113,19 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
handleOptions(ctx, request, response); handleOptions(ctx, request, response);
return; return;
} }
if ("HEAD".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpResponseStatus.OK);
response.send();
return;
}
Action action; Action action;
if (isValidePath(request.getPath())) { if (isValidePath(request.getPath())) {
action = ServerSetting.getAction(request.getPath(), request.getMethod().toUpperCase()); action = ServerSetting.getAction(request.getPath(), request.getMethod().toUpperCase());
if (action != null) {
Logger.info("Http request whit url {}", request.getPath());
}
} else { } else {
action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR); action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
} }
@@ -130,7 +139,11 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
action = ServerSetting.getAction(ServerSetting.MAPPING_ALL, request.getMethod()); action = ServerSetting.getAction(ServerSetting.MAPPING_ALL, request.getMethod());
if(null == action){ if(null == action){
// 非Action方法,调用静态文件读取 // 非Action方法,调用静态文件读取
action = Singleton.get(FileAction.class); if(request.getPath().startsWith("/fs")) {
action = Singleton.get(FileAction.class);
} else {
action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
}
} }
} }
action.ctx = ctx; action.ctx = ctx;
@@ -81,18 +81,6 @@ public class Request {
// request URI parameters // request URI parameters
this.putParams(new QueryStringDecoder(uri)); this.putParams(new QueryStringDecoder(uri));
if(nettyRequest.method() != HttpMethod.GET){
HttpPostRequestDecoder decoder = null;
try {
decoder = new HttpPostRequestDecoder(HTTP_DATA_FACTORY, nettyRequest);
this.putParams(decoder);
} finally {
if(null != decoder){
decoder.destroy();
decoder = null;
}
}
}
// IP // IP
this.putIp(ctx); this.putIp(ctx);
@@ -41,6 +41,7 @@ import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.DefaultCookie; import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder; import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import org.apache.tika.Tika;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
@@ -469,7 +470,9 @@ public class Response {
this.setContentLength(fileLength); this.setContentLength(fileLength);
//文件类型 //文件类型
String contentType = HttpUtil.getMimeType(file.getName()); Tika tika = new Tika();
String contentType = tika.detect(file);
// String contentType = HttpUtil.getMimeType(file.getName());
if(StrUtil.isBlank(contentType)){ if(StrUtil.isBlank(contentType)){
//无法识别默认使用数据流 //无法识别默认使用数据流
contentType = "application/octet-stream"; contentType = "application/octet-stream";
@@ -110,8 +110,13 @@ public final class BrokerConstants {
public static final String TOKEN_SECRET_KEY = "token.key"; public static final String TOKEN_SECRET_KEY = "token.key";
public static final String TOKEN_EXPIRE_TIME = "token.expire_time"; public static final String TOKEN_EXPIRE_TIME = "token.expire_time";
public static final String CONNECT_CLIENT_SIGNATURE_LIST = "connect.client_signature_list";
public static final String CONNECT_REJECT_EMPTY_SIGNATURE = "connect.reject_empty_signature";
public static final String EMBED_DB_PROPERTY_NAME = "embed.db"; public static final String EMBED_DB_PROPERTY_NAME = "embed.db";
public static final String DB_AUTO_CLEAN_HISTORY_MESSAGES = "db.auto_clean_history_messages"; public static final String DB_AUTO_CLEAN_HISTORY_MESSAGES = "db.auto_clean_history_messages";
public static final String H2DB_PATH = "h2db.path";
public static final String SENSITIVE_Filter_Type = "sensitive.filter.type"; public static final String SENSITIVE_Filter_Type = "sensitive.filter.type";
@@ -120,7 +125,9 @@ public final class BrokerConstants {
public static final String SENSITIVE_Remote_Fail_When_Matched = "sensitive.remote_fail_when_matched"; public static final String SENSITIVE_Remote_Fail_When_Matched = "sensitive.remote_fail_when_matched";
public static final String MESSAGE_Forward_Url = "message.forward.url"; public static final String MESSAGE_Forward_Url = "message.forward.url";
public static final String MESSAGE_Sensitive_Forward_Url = "message.sensitive.forward.url";
public static final String MESSAGE_Forward_Types = "message.forward.types"; public static final String MESSAGE_Forward_Types = "message.forward.types";
public static final String MESSAGE_Forward_Exclude_Types = "message.forward.exclude_types";
public static final String MESSAGE_MentionMsg_Forward_Url = "message.mentionmsg.forward.url"; public static final String MESSAGE_MentionMsg_Forward_Url = "message.mentionmsg.forward.url";
@@ -138,6 +145,7 @@ public final class BrokerConstants {
public static final String MESSAGE_Max_Queue = "message.max_queue"; public static final String MESSAGE_Max_Queue = "message.max_queue";
public static final String MESSAGE_Disable_Stranger_Chat = "message.disable_stranger_chat"; public static final String MESSAGE_Disable_Stranger_Chat = "message.disable_stranger_chat";
public static final String MESSAGE_Allow_Stranger_Chat_List = "message.allow_stranger_chat_list";
public static final String MESSAGE_Blacklist_Strategy = "message.blacklist.strategy"; public static final String MESSAGE_Blacklist_Strategy = "message.blacklist.strategy";
@@ -153,7 +161,6 @@ public final class BrokerConstants {
public static final String FRIEND_Repeat_Request_Duration = "friend.repeat_request_duration"; public static final String FRIEND_Repeat_Request_Duration = "friend.repeat_request_duration";
public static final String FRIEND_Reject_Request_Duration = "friend.reject_request_duration"; public static final String FRIEND_Reject_Request_Duration = "friend.reject_request_duration";
public static final String FRIEND_Request_Expiration_Duration = "friend.request_expiration_duration"; public static final String FRIEND_Request_Expiration_Duration = "friend.request_expiration_duration";
public static final String FRIEND_New_Welcome_Message = "friend.new_welcome_message";
public static final String CHATROOM_Participant_Idle_Time = "chatroom.participant_idle_time"; public static final String CHATROOM_Participant_Idle_Time = "chatroom.participant_idle_time";
public static final String CHATROOM_Rejoin_When_Active = "chatroom.rejoin_when_active"; public static final String CHATROOM_Rejoin_When_Active = "chatroom.rejoin_when_active";
@@ -40,6 +40,10 @@ public class CreateGroupHandler extends GroupHandler<WFCMessage.CreateGroupReque
return ErrorCode.ERROR_CODE_NOT_RIGHT; return ErrorCode.ERROR_CODE_NOT_RIGHT;
} }
if(!isAdmin && request.getGroup().getGroupInfo().getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
WFCMessage.GroupInfo groupInfo = m_messagesStore.createGroup(fromUser, request.getGroup().getGroupInfo(), request.getGroup().getMembersList(), request.getMemberExtra(), isAdmin); WFCMessage.GroupInfo groupInfo = m_messagesStore.createGroup(fromUser, request.getGroup().getGroupInfo(), request.getGroup().getMembersList(), request.getMemberExtra(), isAdmin);
if (groupInfo != null) { if (groupInfo != null) {
if(request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if(request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
@@ -0,0 +1,33 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.imhandler;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf;
import win.liyufan.im.IMTopic;
import java.util.Set;
import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS;
@Handler(IMTopic.GetCommonGroupsTopic)
public class GetCommonGroupsHandler extends IMHandler<WFCMessage.IDBuf> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.IDBuf request, Qos1PublishHandler.IMCallback callback) {
WFCMessage.PullUserResult.Builder resultBuilder = WFCMessage.PullUserResult.newBuilder();
Set<String> strings = m_messagesStore.getCommonGroupIds(fromUser, request.getId());
WFCMessage.IDListBuf idListBuf = WFCMessage.IDListBuf.newBuilder().addAllId(strings).build();
byte[] data = idListBuf.toByteArray();
ackPayload.ensureWritable(data.length).writeBytes(data);
return ERROR_CODE_SUCCESS;
}
}
@@ -0,0 +1,33 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.imhandler;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf;
import win.liyufan.im.IMTopic;
import java.util.Set;
import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS;
@Handler(IMTopic.GetMyGroupsTopic)
public class GetMyGroupsHandler extends IMHandler<Void> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, Void request, Qos1PublishHandler.IMCallback callback) {
WFCMessage.PullUserResult.Builder resultBuilder = WFCMessage.PullUserResult.newBuilder();
Set<String> strings = m_messagesStore.getUserGroupIds(fromUser);
WFCMessage.IDListBuf idListBuf = WFCMessage.IDListBuf.newBuilder().addAllId(strings).build();
byte[] data = idListBuf.toByteArray();
ackPayload.ensureWritable(data.length).writeBytes(data);
return ERROR_CODE_SUCCESS;
}
}
@@ -26,6 +26,7 @@ public class GetQiniuUploadTokenHandler extends IMHandler<Byte> {
String token; String token;
WFCMessage.GetUploadTokenResult.Builder resultBuilder = WFCMessage.GetUploadTokenResult.newBuilder(); WFCMessage.GetUploadTokenResult.Builder resultBuilder = WFCMessage.GetUploadTokenResult.newBuilder();
if (MediaServerConfig.USER_QINIU) { if (MediaServerConfig.USER_QINIU) {
Auth auth = Auth.create(MediaServerConfig.QINIU_ACCESS_KEY, MediaServerConfig.QINIU_SECRET_KEY); Auth auth = Auth.create(MediaServerConfig.QINIU_ACCESS_KEY, MediaServerConfig.QINIU_SECRET_KEY);
@@ -10,12 +10,10 @@ package io.moquette.imhandler;
import cn.wildfirechat.proto.ProtoConstants; import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage; import cn.wildfirechat.proto.WFCMessage;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.spi.impl.Qos1PublishHandler; import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import cn.wildfirechat.common.ErrorCode; import cn.wildfirechat.common.ErrorCode;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import win.liyufan.im.I18n;
import win.liyufan.im.IMTopic; import win.liyufan.im.IMTopic;
import win.liyufan.im.MessageShardingUtil; import win.liyufan.im.MessageShardingUtil;
@@ -43,17 +41,8 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType); saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType);
} }
MemorySessionStore.Session session = m_sessionsStore.getSession(clientID);
String language = "zh_CN";
if (session != null && !StringUtil.isNullOrEmpty(session.getLanguage())) {
language = session.getLanguage();
}
WFCMessage.MessageContent.Builder contentBuilder = WFCMessage.MessageContent.newBuilder(); WFCMessage.MessageContent.Builder contentBuilder = WFCMessage.MessageContent.newBuilder();
if (m_messagesStore.isNewFriendWelcomeMessage()) { contentBuilder.setType(92);
contentBuilder.setType(92);
} else {
contentBuilder.setType(90).setContent(I18n.getString(language, "Above_Greeting_Message"));
}
builder = WFCMessage.Message.newBuilder(); builder = WFCMessage.Message.newBuilder();
builder.setFromUser(request.getTargetUid()); builder.setFromUser(request.getTargetUid());
@@ -64,13 +53,7 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
messageId = MessageShardingUtil.generateId(); messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId); builder.setMessageId(messageId);
saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType); saveAndPublish(request.getTargetUid(), null, builder.build(), requestSourceType);
contentBuilder.setType(93);
if (m_messagesStore.isNewFriendWelcomeMessage()) {
contentBuilder.setType(93);
} else {
contentBuilder.setContent(I18n.getString(language, "Friend_Can_Start_Chat"));
}
builder.setContent(contentBuilder); builder.setContent(contentBuilder);
messageId = MessageShardingUtil.generateId(); messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId); builder.setMessageId(messageId);
@@ -25,6 +25,7 @@ import io.netty.util.internal.StringUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import cn.wildfirechat.common.ErrorCode; import cn.wildfirechat.common.ErrorCode;
import win.liyufan.im.GsonUtil;
import win.liyufan.im.RateLimiter; import win.liyufan.im.RateLimiter;
import win.liyufan.im.Utility; import win.liyufan.im.Utility;
@@ -39,6 +40,8 @@ import java.util.*;
import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_OVER_FREQUENCY; import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_OVER_FREQUENCY;
import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS; import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS;
import static io.moquette.BrokerConstants.CLIENT_REQUEST_RATE_LIMIT; import static io.moquette.BrokerConstants.CLIENT_REQUEST_RATE_LIMIT;
import static win.liyufan.im.IMTopic.GetUserSettingTopic;
import static win.liyufan.im.IMTopic.PullMessageTopic;
/** /**
* 请求处理接口<br> * 请求处理接口<br>
@@ -139,7 +142,7 @@ abstract public class IMHandler<T> {
// } // }
//json ? //json ?
return (T)(new Gson().fromJson(new String(bytes), dataCls)); return (T)(GsonUtil.gson.fromJson(new String(bytes), dataCls));
} }
public static void init(IMessagesStore ms, ISessionsStore ss, MessagesPublisher p, ThreadPoolExecutorWrapper businessExecutor, Server server) { public static void init(IMessagesStore ms, ISessionsStore ss, MessagesPublisher p, ThreadPoolExecutorWrapper businessExecutor, Server server) {
@@ -162,10 +165,17 @@ abstract public class IMHandler<T> {
mLimitCounter = new RateLimiter(5, clientRateLimit); mLimitCounter = new RateLimiter(5, clientRateLimit);
} }
boolean isNotLimitTopic(String topic) {
if (PullMessageTopic.equals(topic)
|| GetUserSettingTopic.equals(topic)) {
return true;
}
return false;
}
public ErrorCode preAction(String clientID, String fromUser, String topic, Qos1PublishHandler.IMCallback callback, ProtoConstants.RequestSourceType requestSourceType) { public ErrorCode preAction(String clientID, String fromUser, String topic, Qos1PublishHandler.IMCallback callback, ProtoConstants.RequestSourceType requestSourceType) {
LOG.info("imHandler fromUser={}, clientId={}, topic={}", fromUser, clientID, topic); LOG.info("imHandler fromUser={}, clientId={}, topic={}", fromUser, clientID, topic);
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User && !mLimitCounter.isGranted(clientID + fromUser + topic)) { if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_User && !isNotLimitTopic(topic) && !mLimitCounter.isGranted(clientID + fromUser + topic)) {
return ErrorCode.ERROR_CODE_OVER_FREQUENCY; return ErrorCode.ERROR_CODE_OVER_FREQUENCY;
} }
return ErrorCode.ERROR_CODE_SUCCESS; return ErrorCode.ERROR_CODE_SUCCESS;
@@ -29,6 +29,10 @@ public class KickoffGroupMember extends GroupHandler<WFCMessage.RemoveGroupMembe
errorCode = ErrorCode.ERROR_CODE_NOT_EXIST; errorCode = ErrorCode.ERROR_CODE_NOT_EXIST;
} else { } else {
boolean isAdmin = requestSourceType == ProtoConstants.RequestSourceType.Request_From_Admin; boolean isAdmin = requestSourceType == ProtoConstants.RequestSourceType.Request_From_Admin;
if (!isAdmin && groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
boolean isAllow = isAdmin; boolean isAllow = isAdmin;
if (!isAllow) { if (!isAllow) {
if (groupInfo.getOwner() != null) { if (groupInfo.getOwner() != null) {
@@ -31,7 +31,7 @@ public class QuitGroupHandler extends GroupHandler<WFCMessage.QuitGroupRequest>
return ErrorCode.ERROR_CODE_NOT_RIGHT; return ErrorCode.ERROR_CODE_NOT_RIGHT;
} }
ErrorCode errorCode = m_messagesStore.quitGroup(fromUser, request.getGroupId()); ErrorCode errorCode = m_messagesStore.quitGroup(fromUser, request.getGroupId(), isAdmin);
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) { if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) { if (request.hasNotifyContent() && request.getNotifyContent().getType() > 0) {
sendGroupNotification(fromUser, request.getGroupId(), request.getToLineList(), request.getNotifyContent()); sendGroupNotification(fromUser, request.getGroupId(), request.getToLineList(), request.getNotifyContent());
@@ -40,6 +40,11 @@ public class RouteHandler extends IMHandler<WFCMessage.RouteRequest> {
return ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH; return ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH;
} }
if(request.getPlatform() != session.getPlatform() && m_messagesStore.existSignatures()) {
LOG.error("Session <{}, {}> platform is {} mismatch the request {}", session.getUsername(), session.getClientID(), session.getPlatform(), request.getPlatform());
return ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH;
}
String serverIp = mServer.getServerIp(); String serverIp = mServer.getServerIp();
String longPort = mServer.getLongPort(); String longPort = mServer.getLongPort();
String shortPort = mServer.getShortPort(); String shortPort = mServer.getShortPort();
@@ -20,11 +20,9 @@ import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import cn.wildfirechat.common.ErrorCode; import cn.wildfirechat.common.ErrorCode;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import win.liyufan.im.HttpUtils; import win.liyufan.im.*;
import win.liyufan.im.IMTopic;
import win.liyufan.im.MessageShardingUtil;
import win.liyufan.im.Utility;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -34,7 +32,9 @@ import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS;
public class SendMessageHandler extends IMHandler<WFCMessage.Message> { public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
private int mSensitiveType = 0; //命中敏感词时,0 失败,1 吞掉, 2 敏感词替换成*。 private int mSensitiveType = 0; //命中敏感词时,0 失败,1 吞掉, 2 敏感词替换成*。
private String mForwardUrl = null; private String mForwardUrl = null;
private String mSensitiveMessageForwardUrl = null;
private Set<Integer> mForwardMessageTypes = new HashSet<>(); private Set<Integer> mForwardMessageTypes = new HashSet<>();
private Set<Integer> mForwardExcludeMessageTypes = new HashSet<>();
private String mMentionForwardUrl = null; private String mMentionForwardUrl = null;
private int mBlacklistStrategy = 0; //黑名单中时,0失败,1吞掉。 private int mBlacklistStrategy = 0; //黑名单中时,0失败,1吞掉。
private boolean mNoForwardAdminMessage = false; private boolean mNoForwardAdminMessage = false;
@@ -49,14 +49,11 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
mForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Url); mForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Url);
if (!StringUtil.isNullOrEmpty(mForwardUrl)) { if (!StringUtil.isNullOrEmpty(mForwardUrl)) {
String forwardTypes = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Types); String forwardTypes = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Types);
if (!StringUtil.isNullOrEmpty(forwardTypes)) { parseTypes(mForwardMessageTypes, forwardTypes);
String[] tss = forwardTypes.split(","); String excludeTypes = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_Exclude_Types);
for (String ts:tss) { parseTypes(mForwardExcludeMessageTypes, excludeTypes);
mForwardMessageTypes.add(Integer.parseInt(ts.trim()));
}
}
} }
mSensitiveMessageForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Sensitive_Forward_Url);
mMentionForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_MentionMsg_Forward_Url); mMentionForwardUrl = mServer.getConfig().getProperty(BrokerConstants.MESSAGE_MentionMsg_Forward_Url);
try { try {
@@ -99,6 +96,30 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
} }
private void parseTypes(Collection<Integer> collection, String types) {
try {
if (!StringUtil.isNullOrEmpty(types)) {
String[] tss = types.split(",");
for (String ts:tss) {
if(ts.contains("-")) {
String[] ss = ts.split("-");
if(ss.length == 2) {
int begin = Integer.parseInt(ss[0]);
int end = Integer.parseInt(ss[1]);
for (int i = begin; i <= end ; i++) {
collection.add(i);
}
}
} else {
collection.add(Integer.parseInt(ts.trim()));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override @Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.Message message, Qos1PublishHandler.IMCallback callback) { public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.Message message, Qos1PublishHandler.IMCallback callback) {
ErrorCode errorCode = ErrorCode.ERROR_CODE_SUCCESS; ErrorCode errorCode = ErrorCode.ERROR_CODE_SUCCESS;
@@ -181,7 +202,7 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
} }
} }
if (!StringUtil.isNullOrEmpty(mForwardUrl) && (mForwardMessageTypes.isEmpty() || mForwardMessageTypes.contains(message.getContent().getType())) && !(isAdmin && mNoForwardAdminMessage)) { if (!StringUtil.isNullOrEmpty(mForwardUrl) && (mForwardMessageTypes.isEmpty() || mForwardMessageTypes.contains(message.getContent().getType())) && !(isAdmin && mNoForwardAdminMessage) && !mForwardExcludeMessageTypes.contains(message.getContent().getType())) {
publisher.forwardMessage(message, mForwardUrl, outputClient); publisher.forwardMessage(message, mForwardUrl, outputClient);
} }
@@ -194,6 +215,9 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
Set<String> matched = m_messagesStore.handleSensitiveWord(message.getContent().getSearchableContent()); Set<String> matched = m_messagesStore.handleSensitiveWord(message.getContent().getSearchableContent());
if (matched != null && !matched.isEmpty()) { if (matched != null && !matched.isEmpty()) {
m_messagesStore.storeSensitiveMessage(message); m_messagesStore.storeSensitiveMessage(message);
if (!StringUtil.isNullOrEmpty(mSensitiveMessageForwardUrl)) {
publisher.forwardMessage(message, mSensitiveMessageForwardUrl, outputClient);
}
if (mSensitiveType == 0) { if (mSensitiveType == 0) {
errorCode = ErrorCode.ERROR_CODE_SENSITIVE_MATCHED; errorCode = ErrorCode.ERROR_CODE_SENSITIVE_MATCHED;
} else if (mSensitiveType == 1) { } else if (mSensitiveType == 1) {
@@ -215,7 +239,7 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
if(StringUtil.isNullOrEmpty(content)) { if(StringUtil.isNullOrEmpty(content)) {
saveAndPublish(fromUser, clientID, finalMsg, requestSourceType); saveAndPublish(fromUser, clientID, finalMsg, requestSourceType);
} else { } else {
MessagePayload payload = new Gson().fromJson(content, MessagePayload.class); MessagePayload payload = GsonUtil.gson.fromJson(content, MessagePayload.class);
if (payload != null && payload.getType() > 0) { if (payload != null && payload.getType() > 0) {
WFCMessage.Message newMsg = finalMsg.toBuilder().setContent(payload.toProtoMessageContent()).build(); WFCMessage.Message newMsg = finalMsg.toBuilder().setContent(payload.toProtoMessageContent()).build();
saveAndPublish(fromUser, clientID, newMsg, requestSourceType); saveAndPublish(fromUser, clientID, newMsg, requestSourceType);
@@ -72,7 +72,7 @@ public class InterceptConnectMessage extends InterceptAbstractMessage {
} }
public byte[] getPassword() { public byte[] getPassword() {
return msg.payload().password(); return msg.payload().passwordInBytes();
} }
public String getWillTopic() { public String getWillTopic() {
@@ -1712,6 +1712,35 @@ public class DatabaseStore {
return null; return null;
} }
Set<String> getCommonGroupIds(String userId1, String userId2) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet rs = null;
try {
connection = DBUtil.getConnection();
String sql = "select t1._gid from t_group_member t1, t_group_member t2 where t1._mid = ? and t2._mid = ? and t1._type <> 4 and t2._type <> 4 and t1._gid = t2._gid";
statement = connection.prepareStatement(sql);
statement.setString(1, userId1);
statement.setString(2, userId2);
rs = statement.executeQuery();
Set<String> out = new HashSet<>();
while (rs.next()) {
String uid = rs.getString(1);
out.add(uid);
}
return out;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Utility.printExecption(LOG, e, RDBS_Exception);
} finally {
DBUtil.closeDB(connection, statement, rs);
}
return null;
}
void updateGroupMemberDt(final String groupId, final long dt) { void updateGroupMemberDt(final String groupId, final long dt) {
Connection connection = null; Connection connection = null;
PreparedStatement statement = null; PreparedStatement statement = null;
@@ -49,6 +49,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -121,6 +122,8 @@ public class MemoryMessagesStore implements IMessagesStore {
private Lock mReadLock = mLock.readLock(); private Lock mReadLock = mLock.readLock();
private Lock mWriteLock = mLock.writeLock(); private Lock mWriteLock = mLock.writeLock();
private Lock mUserSettingLock = new ReentrantLock();
private final DatabaseStore databaseStore; private final DatabaseStore databaseStore;
private ConcurrentHashMap<String, Long> userMaxPullSeq = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, Long> userMaxPullSeq = new ConcurrentHashMap<>();
@@ -138,11 +141,14 @@ public class MemoryMessagesStore implements IMessagesStore {
private long mFriendRequestDuration = 7 * 24 * 60 * 60 * 1000; private long mFriendRequestDuration = 7 * 24 * 60 * 60 * 1000;
private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000; private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000;
private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000; private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000;
private boolean mFriendNewWelcomeMessage = true;
private boolean mMultiPlatformNotification = false; private boolean mMultiPlatformNotification = false;
private boolean mMobileDefaultSilentWhenPCOnline = true; private boolean mMobileDefaultSilentWhenPCOnline = true;
private boolean mDisableStrangerChat = false; private boolean mDisableStrangerChat = false;
private Set<String> mAllowStrangerChatSet = new HashSet<>();
private Set<String> mClientSignatureSet = new HashSet<>();
private boolean mRejectEmptySignature = true;
private long mChatroomParticipantIdleTime = 900000; private long mChatroomParticipantIdleTime = 900000;
private boolean mChatroomRejoinWhenActive = true; private boolean mChatroomRejoinWhenActive = true;
@@ -188,6 +194,27 @@ public class MemoryMessagesStore implements IMessagesStore {
IS_CHATROOM_MESSAGE_REMOTE_HISTORY_MESSAGE = "1".equals(m_Server.getConfig().getProperty(MESSAGE_Remote_Chatroom_History_Message, "1")); IS_CHATROOM_MESSAGE_REMOTE_HISTORY_MESSAGE = "1".equals(m_Server.getConfig().getProperty(MESSAGE_Remote_Chatroom_History_Message, "1"));
Constants.MAX_MESSAGE_QUEUE = Integer.parseInt(m_Server.getConfig().getProperty(MESSAGE_Max_Queue)); Constants.MAX_MESSAGE_QUEUE = Integer.parseInt(m_Server.getConfig().getProperty(MESSAGE_Max_Queue));
try {
String signatureList = m_Server.getConfig().getProperty(BrokerConstants.CONNECT_CLIENT_SIGNATURE_LIST);
if(!StringUtil.isNullOrEmptyAfterTrim(signatureList)) {
signatureList = signatureList.replace(",", ",");
for (String s : signatureList.split(",")) {
mClientSignatureSet.add(s.trim());
}
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if(!mClientSignatureSet.isEmpty()) {
mRejectEmptySignature = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CONNECT_REJECT_EMPTY_SIGNATURE, "true"));
} else {
mRejectEmptySignature = false;
}
} catch (Exception e) {
}
try { try {
mDisableStrangerChat = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.MESSAGE_Disable_Stranger_Chat)); mDisableStrangerChat = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.MESSAGE_Disable_Stranger_Chat));
} catch (Exception e) { } catch (Exception e) {
@@ -196,6 +223,18 @@ public class MemoryMessagesStore implements IMessagesStore {
printMissConfigLog(MESSAGE_Disable_Stranger_Chat, mDisableStrangerChat + ""); printMissConfigLog(MESSAGE_Disable_Stranger_Chat, mDisableStrangerChat + "");
} }
try {
String allowStrangerList = m_Server.getConfig().getProperty(BrokerConstants.MESSAGE_Allow_Stranger_Chat_List);
if(!StringUtil.isNullOrEmptyAfterTrim(allowStrangerList)) {
allowStrangerList = allowStrangerList.replace(",", ",");
for (String s : allowStrangerList.split(",")) {
mAllowStrangerChatSet.add(s.trim());
}
}
} catch (Exception e) {
e.printStackTrace();
}
try { try {
mMultiPlatformNotification = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.SERVER_MULTI_PLATFROM_NOTIFICATION, "false")); mMultiPlatformNotification = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.SERVER_MULTI_PLATFROM_NOTIFICATION, "false"));
} catch (Exception e) { } catch (Exception e) {
@@ -256,15 +295,6 @@ public class MemoryMessagesStore implements IMessagesStore {
printMissConfigLog(FRIEND_Request_Expiration_Duration, mFriendRequestExpiration + ""); printMissConfigLog(FRIEND_Request_Expiration_Duration, mFriendRequestExpiration + "");
} }
try {
mFriendNewWelcomeMessage = Boolean.parseBoolean(m_Server.getConfig().getProperty(FRIEND_New_Welcome_Message));
} catch (Exception e) {
e.printStackTrace();
Utility.printExecption(LOG, e);
printMissConfigLog(FRIEND_New_Welcome_Message, mFriendNewWelcomeMessage + "");
}
try { try {
mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active)); mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active));
} catch (Exception e) { } catch (Exception e) {
@@ -539,8 +569,17 @@ public class MemoryMessagesStore implements IMessagesStore {
int pullType = ProtoConstants.PullType.Pull_Normal; int pullType = ProtoConstants.PullType.Pull_Normal;
if (type == ProtoConstants.ConversationType.ConversationType_Private) { if (type == ProtoConstants.ConversationType.ConversationType_Private) {
notifyReceivers.add(fromUser); if(message.getToCount() > 0) {
notifyReceivers.add(message.getConversation().getTarget()); if(message.getToList().contains(fromUser)) {
notifyReceivers.add(fromUser);
}
if(message.getToList().contains(message.getConversation().getTarget())) {
notifyReceivers.add(message.getConversation().getTarget());
}
} else {
notifyReceivers.add(fromUser);
notifyReceivers.add(message.getConversation().getTarget());
}
pullType = ProtoConstants.PullType.Pull_Normal; pullType = ProtoConstants.PullType.Pull_Normal;
} else if (type == ProtoConstants.ConversationType.ConversationType_Group) { } else if (type == ProtoConstants.ConversationType.ConversationType_Group) {
notifyReceivers.add(fromUser); notifyReceivers.add(fromUser);
@@ -1143,7 +1182,7 @@ public class MemoryMessagesStore implements IMessagesStore {
event.type = type; event.type = type;
m_Server.getCallbackScheduler().execute(() -> { m_Server.getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(mGroupInfoUpdateCallback, new Gson().toJson(event), HttpUtils.HttpPostType.POST_TYPE_Grout_Event_Callback); HttpUtils.httpJsonPost(mGroupInfoUpdateCallback, GsonUtil.gson.toJson(event), HttpUtils.HttpPostType.POST_TYPE_Grout_Event_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -1162,7 +1201,7 @@ public class MemoryMessagesStore implements IMessagesStore {
event.value = value; event.value = value;
m_Server.getCallbackScheduler().execute(() -> { m_Server.getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(mGroupMemberUpdateCallback, new Gson().toJson(event), HttpUtils.HttpPostType.POST_TYPE_Grout_Member_Event_Callback); HttpUtils.httpJsonPost(mGroupMemberUpdateCallback, GsonUtil.gson.toJson(event), HttpUtils.HttpPostType.POST_TYPE_Grout_Member_Event_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -1180,7 +1219,7 @@ public class MemoryMessagesStore implements IMessagesStore {
event.value = value; event.value = value;
m_Server.getCallbackScheduler().execute(() -> { m_Server.getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(mRelationUpdateCallback, new Gson().toJson(event), HttpUtils.HttpPostType.POST_TYPE_User_Relation_Event_Callback); HttpUtils.httpJsonPost(mRelationUpdateCallback, GsonUtil.gson.toJson(event), HttpUtils.HttpPostType.POST_TYPE_User_Relation_Event_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -1193,7 +1232,7 @@ public class MemoryMessagesStore implements IMessagesStore {
if (!StringUtil.isNullOrEmpty(mUserInfoUpdateCallback)) { if (!StringUtil.isNullOrEmpty(mUserInfoUpdateCallback)) {
m_Server.getCallbackScheduler().execute(() -> { m_Server.getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(mUserInfoUpdateCallback, new Gson().toJson(InputOutputUserInfo.fromPbUser(user)), HttpUtils.HttpPostType.POST_TYPE_User_Info_Event_Callback); HttpUtils.httpJsonPost(mUserInfoUpdateCallback, GsonUtil.gson.toJson(InputOutputUserInfo.fromPbUser(user)), HttpUtils.HttpPostType.POST_TYPE_User_Info_Event_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -1210,7 +1249,7 @@ public class MemoryMessagesStore implements IMessagesStore {
event.type = type; event.type = type;
m_Server.getCallbackScheduler().execute(() -> { m_Server.getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(mChannelInfoUpdateCallback, new Gson().toJson(event), HttpUtils.HttpPostType.POST_TYPE_Channel_Info_Event_Callback); HttpUtils.httpJsonPost(mChannelInfoUpdateCallback, GsonUtil.gson.toJson(event), HttpUtils.HttpPostType.POST_TYPE_Channel_Info_Event_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -1226,7 +1265,7 @@ public class MemoryMessagesStore implements IMessagesStore {
event.type = type; event.type = type;
m_Server.getCallbackScheduler().execute(() -> { m_Server.getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(mChatroomInfoUpdateCallback, new Gson().toJson(event), HttpUtils.HttpPostType.POST_TYPE_Chatroom_Info_Event_Callback); HttpUtils.httpJsonPost(mChatroomInfoUpdateCallback, GsonUtil.gson.toJson(event), HttpUtils.HttpPostType.POST_TYPE_Chatroom_Info_Event_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -1244,7 +1283,7 @@ public class MemoryMessagesStore implements IMessagesStore {
event.type = type; event.type = type;
m_Server.getCallbackScheduler().execute(() -> { m_Server.getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(mChatroomMemberUpdateCallback, new Gson().toJson(event), HttpUtils.HttpPostType.POST_TYPE_Chatroom_Member_Event_Callback); HttpUtils.httpJsonPost(mChatroomMemberUpdateCallback, GsonUtil.gson.toJson(event), HttpUtils.HttpPostType.POST_TYPE_Chatroom_Member_Event_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -1267,6 +1306,9 @@ public class MemoryMessagesStore implements IMessagesStore {
int maxCount = Integer.MAX_VALUE; int maxCount = Integer.MAX_VALUE;
if (!isAdmin) { if (!isAdmin) {
if (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
boolean isMember = false; boolean isMember = false;
boolean isManager = false; boolean isManager = false;
WFCMessage.GroupMember gm = getGroupMember(groupId, operator); WFCMessage.GroupMember gm = getGroupMember(groupId, operator);
@@ -1407,18 +1449,22 @@ public class MemoryMessagesStore implements IMessagesStore {
void removeFavGroup(String groupId, List<String> memberIds) { void removeFavGroup(String groupId, List<String> memberIds) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
databaseStore.removeFavGroup(groupId, memberIds); databaseStore.removeFavGroup(groupId, memberIds);
try {
for (String member : memberIds) { mUserSettingLock.lock();
userSettingMap.remove(member); MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
for (String member : memberIds) {
userSettingMap.remove(member);
}
} finally {
mUserSettingLock.unlock();
} }
} }
@Override @Override
public ErrorCode quitGroup(String operator, String groupId) { public ErrorCode quitGroup(String operator, String groupId, boolean admin) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
IMap<String, WFCMessage.GroupInfo> mIMap = hzInstance.getMap(GROUPS_MAP); IMap<String, WFCMessage.GroupInfo> mIMap = hzInstance.getMap(GROUPS_MAP);
@@ -1438,6 +1484,10 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_NOT_EXIST; return ErrorCode.ERROR_CODE_NOT_EXIST;
} }
if(!admin && groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if (groupInfo.getType() != ProtoConstants.GroupType.GroupType_Free && groupInfo.getOwner() != null && groupInfo.getOwner().equals(operator)) { if (groupInfo.getType() != ProtoConstants.GroupType.GroupType_Free && groupInfo.getOwner() != null && groupInfo.getOwner().equals(operator)) {
MultiMap<String, WFCMessage.GroupMember> groupMembers = hzInstance.getMultiMap(GROUP_MEMBERS); MultiMap<String, WFCMessage.GroupMember> groupMembers = hzInstance.getMultiMap(GROUP_MEMBERS);
Collection<WFCMessage.GroupMember> members = groupMembers.get(groupId); Collection<WFCMessage.GroupMember> members = groupMembers.get(groupId);
@@ -1480,7 +1530,7 @@ public class MemoryMessagesStore implements IMessagesStore {
int[] removedCount = new int[1]; int[] removedCount = new int[1];
for (String groupId : groupIds) { for (String groupId : groupIds) {
removedCount[0] = 0; removedCount[0] = 0;
ErrorCode errorCode = quitGroup(userId, groupId); ErrorCode errorCode = quitGroup(userId, groupId, true);
LOG.info("clear user {} group {} result {}", userId, groupId, errorCode); LOG.info("clear user {} group {} result {}", userId, groupId, errorCode);
} }
} }
@@ -1488,13 +1538,17 @@ public class MemoryMessagesStore implements IMessagesStore {
private void removeGroupUserSettings(String groupId, List<String> users) { private void removeGroupUserSettings(String groupId, List<String> users) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
databaseStore.removeGroupUserSettings(groupId, users); databaseStore.removeGroupUserSettings(groupId, users);
for (String userId:users) { try {
userSettingMap.remove(userId); mUserSettingLock.lock();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
for (String userId:users) {
userSettingMap.remove(userId);
}
} finally {
mUserSettingLock.unlock();
} }
} }
@Override @Override
@@ -1512,6 +1566,7 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
if (!isAdmin && (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Free || if (!isAdmin && (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Free ||
groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization ||
(groupInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted || groupInfo.getType() == ProtoConstants.GroupType.GroupType_Normal) (groupInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted || groupInfo.getType() == ProtoConstants.GroupType.GroupType_Normal)
&& (groupInfo.getOwner() == null || !groupInfo.getOwner().equals(operator)))) { && (groupInfo.getOwner() == null || !groupInfo.getOwner().equals(operator)))) {
return ErrorCode.ERROR_CODE_NOT_RIGHT; return ErrorCode.ERROR_CODE_NOT_RIGHT;
@@ -1555,7 +1610,11 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
if (!isAdmin) { if (!isAdmin) {
if (oldInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted) { if (oldInfo.getType() == ProtoConstants.GroupType.GroupType_Organization && modifyType != Modify_Group_Extra && modifyType != Modify_Group_Mute && modifyType != Modify_Group_PrivateChat) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
if (oldInfo.getType() == ProtoConstants.GroupType.GroupType_Restricted || oldInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
boolean isAllow = false; boolean isAllow = false;
if ((oldInfo.getOwner() != null && oldInfo.getOwner().equals(operator))) { if ((oldInfo.getOwner() != null && oldInfo.getOwner().equals(operator))) {
isAllow = true; isAllow = true;
@@ -1832,6 +1891,11 @@ public class MemoryMessagesStore implements IMessagesStore {
return databaseStore.getUserGroupIds(userId); return databaseStore.getUserGroupIds(userId);
} }
@Override
public Set<String> getCommonGroupIds(String userId1, String userId2) {
return databaseStore.getCommonGroupIds(userId1, userId2);
}
@Override @Override
public ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members) { public ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
@@ -1916,6 +1980,10 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_NOT_RIGHT; return ErrorCode.ERROR_CODE_NOT_RIGHT;
} }
if (!isAdmin && groupInfo.getType() == ProtoConstants.GroupType.GroupType_Organization) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
//check the new owner is in member list? is that necessary? //check the new owner is in member list? is that necessary?
long updateDt = System.currentTimeMillis(); long updateDt = System.currentTimeMillis();
groupInfo = groupInfo.toBuilder().setOwner(newOwner).setUpdateDt(updateDt).setMemberUpdateDt(updateDt).build(); groupInfo = groupInfo.toBuilder().setOwner(newOwner).setUpdateDt(updateDt).setMemberUpdateDt(updateDt).build();
@@ -2780,6 +2848,11 @@ public class MemoryMessagesStore implements IMessagesStore {
if (mDisableStrangerChat) { if (mDisableStrangerChat) {
//在禁止私聊时,是否是允许私聊的用户id
if(mAllowStrangerChatSet.contains(targetUser)) {
return ErrorCode.ERROR_CODE_SUCCESS;
}
//在禁止私聊时,允许机器人,物联网设备及管理员进行私聊。 //在禁止私聊时,允许机器人,物联网设备及管理员进行私聊。
IMap<String, WFCMessage.User> mUserMap = hzInstance.getMap(USERS); IMap<String, WFCMessage.User> mUserMap = hzInstance.getMap(USERS);
WFCMessage.User target = mUserMap.get(targetUser); WFCMessage.User target = mUserMap.get(targetUser);
@@ -3363,10 +3436,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) { if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) {
try { try {
URL url = new URL(robotData.getCallback()); URL url = new URL(robotData.getCallback());
if(url.getHost().equals(host)) { if(url.getHost() == null) {
LOG.error("get application auth code error, application callback is invalid url");
} else if(url.getHost().equals(host)) {
secret = robotData.getSecret(); secret = robotData.getSecret();
} else { } else {
LOG.warn("get application auth code error, request host is not the same host with callback host"); LOG.error("get application auth code error, request host is not the same host with callback host");
} }
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
@@ -3387,10 +3462,12 @@ public class MemoryMessagesStore implements IMessagesStore {
if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) { if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) {
try { try {
URL url = new URL(channelData.getCallback()); URL url = new URL(channelData.getCallback());
if(url.getHost().equals(host)) { if(url.getHost() == null) {
LOG.error("get application auth code error, application callback is invalid url");
} else if(url.getHost().equals(host)) {
secret = channelData.getSecret(); secret = channelData.getSecret();
} else { } else {
LOG.warn("get application auth code error, request host is not the same host with callback host"); LOG.error("get application auth code error, request host is not the same host with callback host");
} }
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
@@ -3496,11 +3573,6 @@ public class MemoryMessagesStore implements IMessagesStore {
return ErrorCode.ERROR_CODE_AUTH_FAILURE; return ErrorCode.ERROR_CODE_AUTH_FAILURE;
} }
@Override
public boolean isNewFriendWelcomeMessage() {
return mFriendNewWelcomeMessage;
}
@Override @Override
public ErrorCode deleteFriend(String userId, String friendUid, long[] head) { public ErrorCode deleteFriend(String userId, String friendUid, long[] head) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
@@ -3550,9 +3622,15 @@ public class MemoryMessagesStore implements IMessagesStore {
IMap<String, WFCMessage.GroupInfo> mGroups = hzInstance.getMap(GROUPS_MAP); IMap<String, WFCMessage.GroupInfo> mGroups = hzInstance.getMap(GROUPS_MAP);
IMap<String, WFCMessage.ChannelInfo> mChannels = hzInstance.getMap(CHANNELS); IMap<String, WFCMessage.ChannelInfo> mChannels = hzInstance.getMap(CHANNELS);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId); Collection<WFCMessage.UserSettingEntry> entries;
if (entries == null || entries.size() == 0) { try {
entries = loadPersistedUserSettings(userId, userSettingMap); mUserSettingLock.lock();
entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
}
} finally {
mUserSettingLock.unlock();
} }
ErrorCode ec = ErrorCode.ERROR_CODE_NOT_MODIFIED; ErrorCode ec = ErrorCode.ERROR_CODE_NOT_MODIFIED;
@@ -3631,17 +3709,22 @@ public class MemoryMessagesStore implements IMessagesStore {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING); MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId); try {
if (entries == null || entries.size() == 0) { mUserSettingLock.lock();
entries = loadPersistedUserSettings(userId, userSettingMap); Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
} if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
}
if (entries != null) { if (entries != null) {
for (WFCMessage.UserSettingEntry entry : entries) { for (WFCMessage.UserSettingEntry entry : entries) {
if (entry.getScope() == scope && (key== null || key.equals(entry.getKey()))) { if (entry.getScope() == scope && (key== null || key.equals(entry.getKey()))) {
return entry; return entry;
}
} }
} }
} finally {
mUserSettingLock.unlock();
} }
return null; return null;
@@ -3652,19 +3735,25 @@ public class MemoryMessagesStore implements IMessagesStore {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING); MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId); List<WFCMessage.UserSettingEntry> result;
if (entries == null || entries.size() == 0) { try {
entries = loadPersistedUserSettings(userId, userSettingMap); mUserSettingLock.lock();
} Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
}
List<WFCMessage.UserSettingEntry> result = new ArrayList<>(); result = new ArrayList<>();
if (entries != null) { if (entries != null) {
for (WFCMessage.UserSettingEntry entry : entries for (WFCMessage.UserSettingEntry entry : entries
) { ) {
if (entry.getScope() == scope) { if (entry.getScope() == scope) {
result.add(entry); result.add(entry);
}
} }
} }
} finally {
mUserSettingLock.unlock();
} }
return result; return result;
@@ -3675,28 +3764,34 @@ public class MemoryMessagesStore implements IMessagesStore {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING); MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId); long updateDt = 0;
if (entries == null || entries.size() == 0) { try {
entries = loadPersistedUserSettings(userId, userSettingMap); mUserSettingLock.lock();
if (entries == null) { Collection<WFCMessage.UserSettingEntry> entries = userSettingMap.get(userId);
entries = new ArrayList<>(); if (entries == null || entries.size() == 0) {
entries = loadPersistedUserSettings(userId, userSettingMap);
if (entries == null) {
entries = new ArrayList<>();
}
} }
}
long updateDt = System.currentTimeMillis(); updateDt = System.currentTimeMillis();
WFCMessage.UserSettingEntry settingEntry = WFCMessage.UserSettingEntry.newBuilder().setScope(request.getScope()).setKey(request.getKey()).setValue(request.getValue()).setUpdateDt(updateDt).build(); WFCMessage.UserSettingEntry settingEntry = WFCMessage.UserSettingEntry.newBuilder().setScope(request.getScope()).setKey(request.getKey()).setValue(request.getValue()).setUpdateDt(updateDt).build();
databaseStore.persistUserSetting(userId, settingEntry); databaseStore.persistUserSetting(userId, settingEntry);
for (WFCMessage.UserSettingEntry entry : entries for (WFCMessage.UserSettingEntry entry : entries
) { ) {
if (entry.getScope() == request.getScope() && entry.getKey().equals(request.getKey())) { if (entry.getScope() == request.getScope() && entry.getKey().equals(request.getKey())) {
userSettingMap.remove(userId, entry); userSettingMap.remove(userId, entry);
userSettingMap.put(userId, settingEntry); userSettingMap.put(userId, settingEntry);
break; break;
}
} }
}
userSettingMap.put(userId, settingEntry); userSettingMap.put(userId, settingEntry);
} finally {
mUserSettingLock.unlock();
}
if(request.getScope() == UserSettingScope.kUserSettingConversationSilent) { if(request.getScope() == UserSettingScope.kUserSettingConversationSilent) {
int firstSplit = request.getKey().indexOf("-"); int firstSplit = request.getKey().indexOf("-");
@@ -3721,8 +3816,14 @@ public class MemoryMessagesStore implements IMessagesStore {
public void clearUserSettings(String userId) { public void clearUserSettings(String userId) {
databaseStore.clearUserSetting(userId); databaseStore.clearUserSetting(userId);
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
userSettingMap.remove(userId); try {
mUserSettingLock.lock();
MultiMap<String, WFCMessage.UserSettingEntry> userSettingMap = hzInstance.getMultiMap(USER_SETTING);
userSettingMap.remove(userId);
} finally {
mUserSettingLock.unlock();
}
} }
@Override @Override
@@ -3889,7 +3990,7 @@ public class MemoryMessagesStore implements IMessagesStore {
Utility.printExecption(LOG, e); Utility.printExecption(LOG, e);
} }
} else if(modifyType == Modify_Channel_Menu) { } else if(modifyType == Modify_Channel_Menu) {
OutputGetChannelInfo.OutputMenuList outputMenuButtons = new Gson().fromJson(value, OutputGetChannelInfo.OutputMenuList.class); OutputGetChannelInfo.OutputMenuList outputMenuButtons = GsonUtil.gson.fromJson(value, OutputGetChannelInfo.OutputMenuList.class);
if (!outputMenuButtons.isEmpty()) { if (!outputMenuButtons.isEmpty()) {
for (OutputGetChannelInfo.OutputMenu outputMenuButton : outputMenuButtons) { for (OutputGetChannelInfo.OutputMenu outputMenuButton : outputMenuButtons) {
newInfoBuilder.addMenu(outputMenuButton.toPbInfo()); newInfoBuilder.addMenu(outputMenuButton.toPbInfo());
@@ -4253,6 +4354,34 @@ public class MemoryMessagesStore implements IMessagesStore {
return UUIDGenerator.getUUID(); return UUIDGenerator.getUUID();
} }
@Override
public boolean checkSignature(String signature) {
if(mClientSignatureSet.isEmpty()) {
return true;
}
if(StringUtil.isNullOrEmptyAfterTrim(signature)) {
if(mRejectEmptySignature) {
LOG.info("Failed check signature, no signature");
return false;
} else {
return true;
}
} else {
if(mClientSignatureSet.contains(signature)) {
return true;
} else {
LOG.info("Failed check signature, signature no matched");
return false;
}
}
}
@Override
public boolean existSignatures() {
return !mClientSignatureSet.isEmpty();
}
@Override @Override
public void storeRetained(Topic topic, StoredMessage storedMessage) { public void storeRetained(Topic topic, StoredMessage storedMessage) {
LOG.debug("Store retained message for topic={}, CId={}", topic, storedMessage.getClientID()); LOG.debug("Store retained message for topic={}, CId={}", topic, storedMessage.getClientID());
@@ -415,7 +415,7 @@ public class MemorySessionStore implements ISessionsStore {
remove = true; remove = true;
} }
} else if(platform == ProtoConstants.Platform.Platform_OSX || platform == ProtoConstants.Platform.Platform_Windows || platform == ProtoConstants.Platform.Platform_LINUX) { } else if(platform == ProtoConstants.Platform.Platform_OSX || platform == ProtoConstants.Platform.Platform_Windows || platform == ProtoConstants.Platform.Platform_LINUX) {
if (s.getPlatform() == ProtoConstants.Platform.Platform_OSX || s.getPlatform() == ProtoConstants.Platform.Platform_Windows || platform == ProtoConstants.Platform.Platform_LINUX) { if (s.getPlatform() == ProtoConstants.Platform.Platform_OSX || s.getPlatform() == ProtoConstants.Platform.Platform_Windows || s.getPlatform() == ProtoConstants.Platform.Platform_LINUX) {
remove = true; remove = true;
} }
} else { } else {
@@ -378,7 +378,7 @@ public class Server {
LOG.info("Configuring embedded Hazelcast instance"); LOG.info("Configuring embedded Hazelcast instance");
serverIp = getServerIp(config); serverIp = getServerIp(config);
String hzConfigPath = "config/hazelcast.xml"; String hzConfigPath = System.getProperty("hazelcast.configuration", "config/hazelcast.xml");
if (hzConfigPath != null) { if (hzConfigPath != null) {
boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null; boolean isHzConfigOnClasspath = this.getClass().getClassLoader().getResource(hzConfigPath) != null;
Config hzconfig = isHzConfigOnClasspath Config hzconfig = isHzConfigOnClasspath
@@ -110,7 +110,7 @@ public interface IMessagesStore {
WFCMessage.GroupInfo createGroup(String operator, WFCMessage.GroupInfo groupInfo, List<WFCMessage.GroupMember> memberList, String memberExtra, boolean isAdmin); WFCMessage.GroupInfo createGroup(String operator, WFCMessage.GroupInfo groupInfo, List<WFCMessage.GroupMember> memberList, String memberExtra, boolean isAdmin);
ErrorCode addGroupMembers(String operator, boolean isAdmin, String groupId, List<WFCMessage.GroupMember> memberList, String extra); ErrorCode addGroupMembers(String operator, boolean isAdmin, String groupId, List<WFCMessage.GroupMember> memberList, String extra);
ErrorCode kickoffGroupMembers(String operator, boolean isAdmin, String groupId, List<String> memberList); ErrorCode kickoffGroupMembers(String operator, boolean isAdmin, String groupId, List<String> memberList);
ErrorCode quitGroup(String operator, String groupId); ErrorCode quitGroup(String operator, String groupId, boolean admin);
void clearUserGroups(String userId); void clearUserGroups(String userId);
ErrorCode dismissGroup(String operator, String groupId, boolean isAdmin); ErrorCode dismissGroup(String operator, String groupId, boolean isAdmin);
ErrorCode modifyGroupInfo(String operator, String groupId, int modifyType, String value, boolean isAdmin); ErrorCode modifyGroupInfo(String operator, String groupId, int modifyType, String value, boolean isAdmin);
@@ -119,6 +119,7 @@ public interface IMessagesStore {
List<WFCMessage.GroupInfo> getGroupInfos(List<WFCMessage.UserRequest> requests); List<WFCMessage.GroupInfo> getGroupInfos(List<WFCMessage.UserRequest> requests);
WFCMessage.GroupInfo getGroupInfo(String groupId); WFCMessage.GroupInfo getGroupInfo(String groupId);
Set<String> getUserGroupIds(String userId); Set<String> getUserGroupIds(String userId);
Set<String> getCommonGroupIds(String userId1, String userId2);
ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members); ErrorCode getGroupMembers(String fromUser, String groupId, long maxDt, List<WFCMessage.GroupMember> members);
WFCMessage.GroupMember getGroupMember(String groupId, String memberId); WFCMessage.GroupMember getGroupMember(String groupId, String memberId);
ErrorCode transferGroup(String operator, String groupId, String newOwner, boolean isAdmin); ErrorCode transferGroup(String operator, String groupId, String newOwner, boolean isAdmin);
@@ -179,7 +180,6 @@ public interface IMessagesStore {
ErrorCode saveAddFriendRequest(String userId, WFCMessage.AddFriendRequest request, long[] head, boolean isAdmin); ErrorCode saveAddFriendRequest(String userId, WFCMessage.AddFriendRequest request, long[] head, boolean isAdmin);
ErrorCode handleFriendRequest(String userId, WFCMessage.HandleFriendRequest request, WFCMessage.Message.Builder msgBuilder, long[] heads, boolean isAdmin); ErrorCode handleFriendRequest(String userId, WFCMessage.HandleFriendRequest request, WFCMessage.Message.Builder msgBuilder, long[] heads, boolean isAdmin);
boolean isNewFriendWelcomeMessage();
ErrorCode deleteFriend(String userId, String friendUid, long[] head); ErrorCode deleteFriend(String userId, String friendUid, long[] head);
ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head); ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head);
FriendData getFriendData(String fromUser, String targetUserId); FriendData getFriendData(String fromUser, String targetUserId);
@@ -249,6 +249,9 @@ public interface IMessagesStore {
//使用了数据库,会比较慢,仅能用户用户/群组等id的生成 //使用了数据库,会比较慢,仅能用户用户/群组等id的生成
String getShortUUID(); String getShortUUID();
boolean checkSignature(String signature);
boolean existSignatures();
/** /**
* Used to initialize all persistent store structures * Used to initialize all persistent store structures
*/ */
@@ -35,6 +35,7 @@ import io.moquette.spi.ISessionsStore;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.handler.codec.mqtt.*; import io.netty.handler.codec.mqtt.*;
import win.liyufan.im.GsonUtil;
import win.liyufan.im.HttpUtils; import win.liyufan.im.HttpUtils;
import win.liyufan.im.IMTopic; import win.liyufan.im.IMTopic;
@@ -188,7 +189,7 @@ public class MessagesPublisher {
OutputClient finalOutputClient = outputClient; OutputClient finalOutputClient = outputClient;
Server.getServer().getCallbackScheduler().execute(() -> { Server.getServer().getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(robot.getCallback(), new Gson().toJson(OutputMessageData.fromProtoMessage(finalMsg, finalOutputClient), OutputMessageData.class), HttpUtils.HttpPostType.POST_TYPE_Robot_Message_Callback); HttpUtils.httpJsonPost(robot.getCallback(), GsonUtil.gson.toJson(OutputMessageData.fromProtoMessage(finalMsg, finalOutputClient), OutputMessageData.class), HttpUtils.HttpPostType.POST_TYPE_Robot_Message_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -616,7 +617,7 @@ public class MessagesPublisher {
OutputClient finalOutputClient = outputClient; OutputClient finalOutputClient = outputClient;
Server.getServer().getCallbackScheduler().execute(() -> { Server.getServer().getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(channelInfo.getCallback() + "/message", new Gson().toJson(OutputMessageData.fromProtoMessage(message, finalOutputClient), OutputMessageData.class), HttpUtils.HttpPostType.POST_TYPE_Channel_Message_Callback); HttpUtils.httpJsonPost(channelInfo.getCallback() + "/message", GsonUtil.gson.toJson(OutputMessageData.fromProtoMessage(message, finalOutputClient), OutputMessageData.class), HttpUtils.HttpPostType.POST_TYPE_Channel_Message_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -651,6 +652,8 @@ public class MessagesPublisher {
pushContent = "[文件]"; pushContent = "[文件]";
} else if(type == ProtoConstants.ContentType.Sticker) { } else if(type == ProtoConstants.ContentType.Sticker) {
pushContent = "[表情]"; pushContent = "[表情]";
} else if (type == ProtoConstants.ContentType.Name_Card) {
pushContent = "[名片]";
} }
} }
@@ -669,7 +672,7 @@ public class MessagesPublisher {
public void forwardMessage(final WFCMessage.Message message, String forwardUrl, OutputClient outputClient) { public void forwardMessage(final WFCMessage.Message message, String forwardUrl, OutputClient outputClient) {
Server.getServer().getCallbackScheduler().execute(() -> { Server.getServer().getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(forwardUrl, new Gson().toJson(OutputMessageData.fromProtoMessage(message, outputClient), OutputMessageData.class), HttpUtils.HttpPostType.POST_TYPE_Forward_Message_Callback); HttpUtils.httpJsonPost(forwardUrl, GsonUtil.gson.toJson(OutputMessageData.fromProtoMessage(message, outputClient), OutputMessageData.class), HttpUtils.HttpPostType.POST_TYPE_Forward_Message_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -679,7 +682,7 @@ public class MessagesPublisher {
public void forwardMessageWithCallback(final WFCMessage.Message message, String forwardUrl, HttpUtils.HttpCallback callback) { public void forwardMessageWithCallback(final WFCMessage.Message message, String forwardUrl, HttpUtils.HttpCallback callback) {
try { try {
HttpUtils.httpJsonPost(forwardUrl, new Gson().toJson(OutputMessageData.fromProtoMessage(message), OutputMessageData.class), new HttpUtils.HttpCallback() { HttpUtils.httpJsonPost(forwardUrl, GsonUtil.gson.toJson(OutputMessageData.fromProtoMessage(message), OutputMessageData.class), new HttpUtils.HttpCallback() {
@Override @Override
public void onSuccess(String content) { public void onSuccess(String content) {
Server.getServer().getImBusinessScheduler().execute(()->{ Server.getServer().getImBusinessScheduler().execute(()->{
@@ -707,7 +710,7 @@ public class MessagesPublisher {
Server.getServer().getCallbackScheduler().execute(() -> { Server.getServer().getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(channelInfo.getCallback() + "/subscribe", new Gson().toJson(new OutputNotifyChannelSubscribeStatus(user, channelInfo.getTargetId(), listen), OutputNotifyChannelSubscribeStatus.class), HttpUtils.HttpPostType.POST_TYPE_Channel_Subscriber_Event_Callback); HttpUtils.httpJsonPost(channelInfo.getCallback() + "/subscribe", GsonUtil.gson.toJson(new OutputNotifyChannelSubscribeStatus(user, channelInfo.getTargetId(), listen), OutputNotifyChannelSubscribeStatus.class), HttpUtils.HttpPostType.POST_TYPE_Channel_Subscriber_Event_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -43,10 +43,12 @@ import io.netty.handler.codec.mqtt.*;
import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import win.liyufan.im.GsonUtil;
import win.liyufan.im.HttpUtils; import win.liyufan.im.HttpUtils;
import win.liyufan.im.IMTopic; import win.liyufan.im.IMTopic;
import win.liyufan.im.Utility; import win.liyufan.im.Utility;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@@ -264,7 +266,7 @@ public class ProtocolProcessor {
if (!StringUtil.isNullOrEmpty(mUserOnlineStatusCallback)) { if (!StringUtil.isNullOrEmpty(mUserOnlineStatusCallback)) {
mServer.getCallbackScheduler().execute(() -> { mServer.getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(mUserOnlineStatusCallback, new Gson().toJson(new UserOnlineStatus(userId, clientId, platform, status, packageName)), HttpUtils.HttpPostType.POST_TYPE_User_Online_Event_Callback); HttpUtils.httpJsonPost(mUserOnlineStatusCallback, GsonUtil.gson.toJson(new UserOnlineStatus(userId, clientId, platform, status, packageName)), HttpUtils.HttpPostType.POST_TYPE_User_Online_Event_Callback);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception); Utility.printExecption(LOG, e, EVENT_CALLBACK_Exception);
@@ -307,7 +309,7 @@ public class ProtocolProcessor {
} }
byte[] pwd = null; byte[] pwd = null;
if (msg.variableHeader().hasPassword()) { if (msg.variableHeader().hasPassword()) {
pwd = msg.payload().password(); pwd = msg.payload().passwordInBytes();
MemorySessionStore.Session session = m_sessionsStore.getSession(clientId); MemorySessionStore.Session session = m_sessionsStore.getSession(clientId);
if (session == null) { if (session == null) {
@@ -338,6 +340,24 @@ public class ProtocolProcessor {
failedCredentials(channel); failedCredentials(channel);
return false; return false;
} }
if(session.getPlatform() == ProtoConstants.Platform.Platform_Android || session.getPlatform() == ProtoConstants.Platform.Platform_APad) {
byte[] signature = msg.payload().signatureInBytes();
String basedSign = null;
if (signature != null && signature.length > 0) {
signature = AES.AESDecrypt(signature, session.getSecret(), true);
}
if (signature != null && signature.length > 0) {
basedSign = Base64.getEncoder().encodeToString(signature);
}
if (!m_messagesStore.checkSignature(basedSign)) {
LOG.error("Bad signature of session <{}, {}>", session.getUsername(), session.getClientID());
failedBadSignature(channel);
return false;
}
}
session.setMqttVersion(mqttVersion); session.setMqttVersion(mqttVersion);
} else { } else {
LOG.error("Client didn't supply any password and MQTT anonymous mode is disabled CId={}", clientId); LOG.error("Client didn't supply any password and MQTT anonymous mode is disabled CId={}", clientId);
@@ -445,6 +465,11 @@ public class ProtocolProcessor {
LOG.info("Client {} failed to connect with bad username or password.", session); LOG.info("Client {} failed to connect with bad username or password.", session);
} }
private void failedBadSignature(Channel session) {
session.writeAndFlush(connAck(CONNECTION_REFUSED_SIGNATURE_FAILURE));
LOG.info("Client {} failed to connect with bad signature.", session);
}
private void setIdleTime(ChannelPipeline pipeline, int idleTime) { private void setIdleTime(ChannelPipeline pipeline, int idleTime) {
if (pipeline.names().contains("idleStateHandler")) { if (pipeline.names().contains("idleStateHandler")) {
pipeline.remove("idleStateHandler"); pipeline.remove("idleStateHandler");
@@ -57,6 +57,7 @@ import io.netty.handler.codec.mqtt.MqttFixedHeader;
import io.netty.handler.codec.mqtt.MqttMessageType; import io.netty.handler.codec.mqtt.MqttMessageType;
import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage;
import cn.wildfirechat.common.ErrorCode; import cn.wildfirechat.common.ErrorCode;
import win.liyufan.im.GsonUtil;
import win.liyufan.im.IMTopic; import win.liyufan.im.IMTopic;
import win.liyufan.im.RateLimiter; import win.liyufan.im.RateLimiter;
import win.liyufan.im.Utility; import win.liyufan.im.Utility;
@@ -142,7 +143,7 @@ public class Qos1PublishHandler extends QosPublishHandler {
out.addSession(userId, session.getClientID(), session.getPlatform(), status, session.getLastActiveTime()); out.addSession(userId, session.getClientID(), session.getPlatform(), status, session.getLastActiveTime());
} }
callback.onRouteHandled(new Gson().toJson(out).getBytes()); callback.onRouteHandled(GsonUtil.gson.toJson(out).getBytes());
}); });
} }
@@ -163,7 +164,7 @@ public class Qos1PublishHandler extends QosPublishHandler {
} else { } else {
MemorySessionStore.Session session = m_sessionStore.getSession(clientID); MemorySessionStore.Session session = m_sessionStore.getSession(clientID);
if (session != null && session.getUsername().equals(fromUser)) { if (session != null && session.getUsername().equals(fromUser)) {
if (data.length > 7*1024 && session.getMqttVersion().protocolLevel() >= MqttVersion.Wildfire_1.protocolLevel()) { if (data.length > 7*1024 && session.getMqttVersion().protocolLevel() >= MqttVersion.MQTT_5.protocolLevel()) {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = null; GZIPOutputStream gzip = null;
try { try {
+32 -9
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,13 +16,30 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.DecoderException;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
final class MqttCodecUtil { final class MqttCodecUtil {
private static final char[] TOPIC_WILDCARDS = {'#', '+'}; private static final char[] TOPIC_WILDCARDS = {'#', '+'};
private static final int MIN_CLIENT_ID_LENGTH = 1;
private static final int MAX_CLIENT_ID_LENGTH = 23; static final AttributeKey<MqttVersion> MQTT_VERSION_KEY = AttributeKey.valueOf("NETTY_CODEC_MQTT_VERSION");
static MqttVersion getMqttVersion(ChannelHandlerContext ctx) {
Attribute<MqttVersion> attr = ctx.channel().attr(MQTT_VERSION_KEY);
MqttVersion version = attr.get();
if (version == null) {
return MqttVersion.MQTT_3_1_1;
}
return version;
}
static void setMqttVersion(ChannelHandlerContext ctx, MqttVersion version) {
Attribute<MqttVersion> attr = ctx.channel().attr(MQTT_VERSION_KEY);
attr.set(version);
}
static boolean isValidPublishTopicName(String topicName) { static boolean isValidPublishTopicName(String topicName) {
// publish topic name must not contain any wildcard // publish topic name must not contain any wildcard
@@ -38,20 +55,20 @@ final class MqttCodecUtil {
return messageId != 0; return messageId != 0;
} }
static boolean isValidClientId(MqttVersion mqttVersion, String clientId) { static boolean isValidClientId(MqttVersion mqttVersion, int maxClientIdLength, String clientId) {
if (mqttVersion == MqttVersion.MQTT_3_1) { if (mqttVersion == MqttVersion.MQTT_3_1) {
return clientId != null && clientId.length() >= MIN_CLIENT_ID_LENGTH && return clientId != null && clientId.length() >= MqttConstant.MIN_CLIENT_ID_LENGTH &&
clientId.length() <= MAX_CLIENT_ID_LENGTH; clientId.length() <= maxClientIdLength;
} }
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) { if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) {
// In 3.1.3.1 Client Identifier of MQTT 3.1.1 specification, The Server MAY allow ClientId’s // In 3.1.3.1 Client Identifier of MQTT 3.1.1 and 5.0 specifications, The Server MAY allow ClientId’s
// that contain more than 23 encoded bytes. And, The Server MAY allow zero-length ClientId. // that contain more than 23 encoded bytes. And, The Server MAY allow zero-length ClientId.
return clientId != null; return clientId != null;
} }
throw new IllegalArgumentException(mqttVersion + " is unknown mqtt version"); throw new IllegalArgumentException(mqttVersion + " is unknown mqtt version");
} }
static MqttFixedHeader validateFixedHeader(MqttFixedHeader mqttFixedHeader) { static MqttFixedHeader validateFixedHeader(ChannelHandlerContext ctx, MqttFixedHeader mqttFixedHeader) {
switch (mqttFixedHeader.messageType()) { switch (mqttFixedHeader.messageType()) {
case PUBREL: case PUBREL:
case SUBSCRIBE: case SUBSCRIBE:
@@ -59,6 +76,12 @@ final class MqttCodecUtil {
if (mqttFixedHeader.qosLevel() != MqttQoS.AT_LEAST_ONCE) { if (mqttFixedHeader.qosLevel() != MqttQoS.AT_LEAST_ONCE) {
throw new DecoderException(mqttFixedHeader.messageType().name() + " message must have QoS 1"); throw new DecoderException(mqttFixedHeader.messageType().name() + " message must have QoS 1");
} }
return mqttFixedHeader;
case AUTH:
if (MqttCodecUtil.getMqttVersion(ctx) != MqttVersion.MQTT_5) {
throw new DecoderException("AUTH message requires at least MQTT 5");
}
return mqttFixedHeader;
default: default:
return mqttFixedHeader; return mqttFixedHeader;
} }
@@ -75,7 +98,7 @@ final class MqttCodecUtil {
case UNSUBACK: case UNSUBACK:
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
//case DISCONNECT: case DISCONNECT:
if (mqttFixedHeader.isDup() || if (mqttFixedHeader.isDup() ||
mqttFixedHeader.qosLevel() != MqttQoS.AT_MOST_ONCE || mqttFixedHeader.qosLevel() != MqttQoS.AT_MOST_ONCE ||
mqttFixedHeader.isRetain()) { mqttFixedHeader.isRetain()) {
+3 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connack">MQTTV3.1/connack</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connack">MQTTV3.1/connack</a>
*/ */
public final class MqttConnAckMessage extends MqttMessage { public final class MqttConnAckMessage extends MqttMessage {
@@ -28,7 +28,7 @@ public final class MqttConnAckMessage extends MqttMessage {
public MqttConnAckMessage(MqttFixedHeader mqttFixedHeader, MqttConnAckVariableHeader variableHeader, MqttConnectAckPayload payload) { public MqttConnAckMessage(MqttFixedHeader mqttFixedHeader, MqttConnAckVariableHeader variableHeader, MqttConnectAckPayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
@Override @Override
public MqttConnAckVariableHeader variableHeader() { public MqttConnAckVariableHeader variableHeader() {
return (MqttConnAckVariableHeader) super.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 * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -27,9 +27,17 @@ public final class MqttConnAckVariableHeader {
private final boolean sessionPresent; private final boolean sessionPresent;
private final MqttProperties properties;
public MqttConnAckVariableHeader(MqttConnectReturnCode connectReturnCode, boolean sessionPresent) { public MqttConnAckVariableHeader(MqttConnectReturnCode connectReturnCode, boolean sessionPresent) {
this(connectReturnCode, sessionPresent, MqttProperties.NO_PROPERTIES);
}
public MqttConnAckVariableHeader(MqttConnectReturnCode connectReturnCode, boolean sessionPresent,
MqttProperties properties) {
this.connectReturnCode = connectReturnCode; this.connectReturnCode = connectReturnCode;
this.sessionPresent = sessionPresent; this.sessionPresent = sessionPresent;
this.properties = MqttProperties.withEmptyDefaults(properties);
} }
public MqttConnectReturnCode connectReturnCode() { public MqttConnectReturnCode connectReturnCode() {
@@ -40,6 +48,10 @@ public final class MqttConnAckVariableHeader {
return sessionPresent; return sessionPresent;
} }
public MqttProperties properties() {
return properties;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
+2 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connect">MQTTV3.1/connect</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connect">MQTTV3.1/connect</a>
*/ */
public final class MqttConnectMessage extends MqttMessage { public final class MqttConnectMessage extends MqttMessage {
+72 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,6 +16,9 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import java.util.Arrays;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
/** /**
@@ -24,33 +27,85 @@ import io.netty.util.internal.StringUtil;
public final class MqttConnectPayload { public final class MqttConnectPayload {
private final String clientIdentifier; private final String clientIdentifier;
private final MqttProperties willProperties;
private final String willTopic; private final String willTopic;
private final String willMessage; private final byte[] willMessage;
private final String userName; private final String userName;
private final byte[] password; private final byte[] password;
private final byte[] signature;
/**
* @deprecated use {@link MqttConnectPayload#MqttConnectPayload(String,
* MqttProperties, String, byte[], String, byte[])} instead
*/
@Deprecated
public MqttConnectPayload( public MqttConnectPayload(
String clientIdentifier, String clientIdentifier,
String willTopic, String willTopic,
String willMessage, String willMessage,
String userName, String userName,
String password) {
this(
clientIdentifier,
MqttProperties.NO_PROPERTIES,
willTopic,
willMessage.getBytes(CharsetUtil.UTF_8),
userName,
password.getBytes(CharsetUtil.UTF_8), null);
}
public MqttConnectPayload(
String clientIdentifier,
String willTopic,
byte[] willMessage,
String userName,
byte[] password) { byte[] password) {
this(clientIdentifier,
MqttProperties.NO_PROPERTIES,
willTopic,
willMessage,
userName,
password, null);
}
public MqttConnectPayload(
String clientIdentifier,
MqttProperties willProperties,
String willTopic,
byte[] willMessage,
String userName,
byte[] password,
byte[] signature) {
this.clientIdentifier = clientIdentifier; this.clientIdentifier = clientIdentifier;
this.willProperties = MqttProperties.withEmptyDefaults(willProperties);
this.willTopic = willTopic; this.willTopic = willTopic;
this.willMessage = willMessage; this.willMessage = willMessage;
this.userName = userName; this.userName = userName;
this.password = password; this.password = password;
this.signature = signature;
} }
public String clientIdentifier() { public String clientIdentifier() {
return clientIdentifier; return clientIdentifier;
} }
public MqttProperties willProperties() {
return willProperties;
}
public String willTopic() { public String willTopic() {
return willTopic; return willTopic;
} }
/**
* @deprecated use {@link MqttConnectPayload#willMessageInBytes()} instead
*/
@Deprecated
public String willMessage() { public String willMessage() {
return willMessage == null ? null : new String(willMessage, CharsetUtil.UTF_8);
}
public byte[] willMessageInBytes() {
return willMessage; return willMessage;
} }
@@ -58,19 +113,31 @@ public final class MqttConnectPayload {
return userName; return userName;
} }
public byte[] password() { /**
* @deprecated use {@link MqttConnectPayload#passwordInBytes()} instead
*/
@Deprecated
public String password() {
return password == null ? null : new String(password, CharsetUtil.UTF_8);
}
public byte[] passwordInBytes() {
return password; return password;
} }
public byte[] signatureInBytes() {
return signature;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
.append('[') .append('[')
.append("clientIdentifier=").append(clientIdentifier) .append("clientIdentifier=").append(clientIdentifier)
.append(", willTopic=").append(willTopic) .append(", willTopic=").append(willTopic)
.append(", willMessage=").append(willMessage) .append(", willMessage=").append(Arrays.toString(willMessage))
.append(", userName=").append(userName) .append(", userName=").append(userName)
.append(", password=").append(password) .append(", password=").append(Arrays.toString(password))
.append(']') .append(']')
.toString(); .toString();
} }
+43 -14
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,31 +16,53 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/** /**
* Return Code of {@link MqttConnAckMessage} * Return Code of {@link MqttConnAckMessage}
*/ */
public enum MqttConnectReturnCode { public enum MqttConnectReturnCode {
CONNECTION_ACCEPTED((byte) 0x00), CONNECTION_ACCEPTED((byte) 0x00),
//MQTT 3 codes
CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION((byte) 0X01), CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION((byte) 0X01),
CONNECTION_REFUSED_IDENTIFIER_REJECTED((byte) 0x02), CONNECTION_REFUSED_IDENTIFIER_REJECTED((byte) 0x02),
CONNECTION_REFUSED_SERVER_UNAVAILABLE((byte) 0x03), CONNECTION_REFUSED_SERVER_UNAVAILABLE((byte) 0x03),
CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD((byte) 0x04), CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD((byte) 0x04),
CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05), CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05),
CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06), CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06),
CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07); CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07),
CONNECTION_REFUSED_SIGNATURE_FAILURE((byte) 0x08),
//MQTT 5 codes
CONNECTION_REFUSED_UNSPECIFIED_ERROR((byte) 0x80),
CONNECTION_REFUSED_MALFORMED_PACKET((byte) 0x81),
CONNECTION_REFUSED_PROTOCOL_ERROR((byte) 0x82),
CONNECTION_REFUSED_IMPLEMENTATION_SPECIFIC((byte) 0x83),
CONNECTION_REFUSED_UNSUPPORTED_PROTOCOL_VERSION((byte) 0x84),
CONNECTION_REFUSED_CLIENT_IDENTIFIER_NOT_VALID((byte) 0x85),
CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD((byte) 0x86),
CONNECTION_REFUSED_NOT_AUTHORIZED_5((byte) 0x87),
CONNECTION_REFUSED_SERVER_UNAVAILABLE_5((byte) 0x88),
CONNECTION_REFUSED_SERVER_BUSY((byte) 0x89),
CONNECTION_REFUSED_BANNED((byte) 0x8A),
CONNECTION_REFUSED_BAD_AUTHENTICATION_METHOD((byte) 0x8C),
CONNECTION_REFUSED_TOPIC_NAME_INVALID((byte) 0x90),
CONNECTION_REFUSED_PACKET_TOO_LARGE((byte) 0x95),
CONNECTION_REFUSED_QUOTA_EXCEEDED((byte) 0x97),
CONNECTION_REFUSED_PAYLOAD_FORMAT_INVALID((byte) 0x99),
CONNECTION_REFUSED_RETAIN_NOT_SUPPORTED((byte) 0x9A),
CONNECTION_REFUSED_QOS_NOT_SUPPORTED((byte) 0x9B),
CONNECTION_REFUSED_USE_ANOTHER_SERVER((byte) 0x9C),
CONNECTION_REFUSED_SERVER_MOVED((byte) 0x9D),
CONNECTION_REFUSED_CONNECTION_RATE_EXCEEDED((byte) 0x9F);
private static final Map<Byte, MqttConnectReturnCode> VALUE_TO_CODE_MAP; private static final MqttConnectReturnCode[] VALUES;
static { static {
final Map<Byte, MqttConnectReturnCode> valueMap = new HashMap<Byte, MqttConnectReturnCode>(); MqttConnectReturnCode[] values = values();
for (MqttConnectReturnCode code: values()) { VALUES = new MqttConnectReturnCode[160];
valueMap.put(code.byteValue, code); for (MqttConnectReturnCode code : values) {
final int unsignedByte = code.byteValue & 0xFF;
// Suppress a warning about out of bounds access since the enum contains only correct values
VALUES[unsignedByte] = code; // lgtm [java/index-out-of-bounds]
} }
VALUE_TO_CODE_MAP = Collections.unmodifiableMap(valueMap);
} }
private final byte byteValue; private final byte byteValue;
@@ -54,9 +76,16 @@ public enum MqttConnectReturnCode {
} }
public static MqttConnectReturnCode valueOf(byte b) { public static MqttConnectReturnCode valueOf(byte b) {
if (VALUE_TO_CODE_MAP.containsKey(b)) { final int unsignedByte = b & 0xFF;
return VALUE_TO_CODE_MAP.get(b); MqttConnectReturnCode mqttConnectReturnCode = null;
try {
mqttConnectReturnCode = VALUES[unsignedByte];
} catch (ArrayIndexOutOfBoundsException ignored) {
// no op
} }
throw new IllegalArgumentException("unknown connect return code: " + (b & 0xFF)); if (mqttConnectReturnCode == null) {
throw new IllegalArgumentException("unknown connect return code: " + unsignedByte);
}
return mqttConnectReturnCode;
} }
} }
+30 -1
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -32,6 +32,7 @@ public final class MqttConnectVariableHeader {
private final boolean isWillFlag; private final boolean isWillFlag;
private final boolean isCleanSession; private final boolean isCleanSession;
private final int keepAliveTimeSeconds; private final int keepAliveTimeSeconds;
private final MqttProperties properties;
public MqttConnectVariableHeader( public MqttConnectVariableHeader(
String name, String name,
@@ -43,6 +44,29 @@ public final class MqttConnectVariableHeader {
boolean isWillFlag, boolean isWillFlag,
boolean isCleanSession, boolean isCleanSession,
int keepAliveTimeSeconds) { int keepAliveTimeSeconds) {
this(name,
version,
hasUserName,
hasPassword,
isWillRetain,
willQos,
isWillFlag,
isCleanSession,
keepAliveTimeSeconds,
MqttProperties.NO_PROPERTIES);
}
public MqttConnectVariableHeader(
String name,
int version,
boolean hasUserName,
boolean hasPassword,
boolean isWillRetain,
int willQos,
boolean isWillFlag,
boolean isCleanSession,
int keepAliveTimeSeconds,
MqttProperties properties) {
this.name = name; this.name = name;
this.version = version; this.version = version;
this.hasUserName = hasUserName; this.hasUserName = hasUserName;
@@ -52,6 +76,7 @@ public final class MqttConnectVariableHeader {
this.isWillFlag = isWillFlag; this.isWillFlag = isWillFlag;
this.isCleanSession = isCleanSession; this.isCleanSession = isCleanSession;
this.keepAliveTimeSeconds = keepAliveTimeSeconds; this.keepAliveTimeSeconds = keepAliveTimeSeconds;
this.properties = MqttProperties.withEmptyDefaults(properties);
} }
public String name() { public String name() {
@@ -90,6 +115,10 @@ public final class MqttConnectVariableHeader {
return keepAliveTimeSeconds; return keepAliveTimeSeconds;
} }
public MqttProperties properties() {
return properties;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
@@ -0,0 +1,39 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
public final class MqttConstant {
private MqttConstant() {
}
/**
* Default max bytes in message
*/
public static final int DEFAULT_MAX_BYTES_IN_MESSAGE = 262144; //256KB
/**
* min client id length
*/
public static final int MIN_CLIENT_ID_LENGTH = 1;
/**
* Default max client id length,In the mqtt3.1 protocol,
* the default maximum Client Identifier length is 23
*/
public static final int DEFAULT_MAX_CLIENT_ID_LENGTH = 23;
}
+443 -112
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -20,26 +20,22 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.ReplayingDecoder; import io.netty.handler.codec.ReplayingDecoder;
import io.netty.handler.codec.mqtt.MqttDecoder.DecoderState; import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import io.netty.util.internal.ObjectUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidClientId;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidMessageId;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidPublishTopicName;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.resetUnusedFields;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.validateFixedHeader;
/** /**
* Decodes Mqtt messages from bytes, following * Decodes Mqtt messages from bytes, following
* <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html"> * the MQTT protocol specification
* the MQTT protocol specification v3.1</a> * <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">v3.1</a>
* or
* <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html">v5.0</a>, depending on the
* version specified in the CONNECT message that first goes through the channel.
*/ */
public final class MqttDecoder extends ReplayingDecoder<DecoderState> { public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState> {
private static final int DEFAULT_MAX_BYTES_IN_MESSAGE = 262144; //256K
/** /**
* States of the decoder. * States of the decoder.
@@ -58,31 +54,42 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private int bytesRemainingInVariablePart; private int bytesRemainingInVariablePart;
private final int maxBytesInMessage; private final int maxBytesInMessage;
private final int maxClientIdLength;
public MqttDecoder() { public MqttDecoder() {
this(DEFAULT_MAX_BYTES_IN_MESSAGE); this(MqttConstant.DEFAULT_MAX_BYTES_IN_MESSAGE, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH);
} }
public MqttDecoder(int maxBytesInMessage) { public MqttDecoder(int maxBytesInMessage) {
this(maxBytesInMessage, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH);
}
public MqttDecoder(int maxBytesInMessage, int maxClientIdLength) {
super(DecoderState.READ_FIXED_HEADER); super(DecoderState.READ_FIXED_HEADER);
this.maxBytesInMessage = maxBytesInMessage; this.maxBytesInMessage = ObjectUtil.checkPositive(maxBytesInMessage, "maxBytesInMessage");
this.maxClientIdLength = ObjectUtil.checkPositive(maxClientIdLength, "maxClientIdLength");
} }
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
switch (state()) { switch (state()) {
case READ_FIXED_HEADER: case READ_FIXED_HEADER: try {
mqttFixedHeader = decodeFixedHeader(buffer); mqttFixedHeader = decodeFixedHeader(ctx, buffer);
bytesRemainingInVariablePart = mqttFixedHeader.remainingLength(); bytesRemainingInVariablePart = mqttFixedHeader.remainingLength();
checkpoint(DecoderState.READ_VARIABLE_HEADER); checkpoint(DecoderState.READ_VARIABLE_HEADER);
// fall through // fall through
} catch (Exception cause) {
out.add(invalidMessage(cause));
return;
}
case READ_VARIABLE_HEADER: try { case READ_VARIABLE_HEADER: try {
if (bytesRemainingInVariablePart > maxBytesInMessage) { final Result<?> decodedVariableHeader = decodeVariableHeader(ctx, buffer, mqttFixedHeader);
throw new DecoderException("too large message: " + bytesRemainingInVariablePart + " bytes");
}
final Result<?> decodedVariableHeader = decodeVariableHeader(buffer, mqttFixedHeader);
variableHeader = decodedVariableHeader.value; variableHeader = decodedVariableHeader.value;
if (bytesRemainingInVariablePart > maxBytesInMessage) {
buffer.skipBytes(actualReadableBytes());
throw new TooLongFrameException("too large message: " + bytesRemainingInVariablePart + " bytes");
}
bytesRemainingInVariablePart -= decodedVariableHeader.numberOfBytesConsumed; bytesRemainingInVariablePart -= decodedVariableHeader.numberOfBytesConsumed;
checkpoint(DecoderState.READ_PAYLOAD); checkpoint(DecoderState.READ_PAYLOAD);
// fall through // fall through
@@ -94,9 +101,11 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
case READ_PAYLOAD: try { case READ_PAYLOAD: try {
final Result<?> decodedPayload = final Result<?> decodedPayload =
decodePayload( decodePayload(
ctx,
buffer, buffer,
mqttFixedHeader.messageType(), mqttFixedHeader.messageType(),
bytesRemainingInVariablePart, bytesRemainingInVariablePart,
maxClientIdLength,
variableHeader); variableHeader);
bytesRemainingInVariablePart -= decodedPayload.numberOfBytesConsumed; bytesRemainingInVariablePart -= decodedPayload.numberOfBytesConsumed;
if (bytesRemainingInVariablePart != 0) { if (bytesRemainingInVariablePart != 0) {
@@ -129,16 +138,20 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private MqttMessage invalidMessage(Throwable cause) { private MqttMessage invalidMessage(Throwable cause) {
checkpoint(DecoderState.BAD_MESSAGE); checkpoint(DecoderState.BAD_MESSAGE);
return MqttMessageFactory.newInvalidMessage(cause); return MqttMessageFactory.newInvalidMessage(mqttFixedHeader, variableHeader, cause);
} }
/** /**
* Decodes the fixed header. It's one byte for the flags and then variable bytes for the remaining length. * Decodes the fixed header. It's one byte for the flags and then variable
* bytes for the remaining length.
*
* @see
* <a href="https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180841">...</a>
* *
* @param buffer the buffer to decode from * @param buffer the buffer to decode from
* @return the fixed header * @return the fixed header
*/ */
private static MqttFixedHeader decodeFixedHeader(ByteBuf buffer) { private static MqttFixedHeader decodeFixedHeader(ChannelHandlerContext ctx, ByteBuf buffer) {
short b1 = buffer.readUnsignedByte(); short b1 = buffer.readUnsignedByte();
MqttMessageType messageType = MqttMessageType.valueOf(b1 >> 4); MqttMessageType messageType = MqttMessageType.valueOf(b1 >> 4);
@@ -146,6 +159,59 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
int qosLevel = (b1 & 0x06) >> 1; int qosLevel = (b1 & 0x06) >> 1;
boolean retain = (b1 & 0x01) != 0; boolean retain = (b1 & 0x01) != 0;
switch (messageType) {
case PUBLISH:
if (qosLevel == 3) {
throw new DecoderException("Illegal QOS Level in fixed header of PUBLISH message ("
+ qosLevel + ')');
}
break;
case PUBREL:
case SUBSCRIBE:
case UNSUBSCRIBE:
if (dupFlag) {
throw new DecoderException("Illegal BIT 3 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
if (qosLevel != 1) {
throw new DecoderException("Illegal QOS Level in fixed header of " + messageType
+ " message, must be 1, found " + qosLevel);
}
if (retain) {
throw new DecoderException("Illegal BIT 0 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
break;
case AUTH:
case CONNACK:
case CONNECT:
case DISCONNECT:
case PINGREQ:
case PINGRESP:
case PUBACK:
case PUBCOMP:
case PUBREC:
case SUBACK:
case UNSUBACK:
if (dupFlag) {
throw new DecoderException("Illegal BIT 3 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
if (qosLevel != 0) {
throw new DecoderException("Illegal BIT 2 or 1 in fixed header of " + messageType
+ " message, must be 0, found " + qosLevel);
}
if (retain) {
throw new DecoderException("Illegal BIT 0 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
break;
default:
throw new DecoderException("Unknown message type, do not know how to validate fixed header");
}
int remainingLength = 0; int remainingLength = 0;
int multiplier = 1; int multiplier = 1;
short digit; short digit;
@@ -163,7 +229,7 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
} }
MqttFixedHeader decodedFixedHeader = MqttFixedHeader decodedFixedHeader =
new MqttFixedHeader(messageType, dupFlag, MqttQoS.valueOf(qosLevel), retain, remainingLength); new MqttFixedHeader(messageType, dupFlag, MqttQoS.valueOf(qosLevel), retain, remainingLength);
return validateFixedHeader(resetUnusedFields(decodedFixedHeader)); return MqttCodecUtil.validateFixedHeader(ctx, MqttCodecUtil.resetUnusedFields(decodedFixedHeader));
} }
/** /**
@@ -172,50 +238,60 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
* @param mqttFixedHeader MqttFixedHeader of the same message * @param mqttFixedHeader MqttFixedHeader of the same message
* @return the variable header * @return the variable header
*/ */
private static Result<?> decodeVariableHeader(ByteBuf buffer, MqttFixedHeader mqttFixedHeader) { private Result<?> decodeVariableHeader(ChannelHandlerContext ctx, ByteBuf buffer, MqttFixedHeader mqttFixedHeader) {
switch (mqttFixedHeader.messageType()) { switch (mqttFixedHeader.messageType()) {
case CONNECT: case CONNECT:
return decodeConnectionVariableHeader(buffer); return decodeConnectionVariableHeader(ctx, buffer);
case CONNACK: case CONNACK:
return decodeConnAckVariableHeader(buffer); return decodeConnAckVariableHeader(ctx, buffer);
case SUBSCRIBE:
case UNSUBSCRIBE: case UNSUBSCRIBE:
case SUBSCRIBE:
case SUBACK: case SUBACK:
case UNSUBACK: case UNSUBACK:
return decodeMessageIdAndPropertiesVariableHeader(ctx, buffer);
case PUBACK: case PUBACK:
case PUBREC: case PUBREC:
case PUBCOMP: case PUBCOMP:
case PUBREL: case PUBREL:
return decodeMessageIdVariableHeader(buffer); return decodePubReplyMessage(buffer);
case PUBLISH: case PUBLISH:
return decodePublishVariableHeader(buffer, mqttFixedHeader); return decodePublishVariableHeader(ctx, buffer, mqttFixedHeader);
case DISCONNECT:
case AUTH:
return decodeReasonCodeAndPropertiesVariableHeader(buffer);
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
case DISCONNECT:
// Empty variable header // Empty variable header
return new Result<Object>(null, 0); return new Result<Object>(null, 0);
default:
//shouldn't reach here
throw new DecoderException("Unknown message type: " + mqttFixedHeader.messageType());
} }
return new Result<Object>(null, 0); //should never reach here
} }
private static Result<MqttConnectVariableHeader> decodeConnectionVariableHeader(ByteBuf buffer) { private static Result<MqttConnectVariableHeader> decodeConnectionVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer) {
final Result<String> protoString = decodeString(buffer); final Result<String> protoString = decodeString(buffer);
int numberOfBytesConsumed = protoString.numberOfBytesConsumed; int numberOfBytesConsumed = protoString.numberOfBytesConsumed;
final byte protocolLevel = buffer.readByte(); final byte protocolLevel = buffer.readByte();
numberOfBytesConsumed += 1; numberOfBytesConsumed += 1;
final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(protoString.value, protocolLevel); MqttVersion version = MqttVersion.fromProtocolNameAndLevel(protoString.value, protocolLevel);
MqttCodecUtil.setMqttVersion(ctx, version);
final int b1 = buffer.readUnsignedByte(); final int b1 = buffer.readUnsignedByte();
numberOfBytesConsumed += 1; numberOfBytesConsumed += 1;
final Result<Integer> keepAlive = decodeMsbLsb(buffer); final int keepAlive = decodeMsbLsb(buffer);
numberOfBytesConsumed += keepAlive.numberOfBytesConsumed; numberOfBytesConsumed += 2;
final boolean hasUserName = (b1 & 0x80) == 0x80; final boolean hasUserName = (b1 & 0x80) == 0x80;
final boolean hasPassword = (b1 & 0x40) == 0x40; final boolean hasPassword = (b1 & 0x40) == 0x40;
@@ -223,69 +299,178 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
final int willQos = (b1 & 0x18) >> 3; final int willQos = (b1 & 0x18) >> 3;
final boolean willFlag = (b1 & 0x04) == 0x04; final boolean willFlag = (b1 & 0x04) == 0x04;
final boolean cleanSession = (b1 & 0x02) == 0x02; final boolean cleanSession = (b1 & 0x02) == 0x02;
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) { if (version == MqttVersion.MQTT_3_1_1 || version == MqttVersion.MQTT_5) {
final boolean zeroReservedFlag = (b1 & 0x01) == 0x0; final boolean zeroReservedFlag = (b1 & 0x01) == 0x0;
if (!zeroReservedFlag) { if (!zeroReservedFlag) {
// MQTT v3.1.1: The Server MUST validate that the reserved flag in the CONNECT Control Packet is // MQTT v3.1.1: The Server MUST validate that the reserved flag in the CONNECT Control Packet is
// set to zero and disconnect the Client if it is not zero. // set to zero and disconnect the Client if it is not zero.
// See http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230 // See https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230
throw new DecoderException("non-zero reserved flag"); throw new DecoderException("non-zero reserved flag");
} }
} }
final MqttProperties properties;
if (version == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
properties = MqttProperties.NO_PROPERTIES;
}
final MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader( final MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader(
mqttVersion.protocolName(), version.protocolName(),
mqttVersion.protocolLevel(), version.protocolLevel(),
hasUserName, hasUserName,
hasPassword, hasPassword,
willRetain, willRetain,
willQos, willQos,
willFlag, willFlag,
cleanSession, cleanSession,
keepAlive.value); keepAlive,
properties);
return new Result<MqttConnectVariableHeader>(mqttConnectVariableHeader, numberOfBytesConsumed); return new Result<MqttConnectVariableHeader>(mqttConnectVariableHeader, numberOfBytesConsumed);
} }
private static Result<MqttConnAckVariableHeader> decodeConnAckVariableHeader(ByteBuf buffer) { private static Result<MqttConnAckVariableHeader> decodeConnAckVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer) {
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
final boolean sessionPresent = (buffer.readUnsignedByte() & 0x01) == 0x01; final boolean sessionPresent = (buffer.readUnsignedByte() & 0x01) == 0x01;
byte returnCode = buffer.readByte(); byte returnCode = buffer.readByte();
final int numberOfBytesConsumed = 2; int numberOfBytesConsumed = 2;
final MqttProperties properties;
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
properties = MqttProperties.NO_PROPERTIES;
}
final MqttConnAckVariableHeader mqttConnAckVariableHeader = final MqttConnAckVariableHeader mqttConnAckVariableHeader =
new MqttConnAckVariableHeader(MqttConnectReturnCode.valueOf(returnCode), sessionPresent); new MqttConnAckVariableHeader(MqttConnectReturnCode.valueOf(returnCode), sessionPresent, properties);
return new Result<MqttConnAckVariableHeader>(mqttConnAckVariableHeader, numberOfBytesConsumed); return new Result<MqttConnAckVariableHeader>(mqttConnAckVariableHeader, numberOfBytesConsumed);
} }
private static Result<MqttMessageIdVariableHeader> decodeMessageIdVariableHeader(ByteBuf buffer) { private static Result<MqttMessageIdAndPropertiesVariableHeader> decodeMessageIdAndPropertiesVariableHeader(
final Result<Integer> messageId = decodeMessageId(buffer); ChannelHandlerContext ctx,
return new Result<MqttMessageIdVariableHeader>( ByteBuf buffer) {
MqttMessageIdVariableHeader.from(messageId.value), final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
messageId.numberOfBytesConsumed); final int packetId = decodeMessageId(buffer);
final MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader;
final int mqtt5Consumed;
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> properties = decodeProperties(buffer);
mqttVariableHeader = new MqttMessageIdAndPropertiesVariableHeader(packetId, properties.value);
mqtt5Consumed = properties.numberOfBytesConsumed;
} else {
mqttVariableHeader = new MqttMessageIdAndPropertiesVariableHeader(packetId,
MqttProperties.NO_PROPERTIES);
mqtt5Consumed = 0;
}
return new Result<MqttMessageIdAndPropertiesVariableHeader>(mqttVariableHeader,
2 + mqtt5Consumed);
} }
private static Result<MqttPublishVariableHeader> decodePublishVariableHeader( private Result<MqttPubReplyMessageVariableHeader> decodePubReplyMessage(ByteBuf buffer) {
final int packetId = decodeMessageId(buffer);
final MqttPubReplyMessageVariableHeader mqttPubAckVariableHeader;
final int consumed;
final int packetIdNumberOfBytesConsumed = 2;
if (bytesRemainingInVariablePart > 3) {
final byte reasonCode = buffer.readByte();
final Result<MqttProperties> properties = decodeProperties(buffer);
mqttPubAckVariableHeader = new MqttPubReplyMessageVariableHeader(packetId,
reasonCode,
properties.value);
consumed = packetIdNumberOfBytesConsumed + 1 + properties.numberOfBytesConsumed;
} else if (bytesRemainingInVariablePart > 2) {
final byte reasonCode = buffer.readByte();
mqttPubAckVariableHeader = new MqttPubReplyMessageVariableHeader(packetId,
reasonCode,
MqttProperties.NO_PROPERTIES);
consumed = packetIdNumberOfBytesConsumed + 1;
} else {
mqttPubAckVariableHeader = new MqttPubReplyMessageVariableHeader(packetId,
(byte) 0,
MqttProperties.NO_PROPERTIES);
consumed = packetIdNumberOfBytesConsumed;
}
return new Result<MqttPubReplyMessageVariableHeader>(mqttPubAckVariableHeader, consumed);
}
private Result<MqttReasonCodeAndPropertiesVariableHeader> decodeReasonCodeAndPropertiesVariableHeader(
ByteBuf buffer) {
final byte reasonCode;
final MqttProperties properties;
final int consumed;
if (bytesRemainingInVariablePart > 1) {
reasonCode = buffer.readByte();
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
consumed = 1 + propertiesResult.numberOfBytesConsumed;
} else if (bytesRemainingInVariablePart > 0) {
reasonCode = buffer.readByte();
properties = MqttProperties.NO_PROPERTIES;
consumed = 1;
} else {
reasonCode = 0;
properties = MqttProperties.NO_PROPERTIES;
consumed = 0;
}
final MqttReasonCodeAndPropertiesVariableHeader mqttReasonAndPropsVariableHeader =
new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, properties);
return new Result<MqttReasonCodeAndPropertiesVariableHeader>(
mqttReasonAndPropsVariableHeader,
consumed);
}
private Result<MqttPublishVariableHeader> decodePublishVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer, ByteBuf buffer,
MqttFixedHeader mqttFixedHeader) { MqttFixedHeader mqttFixedHeader) {
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
final Result<String> decodedTopic = decodeString(buffer); final Result<String> decodedTopic = decodeString(buffer);
if (!isValidPublishTopicName(decodedTopic.value)) { if (!MqttCodecUtil.isValidPublishTopicName(decodedTopic.value)) {
throw new DecoderException("invalid publish topic name: " + decodedTopic.value + " (contains wildcards)"); throw new DecoderException("invalid publish topic name: " + decodedTopic.value + " (contains wildcards)");
} }
int numberOfBytesConsumed = decodedTopic.numberOfBytesConsumed; int numberOfBytesConsumed = decodedTopic.numberOfBytesConsumed;
int messageId = -1; int messageId = -1;
if (mqttFixedHeader.qosLevel().value() > 0) { if (mqttFixedHeader.qosLevel().value() > 0) {
final Result<Integer> decodedMessageId = decodeMessageId(buffer); messageId = decodeMessageId(buffer);
messageId = decodedMessageId.value; numberOfBytesConsumed += 2;
numberOfBytesConsumed += decodedMessageId.numberOfBytesConsumed;
} }
final MqttProperties properties;
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
properties = MqttProperties.NO_PROPERTIES;
}
final MqttPublishVariableHeader mqttPublishVariableHeader = final MqttPublishVariableHeader mqttPublishVariableHeader =
new MqttPublishVariableHeader(decodedTopic.value, messageId); new MqttPublishVariableHeader(decodedTopic.value, messageId, properties);
return new Result<MqttPublishVariableHeader>(mqttPublishVariableHeader, numberOfBytesConsumed); return new Result<MqttPublishVariableHeader>(mqttPublishVariableHeader, numberOfBytesConsumed);
} }
private static Result<Integer> decodeMessageId(ByteBuf buffer) { /**
final Result<Integer> messageId = decodeMsbLsb(buffer); * @return messageId with numberOfBytesConsumed is 2
if (!isValidMessageId(messageId.value)) { */
throw new DecoderException("invalid messageId: " + messageId.value); private static int decodeMessageId(ByteBuf buffer) {
final int messageId = decodeMsbLsb(buffer);
if (!MqttCodecUtil.isValidMessageId(messageId)) {
throw new DecoderException("invalid messageId: " + messageId);
} }
return messageId; return messageId;
} }
@@ -300,13 +485,15 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
* @return the payload * @return the payload
*/ */
private static Result<?> decodePayload( private static Result<?> decodePayload(
ChannelHandlerContext ctx,
ByteBuf buffer, ByteBuf buffer,
MqttMessageType messageType, MqttMessageType messageType,
int bytesRemainingInVariablePart, int bytesRemainingInVariablePart,
int maxClientIdLength,
Object variableHeader) { Object variableHeader) {
switch (messageType) { switch (messageType) {
case CONNECT: case CONNECT:
return decodeConnectionPayload(buffer, (MqttConnectVariableHeader) variableHeader); return decodeConnectionPayload(buffer, maxClientIdLength, (MqttConnectVariableHeader) variableHeader);
case SUBSCRIBE: case SUBSCRIBE:
return decodeSubscribePayload(buffer, bytesRemainingInVariablePart); return decodeSubscribePayload(buffer, bytesRemainingInVariablePart);
@@ -317,6 +504,9 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
case UNSUBSCRIBE: case UNSUBSCRIBE:
return decodeUnsubscribePayload(buffer, bytesRemainingInVariablePart); return decodeUnsubscribePayload(buffer, bytesRemainingInVariablePart);
case UNSUBACK:
return decodeUnsubAckPayload(ctx, buffer, bytesRemainingInVariablePart);
case PUBLISH: case PUBLISH:
return decodePublishPayload(buffer, bytesRemainingInVariablePart); return decodePublishPayload(buffer, bytesRemainingInVariablePart);
@@ -328,42 +518,62 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private static Result<MqttConnectPayload> decodeConnectionPayload( private static Result<MqttConnectPayload> decodeConnectionPayload(
ByteBuf buffer, ByteBuf buffer,
int maxClientIdLength,
MqttConnectVariableHeader mqttConnectVariableHeader) { MqttConnectVariableHeader mqttConnectVariableHeader) {
final Result<String> decodedClientId = decodeString(buffer); final Result<String> decodedClientId = decodeString(buffer);
final String decodedClientIdValue = decodedClientId.value; final String decodedClientIdValue = decodedClientId.value;
final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(mqttConnectVariableHeader.name(), final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(mqttConnectVariableHeader.name(),
(byte) mqttConnectVariableHeader.version()); (byte) mqttConnectVariableHeader.version());
if (!isValidClientId(mqttVersion, decodedClientIdValue)) { if (!MqttCodecUtil.isValidClientId(mqttVersion, maxClientIdLength, decodedClientIdValue)) {
throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + decodedClientIdValue); throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + decodedClientIdValue);
} }
int numberOfBytesConsumed = decodedClientId.numberOfBytesConsumed; int numberOfBytesConsumed = decodedClientId.numberOfBytesConsumed;
Result<String> decodedWillTopic = null; Result<String> decodedWillTopic = null;
Result<String> decodedWillMessage = null; byte[] decodedWillMessage = null;
final MqttProperties willProperties;
if (mqttConnectVariableHeader.isWillFlag()) { if (mqttConnectVariableHeader.isWillFlag()) {
if (mqttVersion.protocolLevel() <= MqttVersion.MQTT_5.protocolLevel()) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
willProperties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
willProperties = MqttProperties.NO_PROPERTIES;
}
decodedWillTopic = decodeString(buffer, 0, 32767); decodedWillTopic = decodeString(buffer, 0, 32767);
numberOfBytesConsumed += decodedWillTopic.numberOfBytesConsumed; numberOfBytesConsumed += decodedWillTopic.numberOfBytesConsumed;
decodedWillMessage = decodeAsciiString(buffer); decodedWillMessage = decodeByteArray(buffer);
numberOfBytesConsumed += decodedWillMessage.numberOfBytesConsumed; numberOfBytesConsumed += decodedWillMessage.length + 2;
} else {
willProperties = MqttProperties.NO_PROPERTIES;
} }
Result<String> decodedUserName = null; Result<String> decodedUserName = null;
Result<byte[]> decodedPassword = null; byte[] decodedPassword = null;
byte[] decodedSignature = null;
if (mqttConnectVariableHeader.hasUserName()) { if (mqttConnectVariableHeader.hasUserName()) {
decodedUserName = decodeString(buffer); decodedUserName = decodeString(buffer);
numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed; numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed;
} }
if (mqttConnectVariableHeader.hasPassword()) { if (mqttConnectVariableHeader.hasPassword()) {
decodedPassword = decodeByte(buffer); decodedPassword = decodeByteArray(buffer);
numberOfBytesConsumed += decodedPassword.numberOfBytesConsumed; numberOfBytesConsumed += decodedPassword.length + 2;
}
if (mqttConnectVariableHeader.isWillRetain()) {
decodedSignature = decodeByteArray(buffer);
numberOfBytesConsumed += decodedSignature.length + 2;
} }
final MqttConnectPayload mqttConnectPayload = final MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload( new MqttConnectPayload(
decodedClientId.value, decodedClientId.value,
willProperties,
decodedWillTopic != null ? decodedWillTopic.value : null, decodedWillTopic != null ? decodedWillTopic.value : null,
decodedWillMessage != null ? decodedWillMessage.value : null, decodedWillMessage,
decodedUserName != null ? decodedUserName.value : null, decodedUserName != null ? decodedUserName.value : null,
decodedPassword != null ? decodedPassword.value : null); decodedPassword,
decodedSignature
);
return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed); return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed);
} }
@@ -375,9 +585,21 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
while (numberOfBytesConsumed < bytesRemainingInVariablePart) { while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
final Result<String> decodedTopicName = decodeString(buffer); final Result<String> decodedTopicName = decodeString(buffer);
numberOfBytesConsumed += decodedTopicName.numberOfBytesConsumed; numberOfBytesConsumed += decodedTopicName.numberOfBytesConsumed;
int qos = buffer.readUnsignedByte() & 0x03; //See 3.8.3.1 Subscription Options of MQTT 5.0 specification for optionByte details
final short optionByte = buffer.readUnsignedByte();
MqttQoS qos = MqttQoS.valueOf(optionByte & 0x03);
boolean noLocal = ((optionByte & 0x04) >> 2) == 1;
boolean retainAsPublished = ((optionByte & 0x08) >> 3) == 1;
MqttSubscriptionOption.RetainedHandlingPolicy retainHandling = MqttSubscriptionOption.RetainedHandlingPolicy.valueOf((optionByte & 0x30) >> 4);
final MqttSubscriptionOption subscriptionOption = new MqttSubscriptionOption(qos,
noLocal,
retainAsPublished,
retainHandling);
numberOfBytesConsumed++; numberOfBytesConsumed++;
subscribeTopics.add(new MqttTopicSubscription(decodedTopicName.value, MqttQoS.valueOf(qos))); subscribeTopics.add(new MqttTopicSubscription(decodedTopicName.value, subscriptionOption));
} }
return new Result<MqttSubscribePayload>(new MqttSubscribePayload(subscribeTopics), numberOfBytesConsumed); return new Result<MqttSubscribePayload>(new MqttSubscribePayload(subscribeTopics), numberOfBytesConsumed);
} }
@@ -385,16 +607,30 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private static Result<MqttSubAckPayload> decodeSubackPayload( private static Result<MqttSubAckPayload> decodeSubackPayload(
ByteBuf buffer, ByteBuf buffer,
int bytesRemainingInVariablePart) { int bytesRemainingInVariablePart) {
final List<Integer> grantedQos = new ArrayList<Integer>(); final List<Integer> grantedQos = new ArrayList<Integer>(bytesRemainingInVariablePart);
int numberOfBytesConsumed = 0; int numberOfBytesConsumed = 0;
while (numberOfBytesConsumed < bytesRemainingInVariablePart) { while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
int qos = buffer.readUnsignedByte() & 0x03; int reasonCode = buffer.readUnsignedByte();
numberOfBytesConsumed++; numberOfBytesConsumed++;
grantedQos.add(qos); grantedQos.add(reasonCode);
} }
return new Result<MqttSubAckPayload>(new MqttSubAckPayload(grantedQos), numberOfBytesConsumed); return new Result<MqttSubAckPayload>(new MqttSubAckPayload(grantedQos), numberOfBytesConsumed);
} }
private static Result<MqttUnsubAckPayload> decodeUnsubAckPayload(
ChannelHandlerContext ctx,
ByteBuf buffer,
int bytesRemainingInVariablePart) {
final List<Short> reasonCodes = new ArrayList<Short>(bytesRemainingInVariablePart);
int numberOfBytesConsumed = 0;
while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
short reasonCode = buffer.readUnsignedByte();
numberOfBytesConsumed++;
reasonCodes.add(reasonCode);
}
return new Result<MqttUnsubAckPayload>(new MqttUnsubAckPayload(reasonCodes), numberOfBytesConsumed);
}
private static Result<MqttUnsubscribePayload> decodeUnsubscribePayload( private static Result<MqttUnsubscribePayload> decodeUnsubscribePayload(
ByteBuf buffer, ByteBuf buffer,
int bytesRemainingInVariablePart) { int bytesRemainingInVariablePart) {
@@ -419,25 +655,9 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
return decodeString(buffer, 0, Integer.MAX_VALUE); return decodeString(buffer, 0, Integer.MAX_VALUE);
} }
private static Result<String> decodeAsciiString(ByteBuf buffer) {
Result<String> result = decodeString(buffer, 0, Integer.MAX_VALUE);
final String s = result.value;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) > 127) {
return new Result<String>(null, result.numberOfBytesConsumed);
}
}
return new Result<String>(s, result.numberOfBytesConsumed);
}
private static Result<byte[]> decodeByte(ByteBuf buffer) {
return decodeByte(buffer, 0, Integer.MAX_VALUE);
}
private static Result<String> decodeString(ByteBuf buffer, int minBytes, int maxBytes) { private static Result<String> decodeString(ByteBuf buffer, int minBytes, int maxBytes) {
final Result<Integer> decodedSize = decodeMsbLsb(buffer); int size = decodeMsbLsb(buffer);
int size = decodedSize.value; int numberOfBytesConsumed = 2;
int numberOfBytesConsumed = decodedSize.numberOfBytesConsumed;
if (size < minBytes || size > maxBytes) { if (size < minBytes || size > maxBytes) {
buffer.skipBytes(size); buffer.skipBytes(size);
numberOfBytesConsumed += size; numberOfBytesConsumed += size;
@@ -449,35 +669,68 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
return new Result<String>(s, numberOfBytesConsumed); return new Result<String>(s, numberOfBytesConsumed);
} }
private static Result<byte[]> decodeByte(ByteBuf buffer, int minBytes, int maxBytes) { /**
final Result<Integer> decodedSize = decodeMsbLsb(buffer); *
int size = decodedSize.value; * @return the decoded byte[], numberOfBytesConsumed = byte[].length + 2
int numberOfBytesConsumed = decodedSize.numberOfBytesConsumed; */
if (size < minBytes || size > maxBytes) { private static byte[] decodeByteArray(ByteBuf buffer) {
buffer.skipBytes(size); int size = decodeMsbLsb(buffer);
numberOfBytesConsumed += size; byte[] bytes = new byte[size];
return new Result<>(null, numberOfBytesConsumed); buffer.readBytes(bytes);
} return bytes;
byte[] s = new byte[size];
buffer.getBytes(buffer.readerIndex(), s, 0, size);
buffer.skipBytes(size);
numberOfBytesConsumed += size;
return new Result<>(s, numberOfBytesConsumed);
} }
private static Result<Integer> decodeMsbLsb(ByteBuf buffer) { // packing utils to reduce the amount of garbage while decoding ints
return decodeMsbLsb(buffer, 0, 65535); private static long packInts(int a, int b) {
return (((long) a) << 32) | (b & 0xFFFFFFFFL);
} }
private static Result<Integer> decodeMsbLsb(ByteBuf buffer, int min, int max) { private static int unpackA(long ints) {
return (int) (ints >> 32);
}
private static int unpackB(long ints) {
return (int) ints;
}
/**
* numberOfBytesConsumed = 2. return decoded result.
*/
private static int decodeMsbLsb(ByteBuf buffer) {
int min = 0;
int max = 65535;
short msbSize = buffer.readUnsignedByte(); short msbSize = buffer.readUnsignedByte();
short lsbSize = buffer.readUnsignedByte(); short lsbSize = buffer.readUnsignedByte();
final int numberOfBytesConsumed = 2;
int result = msbSize << 8 | lsbSize; int result = msbSize << 8 | lsbSize;
if (result < min || result > max) { if (result < min || result > max) {
result = -1; result = -1;
} }
return new Result<Integer>(result, numberOfBytesConsumed); return result;
}
/**
* See 1.5.5 Variable Byte Integer section of MQTT 5.0 specification for encoding/decoding rules
*
* @param buffer the buffer to decode from
* @return result pack with a = decoded integer, b = numberOfBytesConsumed. Need to unpack to read them.
* @throws DecoderException if bad MQTT protocol limits Remaining Length
*/
private static long decodeVariableByteInteger(ByteBuf buffer) {
int remainingLength = 0;
int multiplier = 1;
short digit;
int loops = 0;
do {
digit = buffer.readUnsignedByte();
remainingLength += (digit & 127) * multiplier;
multiplier *= 128;
loops++;
} while ((digit & 128) != 0 && loops < 4);
if (loops == 4 && (digit & 128) != 0) {
throw new DecoderException("MQTT protocol limits Remaining Length to 4 bytes");
}
return packInts(remainingLength, loops);
} }
private static final class Result<T> { private static final class Result<T> {
@@ -490,4 +743,82 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
this.numberOfBytesConsumed = numberOfBytesConsumed; this.numberOfBytesConsumed = numberOfBytesConsumed;
} }
} }
private static Result<MqttProperties> decodeProperties(ByteBuf buffer) {
final long propertiesLength = decodeVariableByteInteger(buffer);
int totalPropertiesLength = unpackA(propertiesLength);
int numberOfBytesConsumed = unpackB(propertiesLength);
MqttProperties decodedProperties = new MqttProperties();
while (numberOfBytesConsumed < totalPropertiesLength) {
long propertyId = decodeVariableByteInteger(buffer);
final int propertyIdValue = unpackA(propertyId);
numberOfBytesConsumed += unpackB(propertyId);
MqttProperties.MqttPropertyType propertyType = MqttProperties.MqttPropertyType.valueOf(propertyIdValue);
switch (propertyType) {
case PAYLOAD_FORMAT_INDICATOR:
case REQUEST_PROBLEM_INFORMATION:
case REQUEST_RESPONSE_INFORMATION:
case MAXIMUM_QOS:
case RETAIN_AVAILABLE:
case WILDCARD_SUBSCRIPTION_AVAILABLE:
case SUBSCRIPTION_IDENTIFIER_AVAILABLE:
case SHARED_SUBSCRIPTION_AVAILABLE:
final int b1 = buffer.readUnsignedByte();
numberOfBytesConsumed++;
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, b1));
break;
case SERVER_KEEP_ALIVE:
case RECEIVE_MAXIMUM:
case TOPIC_ALIAS_MAXIMUM:
case TOPIC_ALIAS:
final int int2BytesResult = decodeMsbLsb(buffer);
numberOfBytesConsumed += 2;
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, int2BytesResult));
break;
case PUBLICATION_EXPIRY_INTERVAL:
case SESSION_EXPIRY_INTERVAL:
case WILL_DELAY_INTERVAL:
case MAXIMUM_PACKET_SIZE:
final int maxPacketSize = buffer.readInt();
numberOfBytesConsumed += 4;
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, maxPacketSize));
break;
case SUBSCRIPTION_IDENTIFIER:
long vbIntegerResult = decodeVariableByteInteger(buffer);
numberOfBytesConsumed += unpackB(vbIntegerResult);
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, unpackA(vbIntegerResult)));
break;
case CONTENT_TYPE:
case RESPONSE_TOPIC:
case ASSIGNED_CLIENT_IDENTIFIER:
case AUTHENTICATION_METHOD:
case RESPONSE_INFORMATION:
case SERVER_REFERENCE:
case REASON_STRING:
final Result<String> stringResult = decodeString(buffer);
numberOfBytesConsumed += stringResult.numberOfBytesConsumed;
decodedProperties.add(new MqttProperties.StringProperty(propertyIdValue, stringResult.value));
break;
case USER_PROPERTY:
final Result<String> keyResult = decodeString(buffer);
final Result<String> valueResult = decodeString(buffer);
numberOfBytesConsumed += keyResult.numberOfBytesConsumed;
numberOfBytesConsumed += valueResult.numberOfBytesConsumed;
decodedProperties.add(new MqttProperties.UserProperty(keyResult.value, valueResult.value));
break;
case CORRELATION_DATA:
case AUTHENTICATION_DATA:
final byte[] binaryDataResult = decodeByteArray(buffer);
numberOfBytesConsumed += binaryDataResult.length + 2;
decodedProperties.add(new MqttProperties.BinaryProperty(propertyIdValue, binaryDataResult));
break;
default:
//shouldn't reach here
throw new DecoderException("Unknown property type: " + propertyType);
}
}
return new Result<MqttProperties>(decodedProperties, numberOfBytesConsumed);
}
} }
+517 -172
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -18,19 +18,22 @@ package io.netty.handler.codec.mqtt;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.EncoderException;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.EmptyArrays;
import java.util.List; import java.util.List;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.*; import static io.netty.buffer.ByteBufUtil.*;
/** /**
* Encodes Mqtt messages into bytes following the protocol specification v3.1 * Encodes Mqtt messages into bytes following the protocol specification v3.1
* as described here <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">MQTTV3.1</a> * as described here <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">MQTTV3.1</a>
* or v5.0 as described here <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html">MQTTv5.0</a> -
* depending on the version specified in the first CONNECT message that goes through the channel.
*/ */
@ChannelHandler.Sharable @ChannelHandler.Sharable
public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> { public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
@@ -41,49 +44,57 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
@Override @Override
protected void encode(ChannelHandlerContext ctx, MqttMessage msg, List<Object> out) throws Exception { protected void encode(ChannelHandlerContext ctx, MqttMessage msg, List<Object> out) throws Exception {
out.add(doEncode(ctx.alloc(), msg)); out.add(doEncode(ctx, msg));
} }
/** /**
* This is the main encoding method. * This is the main encoding method.
* It's only visible for testing. * It's only visible for testing.
* *
* @param byteBufAllocator Allocates ByteBuf
* @param message MQTT message to encode * @param message MQTT message to encode
* @return ByteBuf with encoded bytes * @return ByteBuf with encoded bytes
*/ */
static ByteBuf doEncode(ByteBufAllocator byteBufAllocator, MqttMessage message) { static ByteBuf doEncode(ChannelHandlerContext ctx,
MqttMessage message) {
switch (message.fixedHeader().messageType()) { switch (message.fixedHeader().messageType()) {
case CONNECT: case CONNECT:
return encodeConnectMessage(byteBufAllocator, (MqttConnectMessage) message); return encodeConnectMessage(ctx, (MqttConnectMessage) message);
case CONNACK: case CONNACK:
return encodeConnAckMessage(byteBufAllocator, (MqttConnAckMessage) message); return encodeConnAckMessage(ctx, (MqttConnAckMessage) message);
case PUBLISH: case PUBLISH:
return encodePublishMessage(byteBufAllocator, (MqttPublishMessage) message); return encodePublishMessage(ctx, (MqttPublishMessage) message);
case SUBSCRIBE: case SUBSCRIBE:
return encodeSubscribeMessage(byteBufAllocator, (MqttSubscribeMessage) message); return encodeSubscribeMessage(ctx, (MqttSubscribeMessage) message);
case UNSUBSCRIBE: case UNSUBSCRIBE:
return encodeUnsubscribeMessage(byteBufAllocator, (MqttUnsubscribeMessage) message); return encodeUnsubscribeMessage(ctx, (MqttUnsubscribeMessage) message);
case SUBACK: case SUBACK:
return encodeSubAckMessage(byteBufAllocator, (MqttSubAckMessage) message); return encodeSubAckMessage(ctx, (MqttSubAckMessage) message);
case UNSUBACK: case UNSUBACK:
if (message instanceof MqttUnsubAckMessage) {
return encodeUnsubAckMessage(ctx, (MqttUnsubAckMessage) message);
}
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
case PUBACK: case PUBACK:
case PUBREC: case PUBREC:
case PUBREL: case PUBREL:
case PUBCOMP: case PUBCOMP:
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(byteBufAllocator, message); return encodePubReplyMessage(ctx, message);
case DISCONNECT:
case AUTH:
return encodeReasonCodePlusPropertiesMessage(ctx, message);
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
case DISCONNECT: return encodeMessageWithOnlySingleByteFixedHeader(ctx.alloc(), message);
return encodeMessageWithOnlySingleByteFixedHeader(byteBufAllocator, message);
default: default:
throw new IllegalArgumentException( throw new IllegalArgumentException(
@@ -92,7 +103,7 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} }
private static ByteBuf encodeConnectMessage( private static ByteBuf encodeConnectMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttConnectMessage message) { MqttConnectMessage message) {
int payloadBufferSize = 0; int payloadBufferSize = 0;
@@ -101,70 +112,96 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
MqttConnectPayload payload = message.payload(); MqttConnectPayload payload = message.payload();
MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(variableHeader.name(), MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(variableHeader.name(),
(byte) variableHeader.version()); (byte) variableHeader.version());
MqttCodecUtil.setMqttVersion(ctx, mqttVersion);
// as MQTT 3.1 & 3.1.1 spec, If the User Name Flag is set to 0, the Password Flag MUST be set to 0
if (!variableHeader.hasUserName() && variableHeader.hasPassword()) {
throw new EncoderException("Without a username, the password MUST be not set");
}
// Client id // Client id
String clientIdentifier = payload.clientIdentifier(); String clientIdentifier = payload.clientIdentifier();
if (!isValidClientId(mqttVersion, clientIdentifier)) { if (!MqttCodecUtil.isValidClientId(mqttVersion, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH, clientIdentifier)) {
throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + clientIdentifier); throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + clientIdentifier);
} }
byte[] clientIdentifierBytes = encodeStringUtf8(clientIdentifier); int clientIdentifierBytes = utf8Bytes(clientIdentifier);
payloadBufferSize += 2 + clientIdentifierBytes.length; payloadBufferSize += 2 + clientIdentifierBytes;
// Will topic and message // Will topic and message
String willTopic = payload.willTopic(); String willTopic = payload.willTopic();
byte[] willTopicBytes = willTopic != null ? encodeStringUtf8(willTopic) : EmptyArrays.EMPTY_BYTES; int willTopicBytes = nullableUtf8Bytes(willTopic);
String willMessage = payload.willMessage(); byte[] willMessage = payload.willMessageInBytes();
byte[] willMessageBytes = willMessage != null ? encodeStringUtf8(willMessage) : EmptyArrays.EMPTY_BYTES; byte[] willMessageBytes = willMessage != null ? willMessage : EmptyArrays.EMPTY_BYTES;
if (variableHeader.isWillFlag()) { if (variableHeader.isWillFlag()) {
payloadBufferSize += 2 + willTopicBytes.length; payloadBufferSize += 2 + willTopicBytes;
payloadBufferSize += 2 + willMessageBytes.length; payloadBufferSize += 2 + willMessageBytes.length;
} }
String userName = payload.userName(); String userName = payload.userName();
byte[] userNameBytes = userName != null ? encodeStringUtf8(userName) : EmptyArrays.EMPTY_BYTES; int userNameBytes = nullableUtf8Bytes(userName);
if (variableHeader.hasUserName()) { if (variableHeader.hasUserName()) {
payloadBufferSize += 2 + userNameBytes.length; payloadBufferSize += 2 + userNameBytes;
} }
byte[] passwordBytes = payload.password(); byte[] password = payload.passwordInBytes();
byte[] passwordBytes = password != null ? password : EmptyArrays.EMPTY_BYTES;
if (variableHeader.hasPassword()) { if (variableHeader.hasPassword()) {
payloadBufferSize += 2 + passwordBytes.length; payloadBufferSize += 2 + passwordBytes.length;
} }
// Fixed header // Fixed and variable header
byte[] protocolNameBytes = mqttVersion.protocolNameBytes(); byte[] protocolNameBytes = mqttVersion.protocolNameBytes();
int variableHeaderBufferSize = 2 + protocolNameBytes.length + 4; ByteBuf propertiesBuf = encodePropertiesIfNeeded(
int variablePartSize = variableHeaderBufferSize + payloadBufferSize; mqttVersion,
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize); ctx.alloc(),
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize); message.variableHeader().properties());
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader)); try {
writeVariableLengthInt(buf, variablePartSize); final ByteBuf willPropertiesBuf;
if (variableHeader.isWillFlag()) {
willPropertiesBuf = encodePropertiesIfNeeded(mqttVersion, ctx.alloc(), payload.willProperties());
payloadBufferSize += willPropertiesBuf.readableBytes();
} else {
willPropertiesBuf = Unpooled.EMPTY_BUFFER;
}
try {
int variableHeaderBufferSize = 2 + protocolNameBytes.length + 4 + propertiesBuf.readableBytes();
buf.writeShort(protocolNameBytes.length); int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
buf.writeBytes(protocolNameBytes); int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
buf.writeByte(variableHeader.version()); buf.writeShort(protocolNameBytes.length);
buf.writeByte(getConnVariableHeaderFlag(variableHeader)); buf.writeBytes(protocolNameBytes);
buf.writeShort(variableHeader.keepAliveTimeSeconds());
// Payload buf.writeByte(variableHeader.version());
buf.writeShort(clientIdentifierBytes.length); buf.writeByte(getConnVariableHeaderFlag(variableHeader));
buf.writeBytes(clientIdentifierBytes, 0, clientIdentifierBytes.length); buf.writeShort(variableHeader.keepAliveTimeSeconds());
if (variableHeader.isWillFlag()) { buf.writeBytes(propertiesBuf);
buf.writeShort(willTopicBytes.length);
buf.writeBytes(willTopicBytes, 0, willTopicBytes.length); // Payload
buf.writeShort(willMessageBytes.length); writeExactUTF8String(buf, clientIdentifier, clientIdentifierBytes);
buf.writeBytes(willMessageBytes, 0, willMessageBytes.length); if (variableHeader.isWillFlag()) {
buf.writeBytes(willPropertiesBuf);
writeExactUTF8String(buf, willTopic, willTopicBytes);
buf.writeShort(willMessageBytes.length);
buf.writeBytes(willMessageBytes, 0, willMessageBytes.length);
}
if (variableHeader.hasUserName()) {
writeExactUTF8String(buf, userName, userNameBytes);
}
if (variableHeader.hasPassword()) {
buf.writeShort(passwordBytes.length);
buf.writeBytes(passwordBytes, 0, passwordBytes.length);
}
return buf;
} finally {
willPropertiesBuf.release();
}
} finally {
propertiesBuf.release();
} }
if (variableHeader.hasUserName()) {
buf.writeShort(userNameBytes.length);
buf.writeBytes(userNameBytes, 0, userNameBytes.length);
}
if (variableHeader.hasPassword()) {
buf.writeShort(passwordBytes.length);
buf.writeBytes(passwordBytes, 0, passwordBytes.length);
}
return buf;
} }
private static int getConnVariableHeaderFlag(MqttConnectVariableHeader variableHeader) { private static int getConnVariableHeaderFlag(MqttConnectVariableHeader variableHeader) {
@@ -189,149 +226,285 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} }
private static ByteBuf encodeConnAckMessage( private static ByteBuf encodeConnAckMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttConnAckMessage message) { MqttConnAckMessage message) {
int length = 4; final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttConnectAckPayload payload = null; ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
if (message.payload() != null) { ctx.alloc(),
payload = (MqttConnectAckPayload)message.payload(); message.variableHeader().properties());
if (payload.getData() != null && payload.getData().length > 0) {
length += payload.getData().length;
}
}
ByteBuf buf = byteBufAllocator.buffer(length);
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
buf.writeByte(length - 2);
buf.writeByte(message.variableHeader().isSessionPresent() ? 0x01 : 0x00);
buf.writeByte(message.variableHeader().connectReturnCode().byteValue());
if (length > 4) {
buf.writeBytes(payload.getData());
}
return buf; try {
int length = 4;
MqttConnectAckPayload payload = null;
if (message.payload() != null) {
payload = (MqttConnectAckPayload)message.payload();
if (payload.getData() != null && payload.getData().length > 0) {
length += payload.getData().length;
}
}
ByteBuf buf = ctx.alloc().buffer(length + propertiesBuf.readableBytes());
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, length - 2 + propertiesBuf.readableBytes());
buf.writeByte(message.variableHeader().isSessionPresent() ? 0x01 : 0x00);
buf.writeByte(message.variableHeader().connectReturnCode().byteValue());
if (length > 4) {
buf.writeBytes(payload.getData());
}
buf.writeBytes(propertiesBuf);
return buf;
} finally {
propertiesBuf.release();
}
} }
private static ByteBuf encodeSubscribeMessage( private static ByteBuf encodeSubscribeMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttSubscribeMessage message) { MqttSubscribeMessage message) {
int variableHeaderBufferSize = 2; MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
int payloadBufferSize = 0; ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
MqttFixedHeader mqttFixedHeader = message.fixedHeader(); try {
MqttMessageIdVariableHeader variableHeader = message.variableHeader(); final int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
MqttSubscribePayload payload = message.payload(); int payloadBufferSize = 0;
for (MqttTopicSubscription topic : payload.topicSubscriptions()) { MqttFixedHeader mqttFixedHeader = message.fixedHeader();
String topicName = topic.topicName(); MqttMessageIdVariableHeader variableHeader = message.variableHeader();
byte[] topicNameBytes = encodeStringUtf8(topicName); MqttSubscribePayload payload = message.payload();
payloadBufferSize += 2 + topicNameBytes.length;
payloadBufferSize += 1; for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
String topicName = topic.topicName();
int topicNameBytes = utf8Bytes(topicName);
payloadBufferSize += 2 + topicNameBytes;
payloadBufferSize += 1;
}
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
buf.writeBytes(propertiesBuf);
// Payload
for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
writeUnsafeUTF8String(buf, topic.topicName());
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) {
buf.writeByte(topic.qualityOfService().value());
} else {
final MqttSubscriptionOption option = topic.option();
int optionEncoded = option.retainHandling().value() << 4;
if (option.isRetainAsPublished()) {
optionEncoded |= 0x08;
}
if (option.isNoLocal()) {
optionEncoded |= 0x04;
}
optionEncoded |= option.qos().value();
buf.writeByte(optionEncoded);
}
}
return buf;
} finally {
propertiesBuf.release();
} }
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
// Payload
for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
String topicName = topic.topicName();
byte[] topicNameBytes = encodeStringUtf8(topicName);
buf.writeShort(topicNameBytes.length);
buf.writeBytes(topicNameBytes, 0, topicNameBytes.length);
buf.writeByte(topic.qualityOfService().value());
}
return buf;
} }
private static ByteBuf encodeUnsubscribeMessage( private static ByteBuf encodeUnsubscribeMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttUnsubscribeMessage message) { MqttUnsubscribeMessage message) {
int variableHeaderBufferSize = 2; MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
int payloadBufferSize = 0; ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
MqttFixedHeader mqttFixedHeader = message.fixedHeader(); try {
MqttMessageIdVariableHeader variableHeader = message.variableHeader(); final int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
MqttUnsubscribePayload payload = message.payload(); int payloadBufferSize = 0;
for (String topicName : payload.topics()) { MqttFixedHeader mqttFixedHeader = message.fixedHeader();
byte[] topicNameBytes = encodeStringUtf8(topicName); MqttMessageIdVariableHeader variableHeader = message.variableHeader();
payloadBufferSize += 2 + topicNameBytes.length; MqttUnsubscribePayload payload = message.payload();
for (String topicName : payload.topics()) {
int topicNameBytes = utf8Bytes(topicName);
payloadBufferSize += 2 + topicNameBytes;
}
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
buf.writeBytes(propertiesBuf);
// Payload
for (String topicName : payload.topics()) {
writeUnsafeUTF8String(buf, topicName);
}
return buf;
} finally {
propertiesBuf.release();
} }
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
// Payload
for (String topicName : payload.topics()) {
byte[] topicNameBytes = encodeStringUtf8(topicName);
buf.writeShort(topicNameBytes.length);
buf.writeBytes(topicNameBytes, 0, topicNameBytes.length);
}
return buf;
} }
private static ByteBuf encodeSubAckMessage( private static ByteBuf encodeSubAckMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttSubAckMessage message) { MqttSubAckMessage message) {
int variableHeaderBufferSize = 2; MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
int payloadBufferSize = message.payload().grantedQoSLevels().size(); ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
int variablePartSize = variableHeaderBufferSize + payloadBufferSize; ctx.alloc(),
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize); message.idAndPropertiesVariableHeader().properties());
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize); try {
buf.writeByte(getFixedHeaderByte1(message.fixedHeader())); int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
writeVariableLengthInt(buf, variablePartSize); int payloadBufferSize = message.payload().grantedQoSLevels().size();
buf.writeShort(message.variableHeader().messageId()); int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
for (int qos : message.payload().grantedQoSLevels()) { int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
buf.writeByte(qos); ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
} buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, variablePartSize);
buf.writeShort(message.variableHeader().messageId());
buf.writeBytes(propertiesBuf);
for (int code: message.payload().reasonCodes()) {
buf.writeByte(code);
}
return buf; return buf;
} finally {
propertiesBuf.release();
}
}
private static ByteBuf encodeUnsubAckMessage(
ChannelHandlerContext ctx,
MqttUnsubAckMessage message) {
if (message.variableHeader() instanceof MqttMessageIdAndPropertiesVariableHeader) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
try {
int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
MqttUnsubAckPayload payload = message.payload();
int payloadBufferSize = payload == null ? 0 : payload.unsubscribeReasonCodes().size();
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, variablePartSize);
buf.writeShort(message.variableHeader().messageId());
buf.writeBytes(propertiesBuf);
if (payload != null) {
for (Short reasonCode : payload.unsubscribeReasonCodes()) {
buf.writeByte(reasonCode);
}
}
return buf;
} finally {
propertiesBuf.release();
}
} else {
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
}
} }
private static ByteBuf encodePublishMessage( private static ByteBuf encodePublishMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttPublishMessage message) { MqttPublishMessage message) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttFixedHeader mqttFixedHeader = message.fixedHeader(); MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttPublishVariableHeader variableHeader = message.variableHeader(); MqttPublishVariableHeader variableHeader = message.variableHeader();
ByteBuf payload = message.payload().duplicate(); ByteBuf payload = message.payload().duplicate();
String topicName = variableHeader.topicName(); String topicName = variableHeader.topicName();
byte[] topicNameBytes = encodeStringUtf8(topicName); int topicNameBytes = utf8Bytes(topicName);
int variableHeaderBufferSize = 2 + topicNameBytes.length + ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
(mqttFixedHeader.qosLevel().value() > 0 ? 2 : 0); ctx.alloc(),
int payloadBufferSize = payload.readableBytes(); message.variableHeader().properties());
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize); try {
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader)); int variableHeaderBufferSize = 2 + topicNameBytes +
writeVariableLengthInt(buf, variablePartSize); (mqttFixedHeader.qosLevel().value() > 0 ? 2 : 0) + propertiesBuf.readableBytes();
buf.writeShort(topicNameBytes.length); int payloadBufferSize = payload.readableBytes();
buf.writeBytes(topicNameBytes); int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
if (mqttFixedHeader.qosLevel().value() > 0) { int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
buf.writeShort(variableHeader.messageId());
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
writeExactUTF8String(buf, topicName, topicNameBytes);
if (mqttFixedHeader.qosLevel().value() > 0) {
buf.writeShort(variableHeader.packetId());
}
buf.writeBytes(propertiesBuf);
buf.writeBytes(payload);
return buf;
} finally {
propertiesBuf.release();
} }
buf.writeBytes(payload); }
return buf; private static ByteBuf encodePubReplyMessage(ChannelHandlerContext ctx,
MqttMessage message) {
if (message.variableHeader() instanceof MqttPubReplyMessageVariableHeader) {
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttPubReplyMessageVariableHeader variableHeader =
(MqttPubReplyMessageVariableHeader) message.variableHeader();
int msgId = variableHeader.messageId();
final ByteBuf propertiesBuf;
final boolean includeReasonCode;
final int variableHeaderBufferSize;
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
if (mqttVersion == MqttVersion.MQTT_5 &&
(variableHeader.reasonCode() != MqttPubReplyMessageVariableHeader.REASON_CODE_OK ||
!variableHeader.properties().isEmpty())) {
propertiesBuf = encodeProperties(ctx.alloc(), variableHeader.properties());
includeReasonCode = true;
variableHeaderBufferSize = 3 + propertiesBuf.readableBytes();
} else {
propertiesBuf = Unpooled.EMPTY_BUFFER;
includeReasonCode = false;
variableHeaderBufferSize = 2;
}
try {
final int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variableHeaderBufferSize);
buf.writeShort(msgId);
if (includeReasonCode) {
buf.writeByte(variableHeader.reasonCode());
}
buf.writeBytes(propertiesBuf);
return buf;
} finally {
propertiesBuf.release();
}
} else {
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
}
} }
private static ByteBuf encodeMessageWithOnlySingleByteFixedHeaderAndMessageId( private static ByteBuf encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(
@@ -342,12 +515,11 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
int msgId = variableHeader.messageId(); int msgId = variableHeader.messageId();
int variableHeaderBufferSize = 2; // variable part only has a message id int variableHeaderBufferSize = 2; // variable part only has a message id
ByteBuf payload = null; ByteBuf payload = null;
if (message.payload() != null) { if (message.payload() != null) {
payload = ((ByteBuf)message.payload()).duplicate(); payload = ((ByteBuf)message.payload()).duplicate();
variableHeaderBufferSize += payload.readableBytes(); variableHeaderBufferSize += payload.readableBytes();
} }
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize); int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variableHeaderBufferSize); ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader)); buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
@@ -355,11 +527,53 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
buf.writeShort(msgId); buf.writeShort(msgId);
if (payload != null) { if (payload != null) {
buf.writeBytes(payload); buf.writeBytes(payload);
} }
return buf; return buf;
} }
private static ByteBuf encodeReasonCodePlusPropertiesMessage(
ChannelHandlerContext ctx,
MqttMessage message) {
if (message.variableHeader() instanceof MqttReasonCodeAndPropertiesVariableHeader) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttReasonCodeAndPropertiesVariableHeader variableHeader =
(MqttReasonCodeAndPropertiesVariableHeader) message.variableHeader();
final ByteBuf propertiesBuf;
final boolean includeReasonCode;
final int variableHeaderBufferSize;
if (mqttVersion == MqttVersion.MQTT_5 &&
(variableHeader.reasonCode() != MqttReasonCodeAndPropertiesVariableHeader.REASON_CODE_OK ||
!variableHeader.properties().isEmpty())) {
propertiesBuf = encodeProperties(ctx.alloc(), variableHeader.properties());
includeReasonCode = true;
variableHeaderBufferSize = 1 + propertiesBuf.readableBytes();
} else {
propertiesBuf = Unpooled.EMPTY_BUFFER;
includeReasonCode = false;
variableHeaderBufferSize = 0;
}
try {
final int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variableHeaderBufferSize);
if (includeReasonCode) {
buf.writeByte(variableHeader.reasonCode());
}
buf.writeBytes(propertiesBuf);
return buf;
} finally {
propertiesBuf.release();
}
} else {
return encodeMessageWithOnlySingleByteFixedHeader(ctx.alloc(), message);
}
}
private static ByteBuf encodeMessageWithOnlySingleByteFixedHeader( private static ByteBuf encodeMessageWithOnlySingleByteFixedHeader(
ByteBufAllocator byteBufAllocator, ByteBufAllocator byteBufAllocator,
MqttMessage message) { MqttMessage message) {
@@ -371,6 +585,104 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
return buf; return buf;
} }
private static ByteBuf encodePropertiesIfNeeded(MqttVersion mqttVersion,
ByteBufAllocator byteBufAllocator,
MqttProperties mqttProperties) {
if (mqttVersion == MqttVersion.MQTT_5) {
return encodeProperties(byteBufAllocator, mqttProperties);
}
return Unpooled.EMPTY_BUFFER;
}
private static ByteBuf encodeProperties(ByteBufAllocator byteBufAllocator,
MqttProperties mqttProperties) {
ByteBuf propertiesHeaderBuf = byteBufAllocator.buffer();
// encode also the Properties part
try {
ByteBuf propertiesBuf = byteBufAllocator.buffer();
try {
for (MqttProperties.MqttProperty property : mqttProperties.listAll()) {
MqttProperties.MqttPropertyType propertyType =
MqttProperties.MqttPropertyType.valueOf(property.propertyId);
switch (propertyType) {
case PAYLOAD_FORMAT_INDICATOR:
case REQUEST_PROBLEM_INFORMATION:
case REQUEST_RESPONSE_INFORMATION:
case MAXIMUM_QOS:
case RETAIN_AVAILABLE:
case WILDCARD_SUBSCRIPTION_AVAILABLE:
case SUBSCRIPTION_IDENTIFIER_AVAILABLE:
case SHARED_SUBSCRIPTION_AVAILABLE:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final byte bytePropValue = ((MqttProperties.IntegerProperty) property).value.byteValue();
propertiesBuf.writeByte(bytePropValue);
break;
case SERVER_KEEP_ALIVE:
case RECEIVE_MAXIMUM:
case TOPIC_ALIAS_MAXIMUM:
case TOPIC_ALIAS:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final short twoBytesInPropValue =
((MqttProperties.IntegerProperty) property).value.shortValue();
propertiesBuf.writeShort(twoBytesInPropValue);
break;
case PUBLICATION_EXPIRY_INTERVAL:
case SESSION_EXPIRY_INTERVAL:
case WILL_DELAY_INTERVAL:
case MAXIMUM_PACKET_SIZE:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final int fourBytesIntPropValue = ((MqttProperties.IntegerProperty) property).value;
propertiesBuf.writeInt(fourBytesIntPropValue);
break;
case SUBSCRIPTION_IDENTIFIER:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final int vbi = ((MqttProperties.IntegerProperty) property).value;
writeVariableLengthInt(propertiesBuf, vbi);
break;
case CONTENT_TYPE:
case RESPONSE_TOPIC:
case ASSIGNED_CLIENT_IDENTIFIER:
case AUTHENTICATION_METHOD:
case RESPONSE_INFORMATION:
case SERVER_REFERENCE:
case REASON_STRING:
writeVariableLengthInt(propertiesBuf, property.propertyId);
writeEagerUTF8String(propertiesBuf, ((MqttProperties.StringProperty) property).value);
break;
case USER_PROPERTY:
final List<MqttProperties.StringPair> pairs =
((MqttProperties.UserProperties) property).value;
for (MqttProperties.StringPair pair : pairs) {
writeVariableLengthInt(propertiesBuf, property.propertyId);
writeEagerUTF8String(propertiesBuf, pair.key);
writeEagerUTF8String(propertiesBuf, pair.value);
}
break;
case CORRELATION_DATA:
case AUTHENTICATION_DATA:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final byte[] binaryPropValue = ((MqttProperties.BinaryProperty) property).value;
propertiesBuf.writeShort(binaryPropValue.length);
propertiesBuf.writeBytes(binaryPropValue, 0, binaryPropValue.length);
break;
default:
//shouldn't reach here
throw new EncoderException("Unknown property type: " + propertyType);
}
}
writeVariableLengthInt(propertiesHeaderBuf, propertiesBuf.readableBytes());
propertiesHeaderBuf.writeBytes(propertiesBuf);
return propertiesHeaderBuf;
} finally {
propertiesBuf.release();
}
} catch (RuntimeException e) {
propertiesHeaderBuf.release();
throw e;
}
}
private static int getFixedHeaderByte1(MqttFixedHeader header) { private static int getFixedHeaderByte1(MqttFixedHeader header) {
int ret = 0; int ret = 0;
ret |= header.messageType().value() << 4; ret |= header.messageType().value() << 4;
@@ -395,6 +707,42 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} while (num > 0); } while (num > 0);
} }
private static int nullableUtf8Bytes(String s) {
return s == null? 0 : utf8Bytes(s);
}
private static int nullableMaxUtf8Bytes(String s) {
return s == null? 0 : utf8MaxBytes(s);
}
private static void writeExactUTF8String(ByteBuf buf, String s, int utf8Length) {
buf.ensureWritable(utf8Length + 2);
buf.writeShort(utf8Length);
if (utf8Length > 0) {
final int writtenUtf8Length = reserveAndWriteUtf8(buf, s, utf8Length);
assert writtenUtf8Length == utf8Length;
}
}
private static void writeEagerUTF8String(ByteBuf buf, String s) {
final int maxUtf8Length = nullableMaxUtf8Bytes(s);
buf.ensureWritable(maxUtf8Length + 2);
final int writerIndex = buf.writerIndex();
final int startUtf8String = writerIndex + 2;
buf.writerIndex(startUtf8String);
final int utf8Length = s != null? reserveAndWriteUtf8(buf, s, maxUtf8Length) : 0;
buf.setShort(writerIndex, utf8Length);
}
private static void writeUnsafeUTF8String(ByteBuf buf, String s) {
final int writerIndex = buf.writerIndex();
final int startUtf8String = writerIndex + 2;
// no need to reserve any capacity here, already done earlier: that's why is Unsafe
buf.writerIndex(startUtf8String);
final int utf8Length = s != null? reserveAndWriteUtf8(buf, s, 0) : 0;
buf.setShort(writerIndex, utf8Length);
}
private static int getVariableLengthInt(int num) { private static int getVariableLengthInt(int num) {
int count = 0; int count = 0;
do { do {
@@ -404,7 +752,4 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
return count; return count;
} }
private static byte[] encodeStringUtf8(String s) {
return s.getBytes(CharsetUtil.UTF_8);
}
} }
+2 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -20,7 +20,7 @@ import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#fixed-header"> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#fixed-header">
* MQTTV3.1/fixed-header</a> * MQTTV3.1/fixed-header</a>
*/ */
public final class MqttFixedHeader { public final class MqttFixedHeader {
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+12 -1
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -29,6 +29,17 @@ public class MqttMessage {
private final Object payload; private final Object payload;
private final DecoderResult decoderResult; private final DecoderResult decoderResult;
// Constants for fixed-header only message types with all flags set to 0 (see
// https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_2.2_-)
public static final MqttMessage PINGREQ = new MqttMessage(new MqttFixedHeader(MqttMessageType.PINGREQ, false,
MqttQoS.AT_MOST_ONCE, false, 0));
public static final MqttMessage PINGRESP = new MqttMessage(new MqttFixedHeader(MqttMessageType.PINGRESP, false,
MqttQoS.AT_MOST_ONCE, false, 0));
public static final MqttMessage DISCONNECT = new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false,
MqttQoS.AT_MOST_ONCE, false, 0));
public MqttMessage(MqttFixedHeader mqttFixedHeader) { public MqttMessage(MqttFixedHeader mqttFixedHeader) {
this(mqttFixedHeader, null, null); this(mqttFixedHeader, null, null);
} }
+493 -14
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -15,10 +15,14 @@
*/ */
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
public final class MqttMessageBuilders { public final class MqttMessageBuilders {
@@ -29,6 +33,7 @@ public final class MqttMessageBuilders {
private MqttQoS qos; private MqttQoS qos;
private ByteBuf payload; private ByteBuf payload;
private int messageId; private int messageId;
private MqttProperties mqttProperties;
PublishBuilder() { PublishBuilder() {
} }
@@ -58,9 +63,15 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public PublishBuilder properties(MqttProperties properties) {
this.mqttProperties = properties;
return this;
}
public MqttPublishMessage build() { public MqttPublishMessage build() {
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retained, 0); MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retained, 0);
MqttPublishVariableHeader mqttVariableHeader = new MqttPublishVariableHeader(topic, messageId); MqttPublishVariableHeader mqttVariableHeader =
new MqttPublishVariableHeader(topic, messageId, mqttProperties);
return new MqttPublishMessage(mqttFixedHeader, mqttVariableHeader, Unpooled.buffer().writeBytes(payload)); return new MqttPublishMessage(mqttFixedHeader, mqttVariableHeader, Unpooled.buffer().writeBytes(payload));
} }
} }
@@ -73,13 +84,15 @@ public final class MqttMessageBuilders {
private boolean hasUser; private boolean hasUser;
private boolean hasPassword; private boolean hasPassword;
private int keepAliveSecs; private int keepAliveSecs;
private MqttProperties willProperties = MqttProperties.NO_PROPERTIES;
private boolean willFlag; private boolean willFlag;
private boolean willRetain; private boolean willRetain;
private MqttQoS willQos = MqttQoS.AT_MOST_ONCE; private MqttQoS willQos = MqttQoS.AT_MOST_ONCE;
private String willTopic; private String willTopic;
private String willMessage; private byte[] willMessage;
private String username; private String username;
private byte[] password; private byte[] password;
private MqttProperties properties = MqttProperties.NO_PROPERTIES;
ConnectBuilder() { ConnectBuilder() {
} }
@@ -119,7 +132,16 @@ public final class MqttMessageBuilders {
return this; return this;
} }
/**
* @deprecated use {@link ConnectBuilder#willMessage(byte[])} instead
*/
@Deprecated
public ConnectBuilder willMessage(String willMessage) { public ConnectBuilder willMessage(String willMessage) {
willMessage(willMessage == null ? null : willMessage.getBytes(CharsetUtil.UTF_8));
return this;
}
public ConnectBuilder willMessage(byte[] willMessage) {
this.willMessage = willMessage; this.willMessage = willMessage;
return this; return this;
} }
@@ -129,6 +151,11 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public ConnectBuilder willProperties(MqttProperties willProperties) {
this.willProperties = willProperties;
return this;
}
public ConnectBuilder hasUser(boolean value) { public ConnectBuilder hasUser(boolean value) {
this.hasUser = value; this.hasUser = value;
return this; return this;
@@ -140,17 +167,31 @@ public final class MqttMessageBuilders {
} }
public ConnectBuilder username(String username) { public ConnectBuilder username(String username) {
this.hasUser = true; this.hasUser = username != null;
this.username = username; this.username = username;
return this; return this;
} }
/**
* @deprecated use {@link ConnectBuilder#password(byte[])} instead
*/
@Deprecated
public ConnectBuilder password(String password) {
password(password == null ? null : password.getBytes(CharsetUtil.UTF_8));
return this;
}
public ConnectBuilder password(byte[] password) { public ConnectBuilder password(byte[] password) {
this.hasPassword = true; this.hasPassword = password != null;
this.password = password; this.password = password;
return this; return this;
} }
public ConnectBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttConnectMessage build() { public MqttConnectMessage build() {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
@@ -164,9 +205,10 @@ public final class MqttMessageBuilders {
willQos.value(), willQos.value(),
willFlag, willFlag,
cleanSession, cleanSession,
keepAliveSecs); keepAliveSecs,
properties);
MqttConnectPayload mqttConnectPayload = MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload(clientId, willTopic, willMessage, username, password); new MqttConnectPayload(clientId, willProperties, willTopic, willMessage, username, password, null);
return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload); return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload);
} }
} }
@@ -175,36 +217,54 @@ public final class MqttMessageBuilders {
private List<MqttTopicSubscription> subscriptions; private List<MqttTopicSubscription> subscriptions;
private int messageId; private int messageId;
private MqttProperties properties;
SubscribeBuilder() { SubscribeBuilder() {
} }
public SubscribeBuilder addSubscription(MqttQoS qos, String topic) { public SubscribeBuilder addSubscription(MqttQoS qos, String topic) {
if (subscriptions == null) { ensureSubscriptionsExist();
subscriptions = new ArrayList<MqttTopicSubscription>(5);
}
subscriptions.add(new MqttTopicSubscription(topic, qos)); subscriptions.add(new MqttTopicSubscription(topic, qos));
return this; return this;
} }
public SubscribeBuilder addSubscription(String topic, MqttSubscriptionOption option) {
ensureSubscriptionsExist();
subscriptions.add(new MqttTopicSubscription(topic, option));
return this;
}
public SubscribeBuilder messageId(int messageId) { public SubscribeBuilder messageId(int messageId) {
this.messageId = messageId; this.messageId = messageId;
return this; return this;
} }
public SubscribeBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttSubscribeMessage build() { public MqttSubscribeMessage build() {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(messageId); MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(messageId, properties);
MqttSubscribePayload mqttSubscribePayload = new MqttSubscribePayload(subscriptions); MqttSubscribePayload mqttSubscribePayload = new MqttSubscribePayload(subscriptions);
return new MqttSubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload); return new MqttSubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
} }
private void ensureSubscriptionsExist() {
if (subscriptions == null) {
subscriptions = new ArrayList<MqttTopicSubscription>(5);
}
}
} }
public static final class UnsubscribeBuilder { public static final class UnsubscribeBuilder {
private List<String> topicFilters; private List<String> topicFilters;
private int messageId; private int messageId;
private MqttProperties properties;
UnsubscribeBuilder() { UnsubscribeBuilder() {
} }
@@ -222,21 +282,33 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public UnsubscribeBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttUnsubscribeMessage build() { public MqttUnsubscribeMessage build() {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(messageId); MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(messageId, properties);
MqttUnsubscribePayload mqttSubscribePayload = new MqttUnsubscribePayload(topicFilters); MqttUnsubscribePayload mqttSubscribePayload = new MqttUnsubscribePayload(topicFilters);
return new MqttUnsubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload); return new MqttUnsubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
} }
} }
public interface PropertiesInitializer<T> {
void apply(T builder);
}
public static final class ConnAckBuilder { public static final class ConnAckBuilder {
private MqttConnectReturnCode returnCode; private MqttConnectReturnCode returnCode;
private boolean sessionPresent; private boolean sessionPresent;
private MqttProperties properties = MqttProperties.NO_PROPERTIES;
private ConnAckPropertiesBuilder propsBuilder;
ConnAckBuilder() { private ConnAckBuilder() {
} }
public ConnAckBuilder returnCode(MqttConnectReturnCode returnCode) { public ConnAckBuilder returnCode(MqttConnectReturnCode returnCode) {
@@ -249,15 +321,402 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public ConnAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public ConnAckBuilder properties(PropertiesInitializer<ConnAckPropertiesBuilder> consumer) {
if (propsBuilder == null) {
propsBuilder = new ConnAckPropertiesBuilder();
}
consumer.apply(propsBuilder);
return this;
}
public MqttConnAckMessage build() { public MqttConnAckMessage build() {
if (propsBuilder != null) {
properties = propsBuilder.build();
}
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttConnAckVariableHeader mqttConnAckVariableHeader = MqttConnAckVariableHeader mqttConnAckVariableHeader =
new MqttConnAckVariableHeader(returnCode, sessionPresent); new MqttConnAckVariableHeader(returnCode, sessionPresent, properties);
return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader);
} }
} }
public static final class ConnAckPropertiesBuilder {
private String clientId;
private Long sessionExpiryInterval;
private int receiveMaximum;
private Byte maximumQos;
private boolean retain;
private Long maximumPacketSize;
private int topicAliasMaximum;
private String reasonString;
private final MqttProperties.UserProperties userProperties = new MqttProperties.UserProperties();
private Boolean wildcardSubscriptionAvailable;
private Boolean subscriptionIdentifiersAvailable;
private Boolean sharedSubscriptionAvailable;
private Integer serverKeepAlive;
private String responseInformation;
private String serverReference;
private String authenticationMethod;
private byte[] authenticationData;
public MqttProperties build() {
final MqttProperties props = new MqttProperties();
if (clientId != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.ASSIGNED_CLIENT_IDENTIFIER.value(),
clientId));
}
if (sessionExpiryInterval != null) {
props.add(new MqttProperties.IntegerProperty(
MqttProperties.MqttPropertyType.SESSION_EXPIRY_INTERVAL.value(), sessionExpiryInterval.intValue()));
}
if (receiveMaximum > 0) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.RECEIVE_MAXIMUM.value(), receiveMaximum));
}
if (maximumQos != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.MAXIMUM_QOS.value(), receiveMaximum));
}
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.RETAIN_AVAILABLE.value(), retain ? 1 : 0));
if (maximumPacketSize != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.MAXIMUM_PACKET_SIZE.value(),
maximumPacketSize.intValue()));
}
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS_MAXIMUM.value(),
topicAliasMaximum));
if (reasonString != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.REASON_STRING.value(), reasonString));
}
props.add(userProperties);
if (wildcardSubscriptionAvailable != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.WILDCARD_SUBSCRIPTION_AVAILABLE.value(),
wildcardSubscriptionAvailable ? 1 : 0));
}
if (subscriptionIdentifiersAvailable != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SUBSCRIPTION_IDENTIFIER_AVAILABLE.value(),
subscriptionIdentifiersAvailable ? 1 : 0));
}
if (sharedSubscriptionAvailable != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SHARED_SUBSCRIPTION_AVAILABLE.value(),
sharedSubscriptionAvailable ? 1 : 0));
}
if (serverKeepAlive != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SERVER_KEEP_ALIVE.value(),
serverKeepAlive));
}
if (responseInformation != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.RESPONSE_INFORMATION.value(),
responseInformation));
}
if (serverReference != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.SERVER_REFERENCE.value(),
serverReference));
}
if (authenticationMethod != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_METHOD.value(),
authenticationMethod));
}
if (authenticationData != null) {
props.add(new MqttProperties.BinaryProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_DATA.value(),
authenticationData));
}
return props;
}
public ConnAckPropertiesBuilder sessionExpiryInterval(long seconds) {
this.sessionExpiryInterval = seconds;
return this;
}
public ConnAckPropertiesBuilder receiveMaximum(int value) {
this.receiveMaximum = checkPositive(value, "value");
return this;
}
public ConnAckPropertiesBuilder maximumQos(byte value) {
if (value != 0 && value != 1) {
throw new IllegalArgumentException("maximum QoS property could be 0 or 1");
}
this.maximumQos = value;
return this;
}
public ConnAckPropertiesBuilder retainAvailable(boolean retain) {
this.retain = retain;
return this;
}
public ConnAckPropertiesBuilder maximumPacketSize(long size) {
this.maximumPacketSize = checkPositive(size, "size");
return this;
}
public ConnAckPropertiesBuilder assignedClientId(String clientId) {
this.clientId = clientId;
return this;
}
public ConnAckPropertiesBuilder topicAliasMaximum(int value) {
this.topicAliasMaximum = value;
return this;
}
public ConnAckPropertiesBuilder reasonString(String reason) {
this.reasonString = reason;
return this;
}
public ConnAckPropertiesBuilder userProperty(String name, String value) {
userProperties.add(name, value);
return this;
}
public ConnAckPropertiesBuilder wildcardSubscriptionAvailable(boolean value) {
this.wildcardSubscriptionAvailable = value;
return this;
}
public ConnAckPropertiesBuilder subscriptionIdentifiersAvailable(boolean value) {
this.subscriptionIdentifiersAvailable = value;
return this;
}
public ConnAckPropertiesBuilder sharedSubscriptionAvailable(boolean value) {
this.sharedSubscriptionAvailable = value;
return this;
}
public ConnAckPropertiesBuilder serverKeepAlive(int seconds) {
this.serverKeepAlive = seconds;
return this;
}
public ConnAckPropertiesBuilder responseInformation(String value) {
this.responseInformation = value;
return this;
}
public ConnAckPropertiesBuilder serverReference(String host) {
this.serverReference = host;
return this;
}
public ConnAckPropertiesBuilder authenticationMethod(String methodName) {
this.authenticationMethod = methodName;
return this;
}
public ConnAckPropertiesBuilder authenticationData(byte[] rawData) {
this.authenticationData = rawData.clone();
return this;
}
}
public static final class PubAckBuilder {
private int packetId;
private byte reasonCode;
private MqttProperties properties;
PubAckBuilder() {
}
public PubAckBuilder reasonCode(byte reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public PubAckBuilder packetId(int packetId) {
this.packetId = packetId;
return this;
}
/**
* @deprecated use {@link PubAckBuilder#packetId(int)} instead
*/
@Deprecated
public PubAckBuilder packetId(short packetId) {
return packetId(packetId & 0xFFFF);
}
public PubAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttPubReplyMessageVariableHeader mqttPubAckVariableHeader =
new MqttPubReplyMessageVariableHeader(packetId, reasonCode, properties);
return new MqttMessage(mqttFixedHeader, mqttPubAckVariableHeader);
}
}
public static final class SubAckBuilder {
private int packetId;
private MqttProperties properties;
private final List<MqttQoS> grantedQoses = new ArrayList<MqttQoS>();
SubAckBuilder() {
}
public SubAckBuilder packetId(int packetId) {
this.packetId = packetId;
return this;
}
/**
* @deprecated use {@link SubAckBuilder#packetId(int)} instead
*/
@Deprecated
public SubAckBuilder packetId(short packetId) {
return packetId(packetId & 0xFFFF);
}
public SubAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public SubAckBuilder addGrantedQos(MqttQoS qos) {
this.grantedQoses.add(qos);
return this;
}
public SubAckBuilder addGrantedQoses(MqttQoS... qoses) {
this.grantedQoses.addAll(Arrays.asList(qoses));
return this;
}
public MqttSubAckMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttMessageIdAndPropertiesVariableHeader mqttSubAckVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(packetId, properties);
//transform to primitive types
int[] grantedQoses = new int[this.grantedQoses.size()];
int i = 0;
for (MqttQoS grantedQos : this.grantedQoses) {
grantedQoses[i++] = grantedQos.value();
}
MqttSubAckPayload subAckPayload = new MqttSubAckPayload(grantedQoses);
return new MqttSubAckMessage(mqttFixedHeader, mqttSubAckVariableHeader, subAckPayload);
}
}
public static final class UnsubAckBuilder {
private int packetId;
private MqttProperties properties;
private final List<Short> reasonCodes = new ArrayList<Short>();
UnsubAckBuilder() {
}
public UnsubAckBuilder packetId(int packetId) {
this.packetId = packetId;
return this;
}
/**
* @deprecated use {@link UnsubAckBuilder#packetId(int)} instead
*/
@Deprecated
public UnsubAckBuilder packetId(short packetId) {
return packetId(packetId & 0xFFFF);
}
public UnsubAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public UnsubAckBuilder addReasonCode(short reasonCode) {
this.reasonCodes.add(reasonCode);
return this;
}
public UnsubAckBuilder addReasonCodes(Short... reasonCodes) {
this.reasonCodes.addAll(Arrays.asList(reasonCodes));
return this;
}
public MqttUnsubAckMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.UNSUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttMessageIdAndPropertiesVariableHeader mqttSubAckVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(packetId, properties);
MqttUnsubAckPayload subAckPayload = new MqttUnsubAckPayload(reasonCodes);
return new MqttUnsubAckMessage(mqttFixedHeader, mqttSubAckVariableHeader, subAckPayload);
}
}
public static final class DisconnectBuilder {
private MqttProperties properties;
private byte reasonCode;
DisconnectBuilder() {
}
public DisconnectBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public DisconnectBuilder reasonCode(byte reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public MqttMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttReasonCodeAndPropertiesVariableHeader mqttDisconnectVariableHeader =
new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, properties);
return new MqttMessage(mqttFixedHeader, mqttDisconnectVariableHeader);
}
}
public static final class AuthBuilder {
private MqttProperties properties;
private byte reasonCode;
AuthBuilder() {
}
public AuthBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public AuthBuilder reasonCode(byte reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public MqttMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.AUTH, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttReasonCodeAndPropertiesVariableHeader mqttAuthVariableHeader =
new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, properties);
return new MqttMessage(mqttFixedHeader, mqttAuthVariableHeader);
}
}
public static ConnectBuilder connect() { public static ConnectBuilder connect() {
return new ConnectBuilder(); return new ConnectBuilder();
} }
@@ -278,6 +737,26 @@ public final class MqttMessageBuilders {
return new UnsubscribeBuilder(); return new UnsubscribeBuilder();
} }
public static PubAckBuilder pubAck() {
return new PubAckBuilder();
}
public static SubAckBuilder subAck() {
return new SubAckBuilder();
}
public static UnsubAckBuilder unsubAck() {
return new UnsubAckBuilder();
}
public static DisconnectBuilder disconnect() {
return new DisconnectBuilder();
}
public static AuthBuilder auth() {
return new AuthBuilder();
}
private MqttMessageBuilders() { private MqttMessageBuilders() {
} }
} }
+16 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -50,7 +50,8 @@ public final class MqttMessageFactory {
case UNSUBACK: case UNSUBACK:
return new MqttUnsubAckMessage( return new MqttUnsubAckMessage(
mqttFixedHeader, mqttFixedHeader,
(MqttMessageIdVariableHeader) variableHeader); (MqttMessageIdVariableHeader) variableHeader,
(MqttUnsubAckPayload) payload);
case UNSUBSCRIBE: case UNSUBSCRIBE:
return new MqttUnsubscribeMessage( return new MqttUnsubscribeMessage(
@@ -65,17 +66,24 @@ public final class MqttMessageFactory {
(ByteBuf) payload); (ByteBuf) payload);
case PUBACK: case PUBACK:
//Having MqttPubReplyMessageVariableHeader or MqttMessageIdVariableHeader
return new MqttPubAckMessage(mqttFixedHeader, (MqttMessageIdVariableHeader) variableHeader); return new MqttPubAckMessage(mqttFixedHeader, (MqttMessageIdVariableHeader) variableHeader);
case PUBREC: case PUBREC:
case PUBREL: case PUBREL:
case PUBCOMP: case PUBCOMP:
//Having MqttPubReplyMessageVariableHeader or MqttMessageIdVariableHeader
return new MqttMessage(mqttFixedHeader, variableHeader); return new MqttMessage(mqttFixedHeader, variableHeader);
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
case DISCONNECT:
return new MqttMessage(mqttFixedHeader); return new MqttMessage(mqttFixedHeader);
case DISCONNECT:
case AUTH:
//Having MqttReasonCodeAndPropertiesVariableHeader
return new MqttMessage(mqttFixedHeader,
variableHeader);
default: default:
throw new IllegalArgumentException("unknown message type: " + mqttFixedHeader.messageType()); throw new IllegalArgumentException("unknown message type: " + mqttFixedHeader.messageType());
} }
@@ -85,5 +93,10 @@ public final class MqttMessageFactory {
return new MqttMessage(null, null, null, DecoderResult.failure(cause)); return new MqttMessage(null, null, null, DecoderResult.failure(cause));
} }
public static MqttMessage newInvalidMessage(MqttFixedHeader mqttFixedHeader, Object variableHeader,
Throwable cause) {
return new MqttMessage(mqttFixedHeader, variableHeader, null, DecoderResult.failure(cause));
}
private MqttMessageFactory() { } private MqttMessageFactory() { }
} }
@@ -0,0 +1,52 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.StringUtil;
/**
* Variable Header containing, Packet Id and Properties as in MQTT v5 spec.
*/
public final class MqttMessageIdAndPropertiesVariableHeader extends MqttMessageIdVariableHeader {
private final MqttProperties properties;
public MqttMessageIdAndPropertiesVariableHeader(int messageId, MqttProperties properties) {
super(messageId);
if (messageId < 1 || messageId > 0xffff) {
throw new IllegalArgumentException("messageId: " + messageId + " (expected: 1 ~ 65535)");
}
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
return StringUtil.simpleClassName(this) + "[" +
"messageId=" + messageId() +
", properties=" + properties +
']';
}
@Override
MqttMessageIdAndPropertiesVariableHeader withDefaultEmptyProperties() {
return this;
}
}
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -20,9 +20,9 @@ import io.netty.util.internal.StringUtil;
/** /**
* Variable Header containing only Message Id * Variable Header containing only Message Id
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#msg-id">MQTTV3.1/msg-id</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#msg-id">MQTTV3.1/msg-id</a>
*/ */
public final class MqttMessageIdVariableHeader { public class MqttMessageIdVariableHeader {
private final int messageId; private final int messageId;
@@ -33,7 +33,7 @@ public final class MqttMessageIdVariableHeader {
return new MqttMessageIdVariableHeader(messageId); return new MqttMessageIdVariableHeader(messageId);
} }
private MqttMessageIdVariableHeader(int messageId) { protected MqttMessageIdVariableHeader(int messageId) {
this.messageId = messageId; this.messageId = messageId;
} }
@@ -49,4 +49,12 @@ public final class MqttMessageIdVariableHeader {
.append(']') .append(']')
.toString(); .toString();
} }
public MqttMessageIdAndPropertiesVariableHeader withEmptyProperties() {
return new MqttMessageIdAndPropertiesVariableHeader(messageId, MqttProperties.NO_PROPERTIES);
}
MqttMessageIdAndPropertiesVariableHeader withDefaultEmptyProperties() {
return withEmptyProperties();
}
} }
+22 -7
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -33,7 +33,24 @@ public enum MqttMessageType {
UNSUBACK(11), UNSUBACK(11),
PINGREQ(12), PINGREQ(12),
PINGRESP(13), PINGRESP(13),
DISCONNECT(14); DISCONNECT(14),
AUTH(15);
private static final MqttMessageType[] VALUES;
static {
// this prevent values to be assigned with the wrong order
// and ensure valueOf to work fine
final MqttMessageType[] values = values();
VALUES = new MqttMessageType[values.length + 1];
for (MqttMessageType mqttMessageType : values) {
final int value = mqttMessageType.value;
if (VALUES[value] != null) {
throw new AssertionError("value already in use: " + value);
}
VALUES[value] = mqttMessageType;
}
}
private final int value; private final int value;
@@ -46,12 +63,10 @@ public enum MqttMessageType {
} }
public static MqttMessageType valueOf(int type) { public static MqttMessageType valueOf(int type) {
for (MqttMessageType t : values()) { if (type <= 0 || type >= VALUES.length) {
if (t.value == type) { throw new IllegalArgumentException("unknown message type: " + type);
return t;
}
} }
throw new IllegalArgumentException("unknown message type: " + type); return VALUES[type];
} }
} }
@@ -0,0 +1,414 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.collection.IntObjectHashMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
/**
* MQTT Properties container
* */
public final class MqttProperties {
public enum MqttPropertyType {
// single byte properties
PAYLOAD_FORMAT_INDICATOR(0x01),
REQUEST_PROBLEM_INFORMATION(0x17),
REQUEST_RESPONSE_INFORMATION(0x19),
MAXIMUM_QOS(0x24),
RETAIN_AVAILABLE(0x25),
WILDCARD_SUBSCRIPTION_AVAILABLE(0x28),
SUBSCRIPTION_IDENTIFIER_AVAILABLE(0x29),
SHARED_SUBSCRIPTION_AVAILABLE(0x2A),
// two bytes properties
SERVER_KEEP_ALIVE(0x13),
RECEIVE_MAXIMUM(0x21),
TOPIC_ALIAS_MAXIMUM(0x22),
TOPIC_ALIAS(0x23),
// four bytes properties
PUBLICATION_EXPIRY_INTERVAL(0x02),
SESSION_EXPIRY_INTERVAL(0x11),
WILL_DELAY_INTERVAL(0x18),
MAXIMUM_PACKET_SIZE(0x27),
// Variable Byte Integer
SUBSCRIPTION_IDENTIFIER(0x0B),
// UTF-8 Encoded String properties
CONTENT_TYPE(0x03),
RESPONSE_TOPIC(0x08),
ASSIGNED_CLIENT_IDENTIFIER(0x12),
AUTHENTICATION_METHOD(0x15),
RESPONSE_INFORMATION(0x1A),
SERVER_REFERENCE(0x1C),
REASON_STRING(0x1F),
USER_PROPERTY(0x26),
// Binary Data
CORRELATION_DATA(0x09),
AUTHENTICATION_DATA(0x16);
private static final MqttPropertyType[] VALUES;
static {
VALUES = new MqttPropertyType[43];
for (MqttPropertyType v : values()) {
VALUES[v.value] = v;
}
}
private final int value;
MqttPropertyType(int value) {
this.value = value;
}
public int value() {
return value;
}
public static MqttPropertyType valueOf(int type) {
MqttPropertyType t = null;
try {
t = VALUES[type];
} catch (ArrayIndexOutOfBoundsException ignored) {
// nop
}
if (t == null) {
throw new IllegalArgumentException("unknown property type: " + type);
}
return t;
}
}
public static final MqttProperties NO_PROPERTIES = new MqttProperties(false);
static MqttProperties withEmptyDefaults(MqttProperties properties) {
if (properties == null) {
return MqttProperties.NO_PROPERTIES;
}
return properties;
}
/**
* MQTT property base class
*
* @param <T> property type
*/
public abstract static class MqttProperty<T> {
final T value;
final int propertyId;
protected MqttProperty(int propertyId, T value) {
this.propertyId = propertyId;
this.value = value;
}
/**
* Get MQTT property value
*
* @return property value
*/
public T value() {
return value;
}
/**
* Get MQTT property ID
* @return property ID
*/
public int propertyId() {
return propertyId;
}
@Override
public int hashCode() {
return propertyId + 31 * value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MqttProperty that = (MqttProperty) obj;
return this.propertyId == that.propertyId && this.value.equals(that.value);
}
}
public static final class IntegerProperty extends MqttProperty<Integer> {
public IntegerProperty(int propertyId, Integer value) {
super(propertyId, value);
}
@Override
public String toString() {
return "IntegerProperty(" + propertyId + ", " + value + ")";
}
}
public static final class StringProperty extends MqttProperty<String> {
public StringProperty(int propertyId, String value) {
super(propertyId, value);
}
@Override
public String toString() {
return "StringProperty(" + propertyId + ", " + value + ")";
}
}
public static final class StringPair {
public final String key;
public final String value;
public StringPair(String key, String value) {
this.key = key;
this.value = value;
}
@Override
public int hashCode() {
return key.hashCode() + 31 * value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
StringPair that = (StringPair) obj;
return that.key.equals(this.key) && that.value.equals(this.value);
}
}
//User properties are the only properties that may be included multiple times and
//are the only properties where ordering is required. Therefore, they need a special handling
public static final class UserProperties extends MqttProperty<List<StringPair>> {
public UserProperties() {
super(MqttPropertyType.USER_PROPERTY.value, new ArrayList<StringPair>());
}
/**
* Create user properties from the collection of the String pair values
*
* @param values string pairs. Collection entries are copied, collection itself isn't shared
*/
public UserProperties(Collection<StringPair> values) {
this();
this.value.addAll(values);
}
private static UserProperties fromUserPropertyCollection(Collection<UserProperty> properties) {
UserProperties userProperties = new UserProperties();
for (UserProperty property: properties) {
userProperties.add(new StringPair(property.value.key, property.value.value));
}
return userProperties;
}
public void add(StringPair pair) {
this.value.add(pair);
}
public void add(String key, String value) {
this.value.add(new StringPair(key, value));
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("UserProperties(");
boolean first = true;
for (StringPair pair: value) {
if (!first) {
builder.append(", ");
}
builder.append(pair.key + "->" + pair.value);
first = false;
}
builder.append(")");
return builder.toString();
}
}
public static final class UserProperty extends MqttProperty<StringPair> {
public UserProperty(String key, String value) {
super(MqttPropertyType.USER_PROPERTY.value, new StringPair(key, value));
}
@Override
public String toString() {
return "UserProperty(" + value.key + ", " + value.value + ")";
}
}
public static final class BinaryProperty extends MqttProperty<byte[]> {
public BinaryProperty(int propertyId, byte[] value) {
super(propertyId, value);
}
@Override
public String toString() {
return "BinaryProperty(" + propertyId + ", " + value.length + " bytes)";
}
}
public MqttProperties() {
this(true);
}
private MqttProperties(boolean canModify) {
this.canModify = canModify;
}
private IntObjectHashMap<MqttProperty> props;
private List<UserProperty> userProperties;
private List<IntegerProperty> subscriptionIds;
private final boolean canModify;
public void add(MqttProperty property) {
if (!canModify) {
throw new UnsupportedOperationException("adding property isn't allowed");
}
IntObjectHashMap<MqttProperty> props = this.props;
if (property.propertyId == MqttPropertyType.USER_PROPERTY.value) {
List<UserProperty> userProperties = this.userProperties;
if (userProperties == null) {
userProperties = new ArrayList<UserProperty>(1);
this.userProperties = userProperties;
}
if (property instanceof UserProperty) {
userProperties.add((UserProperty) property);
} else if (property instanceof UserProperties) {
for (StringPair pair: ((UserProperties) property).value) {
userProperties.add(new UserProperty(pair.key, pair.value));
}
} else {
throw new IllegalArgumentException("User property must be of UserProperty or UserProperties type");
}
} else if (property.propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
List<IntegerProperty> subscriptionIds = this.subscriptionIds;
if (subscriptionIds == null) {
subscriptionIds = new ArrayList<IntegerProperty>(1);
this.subscriptionIds = subscriptionIds;
}
if (property instanceof IntegerProperty) {
subscriptionIds.add((IntegerProperty) property);
} else {
throw new IllegalArgumentException("Subscription ID must be an integer property");
}
} else {
if (props == null) {
props = new IntObjectHashMap<MqttProperty>();
this.props = props;
}
props.put(property.propertyId, property);
}
}
public Collection<? extends MqttProperty> listAll() {
IntObjectHashMap<MqttProperty> props = this.props;
if (props == null && subscriptionIds == null && userProperties == null) {
return Collections.<MqttProperty>emptyList();
}
if (subscriptionIds == null && userProperties == null) {
return props.values();
}
if (props == null && userProperties == null) {
return subscriptionIds;
}
List<MqttProperty> propValues = new ArrayList<MqttProperty>(props != null ? props.size() : 1);
if (props != null) {
propValues.addAll(props.values());
}
if (subscriptionIds != null) {
propValues.addAll(subscriptionIds);
}
if (userProperties != null) {
propValues.add(UserProperties.fromUserPropertyCollection(userProperties));
}
return propValues;
}
public boolean isEmpty() {
IntObjectHashMap<MqttProperty> props = this.props;
return props == null || props.isEmpty();
}
/**
* Get property by ID. If there are multiple properties of this type (can be with Subscription ID)
* then return the first one.
*
* @param propertyId ID of the property
* @return a property if it is set, null otherwise
*/
public MqttProperty getProperty(int propertyId) {
if (propertyId == MqttPropertyType.USER_PROPERTY.value) {
//special handling to keep compatibility with earlier versions
List<UserProperty> userProperties = this.userProperties;
if (userProperties == null) {
return null;
}
return UserProperties.fromUserPropertyCollection(userProperties);
}
if (propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
List<IntegerProperty> subscriptionIds = this.subscriptionIds;
if (subscriptionIds == null || subscriptionIds.isEmpty()) {
return null;
}
return subscriptionIds.get(0);
}
IntObjectHashMap<MqttProperty> props = this.props;
return props == null ? null : props.get(propertyId);
}
/**
* Get properties by ID.
* Some properties (Subscription ID and User Properties) may occur multiple times,
* this method returns all their values in order.
*
* @param propertyId ID of the property
* @return all properties having specified ID
*/
public List<? extends MqttProperty> getProperties(int propertyId) {
if (propertyId == MqttPropertyType.USER_PROPERTY.value) {
return userProperties == null ? Collections.<MqttProperty>emptyList() : userProperties;
}
if (propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
return subscriptionIds == null ? Collections.<MqttProperty>emptyList() : subscriptionIds;
}
IntObjectHashMap<MqttProperty> props = this.props;
return (props == null || !props.containsKey(propertyId)) ?
Collections.<MqttProperty>emptyList() :
Collections.singletonList(props.get(propertyId));
}
}
+2 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#puback">MQTTV3.1/puback</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#puback">MQTTV3.1/puback</a>
*/ */
public final class MqttPubAckMessage extends MqttMessage { public final class MqttPubAckMessage extends MqttMessage {
@@ -0,0 +1,56 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.StringUtil;
/**
* Variable Header containing Packet Id, reason code and Properties as in MQTT v5 spec.
*/
public final class MqttPubReplyMessageVariableHeader extends MqttMessageIdVariableHeader {
private final byte reasonCode;
private final MqttProperties properties;
public static final byte REASON_CODE_OK = 0;
public MqttPubReplyMessageVariableHeader(int messageId, byte reasonCode, MqttProperties properties) {
super(messageId);
if (messageId < 1 || messageId > 0xffff) {
throw new IllegalArgumentException("messageId: " + messageId + " (expected: 1 ~ 65535)");
}
this.reasonCode = reasonCode;
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public byte reasonCode() {
return reasonCode;
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
return StringUtil.simpleClassName(this) + "[" +
"messageId=" + messageId() +
", reasonCode=" + reasonCode +
", properties=" + properties +
']';
}
}
+4 -8
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -18,10 +18,10 @@ package io.netty.handler.codec.mqtt;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder; import io.netty.buffer.ByteBufHolder;
import io.netty.util.IllegalReferenceCountException; import io.netty.buffer.ByteBufUtil;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#publish">MQTTV3.1/publish</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#publish">MQTTV3.1/publish</a>
*/ */
public class MqttPublishMessage extends MqttMessage implements ByteBufHolder { public class MqttPublishMessage extends MqttMessage implements ByteBufHolder {
@@ -44,11 +44,7 @@ public class MqttPublishMessage extends MqttMessage implements ByteBufHolder {
@Override @Override
public ByteBuf content() { public ByteBuf content() {
final ByteBuf data = (ByteBuf) super.payload(); return ByteBufUtil.ensureAccessible((ByteBuf) super.payload());
if (data.refCnt() <= 0) {
throw new IllegalReferenceCountException(data.refCnt());
}
return data;
} }
@Override @Override
+16 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -24,11 +24,17 @@ import io.netty.util.internal.StringUtil;
public final class MqttPublishVariableHeader { public final class MqttPublishVariableHeader {
private final String topicName; private final String topicName;
private final int packetId; private int packetId;
private final MqttProperties properties;
public MqttPublishVariableHeader(String topicName, int packetId) { public MqttPublishVariableHeader(String topicName, int packetId) {
this(topicName, packetId, MqttProperties.NO_PROPERTIES);
}
public MqttPublishVariableHeader(String topicName, int packetId, MqttProperties properties) {
this.topicName = topicName; this.topicName = topicName;
this.packetId = packetId; this.packetId = packetId;
this.properties = MqttProperties.withEmptyDefaults(properties);
} }
public String topicName() { public String topicName() {
@@ -47,6 +53,14 @@ public final class MqttPublishVariableHeader {
return packetId; return packetId;
} }
public MqttProperties properties() {
return properties;
}
public void setPacketId(int packetId) {
this.packetId = packetId;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
+12 -6
View File
@@ -5,7 +5,7 @@
* "License"); you may not use this file except in compliance with the License. You may obtain a * "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at: * copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License * Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
@@ -32,11 +32,17 @@ public enum MqttQoS {
} }
public static MqttQoS valueOf(int value) { public static MqttQoS valueOf(int value) {
for (MqttQoS q: values()) { switch (value) {
if (q.value == value) { case 0:
return q; return AT_MOST_ONCE;
} case 1:
return AT_LEAST_ONCE;
case 2:
return EXACTLY_ONCE;
case 0x80:
return FAILURE;
default:
throw new IllegalArgumentException("invalid QoS: " + value);
} }
throw new IllegalArgumentException("invalid QoS: " + value);
} }
} }
@@ -0,0 +1,54 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.StringUtil;
/**
* Variable Header for AUTH and DISCONNECT messages represented by {@link MqttMessage}
*/
public final class MqttReasonCodeAndPropertiesVariableHeader {
private final byte reasonCode;
private final MqttProperties properties;
public static final byte REASON_CODE_OK = 0;
public MqttReasonCodeAndPropertiesVariableHeader(byte reasonCode,
MqttProperties properties) {
this.reasonCode = reasonCode;
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public byte reasonCode() {
return reasonCode;
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this))
.append('[')
.append("reasonCode=").append(reasonCode)
.append(", properties=").append(properties)
.append(']')
.toString();
}
}
+14 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,22 +17,33 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#suback">MQTTV3.1/suback</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#suback">MQTTV3.1/suback</a>
*/ */
public final class MqttSubAckMessage extends MqttMessage { public final class MqttSubAckMessage extends MqttMessage {
public MqttSubAckMessage( public MqttSubAckMessage(
MqttFixedHeader mqttFixedHeader, MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader, MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttSubAckPayload payload) { MqttSubAckPayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
public MqttSubAckMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttSubAckPayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override @Override
public MqttSubAckPayload payload() { public MqttSubAckPayload payload() {
return (MqttSubAckPayload) super.payload(); return (MqttSubAckPayload) super.payload();
+26 -17
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,6 +16,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import java.util.ArrayList; import java.util.ArrayList;
@@ -27,43 +28,51 @@ import java.util.List;
*/ */
public class MqttSubAckPayload { public class MqttSubAckPayload {
private final List<Integer> grantedQoSLevels; private final List<Integer> reasonCodes;
public MqttSubAckPayload(int... grantedQoSLevels) { public MqttSubAckPayload(int... reasonCodes) {
if (grantedQoSLevels == null) { ObjectUtil.checkNotNull(reasonCodes, "reasonCodes");
throw new NullPointerException("grantedQoSLevels");
}
List<Integer> list = new ArrayList<Integer>(grantedQoSLevels.length); List<Integer> list = new ArrayList<Integer>(reasonCodes.length);
for (int v: grantedQoSLevels) { for (int v: reasonCodes) {
list.add(v); list.add(v);
} }
this.grantedQoSLevels = Collections.unmodifiableList(list); this.reasonCodes = Collections.unmodifiableList(list);
} }
public MqttSubAckPayload(Iterable<Integer> grantedQoSLevels) { public MqttSubAckPayload(Iterable<Integer> reasonCodes) {
if (grantedQoSLevels == null) { ObjectUtil.checkNotNull(reasonCodes, "reasonCodes");
throw new NullPointerException("grantedQoSLevels");
}
List<Integer> list = new ArrayList<Integer>(); List<Integer> list = new ArrayList<Integer>();
for (Integer v: grantedQoSLevels) { for (Integer v: reasonCodes) {
if (v == null) { if (v == null) {
break; break;
} }
list.add(v); list.add(v);
} }
this.grantedQoSLevels = Collections.unmodifiableList(list); this.reasonCodes = Collections.unmodifiableList(list);
} }
public List<Integer> grantedQoSLevels() { public List<Integer> grantedQoSLevels() {
return grantedQoSLevels; List<Integer> qosLevels = new ArrayList<Integer>(reasonCodes.size());
for (int code: reasonCodes) {
if (code > MqttQoS.EXACTLY_ONCE.value()) {
qosLevels.add(MqttQoS.FAILURE.value());
} else {
qosLevels.add(code);
}
}
return qosLevels;
}
public List<Integer> reasonCodes() {
return reasonCodes;
} }
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
.append('[') .append('[')
.append("grantedQoSLevels=").append(grantedQoSLevels) .append("reasonCodes=").append(reasonCodes)
.append(']') .append(']')
.toString(); .toString();
} }
+14 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,23 +17,34 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#subscribe"> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#subscribe">
* MQTTV3.1/subscribe</a> * MQTTV3.1/subscribe</a>
*/ */
public final class MqttSubscribeMessage extends MqttMessage { public final class MqttSubscribeMessage extends MqttMessage {
public MqttSubscribeMessage( public MqttSubscribeMessage(
MqttFixedHeader mqttFixedHeader, MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader, MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttSubscribePayload payload) { MqttSubscribePayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
public MqttSubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttSubscribePayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override @Override
public MqttSubscribePayload payload() { public MqttSubscribePayload payload() {
return (MqttSubscribePayload) super.payload(); return (MqttSubscribePayload) super.payload();
+6 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -39,11 +39,12 @@ public final class MqttSubscribePayload {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('['); StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
for (int i = 0; i < topicSubscriptions.size() - 1; i++) { for (int i = 0; i < topicSubscriptions.size(); i++) {
builder.append(topicSubscriptions.get(i)).append(", "); builder.append(topicSubscriptions.get(i)).append(", ");
} }
builder.append(topicSubscriptions.get(topicSubscriptions.size() - 1)); if (!topicSubscriptions.isEmpty()) {
builder.append(']'); builder.setLength(builder.length() - 2);
return builder.toString(); }
return builder.append(']').toString();
} }
} }
@@ -0,0 +1,128 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
/**
* Model the SubscriptionOption used in Subscribe MQTT v5 packet
*/
public final class MqttSubscriptionOption {
public enum RetainedHandlingPolicy {
SEND_AT_SUBSCRIBE(0),
SEND_AT_SUBSCRIBE_IF_NOT_YET_EXISTS(1),
DONT_SEND_AT_SUBSCRIBE(2);
private final int value;
RetainedHandlingPolicy(int value) {
this.value = value;
}
public int value() {
return value;
}
public static RetainedHandlingPolicy valueOf(int value) {
switch (value) {
case 0:
return SEND_AT_SUBSCRIBE;
case 1:
return SEND_AT_SUBSCRIBE_IF_NOT_YET_EXISTS;
case 2:
return DONT_SEND_AT_SUBSCRIBE;
default:
throw new IllegalArgumentException("invalid RetainedHandlingPolicy: " + value);
}
}
}
private final MqttQoS qos;
private final boolean noLocal;
private final boolean retainAsPublished;
private final RetainedHandlingPolicy retainHandling;
public static MqttSubscriptionOption onlyFromQos(MqttQoS qos) {
return new MqttSubscriptionOption(qos, false, false, RetainedHandlingPolicy.SEND_AT_SUBSCRIBE);
}
public MqttSubscriptionOption(MqttQoS qos,
boolean noLocal,
boolean retainAsPublished,
RetainedHandlingPolicy retainHandling) {
this.qos = qos;
this.noLocal = noLocal;
this.retainAsPublished = retainAsPublished;
this.retainHandling = retainHandling;
}
public MqttQoS qos() {
return qos;
}
public boolean isNoLocal() {
return noLocal;
}
public boolean isRetainAsPublished() {
return retainAsPublished;
}
public RetainedHandlingPolicy retainHandling() {
return retainHandling;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MqttSubscriptionOption that = (MqttSubscriptionOption) o;
if (noLocal != that.noLocal) {
return false;
}
if (retainAsPublished != that.retainAsPublished) {
return false;
}
if (qos != that.qos) {
return false;
}
return retainHandling == that.retainHandling;
}
@Override
public int hashCode() {
int result = qos.hashCode();
result = 31 * result + (noLocal ? 1 : 0);
result = 31 * result + (retainAsPublished ? 1 : 0);
result = 31 * result + retainHandling.hashCode();
return result;
}
@Override
public String toString() {
return "SubscriptionOption[" +
"qos=" + qos +
", noLocal=" + noLocal +
", retainAsPublished=" + retainAsPublished +
", retainHandling=" + retainHandling +
']';
}
}
+14 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -25,11 +25,16 @@ import io.netty.util.internal.StringUtil;
public final class MqttTopicSubscription { public final class MqttTopicSubscription {
private final String topicFilter; private final String topicFilter;
private final MqttQoS qualityOfService; private final MqttSubscriptionOption option;
public MqttTopicSubscription(String topicFilter, MqttQoS qualityOfService) { public MqttTopicSubscription(String topicFilter, MqttQoS qualityOfService) {
this.topicFilter = topicFilter; this.topicFilter = topicFilter;
this.qualityOfService = qualityOfService; this.option = MqttSubscriptionOption.onlyFromQos(qualityOfService);
}
public MqttTopicSubscription(String topicFilter, MqttSubscriptionOption option) {
this.topicFilter = topicFilter;
this.option = option;
} }
public String topicName() { public String topicName() {
@@ -37,7 +42,11 @@ public final class MqttTopicSubscription {
} }
public MqttQoS qualityOfService() { public MqttQoS qualityOfService() {
return qualityOfService; return option.qos();
}
public MqttSubscriptionOption option() {
return option;
} }
@Override @Override
@@ -45,7 +54,7 @@ public final class MqttTopicSubscription {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
.append('[') .append('[')
.append("topicFilter=").append(topicFilter) .append("topicFilter=").append(topicFilter)
.append(", qualityOfService=").append(qualityOfService) .append(", option=").append(this.option)
.append(']') .append(']')
.toString(); .toString();
} }
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+34 -4
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,16 +17,46 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsuback">MQTTV3.1/unsuback</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsuback">
* MQTTV3.1/unsuback</a>
*/ */
public final class MqttUnsubAckMessage extends MqttMessage { public final class MqttUnsubAckMessage extends MqttMessage {
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader, MqttMessageIdVariableHeader variableHeader) { public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
super(mqttFixedHeader, variableHeader, null); MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttUnsubAckPayload payload) {
super(mqttFixedHeader, variableHeader, MqttUnsubAckPayload.withEmptyDefaults(payload));
}
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttUnsubAckPayload payload) {
this(mqttFixedHeader, fallbackVariableHeader(variableHeader), payload);
}
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader) {
this(mqttFixedHeader, variableHeader, null);
}
private static MqttMessageIdAndPropertiesVariableHeader fallbackVariableHeader(
MqttMessageIdVariableHeader variableHeader) {
if (variableHeader instanceof MqttMessageIdAndPropertiesVariableHeader) {
return (MqttMessageIdAndPropertiesVariableHeader) variableHeader;
}
return new MqttMessageIdAndPropertiesVariableHeader(variableHeader.messageId(), MqttProperties.NO_PROPERTIES);
} }
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override
public MqttUnsubAckPayload payload() {
return (MqttUnsubAckPayload) super.payload();
}
} }
@@ -0,0 +1,75 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Payload for MQTT unsuback message as in V5.
*/
public final class MqttUnsubAckPayload {
private final List<Short> unsubscribeReasonCodes;
private static final MqttUnsubAckPayload EMPTY = new MqttUnsubAckPayload();
public static MqttUnsubAckPayload withEmptyDefaults(MqttUnsubAckPayload payload) {
if (payload == null) {
return EMPTY;
} else {
return payload;
}
}
public MqttUnsubAckPayload(short... unsubscribeReasonCodes) {
ObjectUtil.checkNotNull(unsubscribeReasonCodes, "unsubscribeReasonCodes");
List<Short> list = new ArrayList<Short>(unsubscribeReasonCodes.length);
for (Short v: unsubscribeReasonCodes) {
list.add(v);
}
this.unsubscribeReasonCodes = Collections.unmodifiableList(list);
}
public MqttUnsubAckPayload(Iterable<Short> unsubscribeReasonCodes) {
ObjectUtil.checkNotNull(unsubscribeReasonCodes, "unsubscribeReasonCodes");
List<Short> list = new ArrayList<Short>();
for (Short v: unsubscribeReasonCodes) {
ObjectUtil.checkNotNull(v, "unsubscribeReasonCode");
list.add(v);
}
this.unsubscribeReasonCodes = Collections.unmodifiableList(list);
}
public List<Short> unsubscribeReasonCodes() {
return unsubscribeReasonCodes;
}
@Override
public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this))
.append('[')
.append("unsubscribeReasonCodes=").append(unsubscribeReasonCodes)
.append(']')
.toString();
}
}
+14 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,23 +17,34 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsubscribe"> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsubscribe">
* MQTTV3.1/unsubscribe</a> * MQTTV3.1/unsubscribe</a>
*/ */
public final class MqttUnsubscribeMessage extends MqttMessage { public final class MqttUnsubscribeMessage extends MqttMessage {
public MqttUnsubscribeMessage( public MqttUnsubscribeMessage(
MqttFixedHeader mqttFixedHeader, MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader, MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttUnsubscribePayload payload) { MqttUnsubscribePayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
public MqttUnsubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttUnsubscribePayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override @Override
public MqttUnsubscribePayload payload() { public MqttUnsubscribePayload payload() {
return (MqttUnsubscribePayload) super.payload(); return (MqttUnsubscribePayload) super.payload();
+6 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -39,11 +39,12 @@ public final class MqttUnsubscribePayload {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('['); StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
for (int i = 0; i < topics.size() - 1; i++) { for (int i = 0; i < topics.size(); i++) {
builder.append("topicName = ").append(topics.get(i)).append(", "); builder.append("topicName = ").append(topics.get(i)).append(", ");
} }
builder.append("topicName = ").append(topics.get(topics.size() - 1)) if (!topics.isEmpty()) {
.append(']'); builder.setLength(builder.length() - 2);
return builder.toString(); }
return builder.append("]").toString();
} }
} }
+25 -15
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -25,7 +25,7 @@ import io.netty.util.internal.ObjectUtil;
public enum MqttVersion { public enum MqttVersion {
MQTT_3_1("MQIsdp", (byte) 3), MQTT_3_1("MQIsdp", (byte) 3),
MQTT_3_1_1("MQTT", (byte) 4), MQTT_3_1_1("MQTT", (byte) 4),
Wildfire_1("MQTT", (byte) 5), MQTT_5("MQTT", (byte) 5),
Wildfire_2("MQTT", (byte) 6), Wildfire_2("MQTT", (byte) 6),
Wildfire_Future("MQTT", (byte) 7); Wildfire_Future("MQTT", (byte) 7);
@@ -50,21 +50,31 @@ public enum MqttVersion {
} }
public static MqttVersion fromProtocolNameAndLevel(String protocolName, byte protocolLevel) { public static MqttVersion fromProtocolNameAndLevel(String protocolName, byte protocolLevel) {
for (MqttVersion mv : values()) { MqttVersion mv = null;
if (mv.name.equals(protocolName)) { switch (protocolLevel) {
if (mv.level == protocolLevel) { case 3:
return mv; mv = MQTT_3_1;
} else { break;
continue; case 4:
} mv = MQTT_3_1_1;
} break;
case 5:
mv = MQTT_5;
break;
case 6:
mv = Wildfire_2;
break;
case 7:
mv = Wildfire_Future;
break;
default:
mv = Wildfire_Future;
break;
} }
if(protocolLevel > Wildfire_Future.level) {
return Wildfire_Future;
}
throw new MqttUnacceptableProtocolVersionException(protocolName + "is unknown protocol name");
}
return mv;
}
public static MqttVersion fromProtocolLevel(int protocolLevel) { public static MqttVersion fromProtocolLevel(int protocolLevel) {
for (MqttVersion mv : values()) { for (MqttVersion mv : values()) {
if (mv.level == (byte )protocolLevel) { 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 * "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at: * copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License * Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
@@ -24,7 +24,6 @@ import com.mchange.v2.c3p0.ComboPooledDataSource;
import io.moquette.BrokerConstants; import io.moquette.BrokerConstants;
import io.moquette.server.config.IConfig; import io.moquette.server.config.IConfig;
import org.flywaydb.core.Flyway; import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.configuration.FlywayConfiguration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -50,6 +49,7 @@ public class DBUtil {
public static void init(IConfig config) { public static void init(IConfig config) {
String embedDB = config.getProperty(BrokerConstants.EMBED_DB_PROPERTY_NAME); String embedDB = config.getProperty(BrokerConstants.EMBED_DB_PROPERTY_NAME);
String h2dbPath = config.getProperty(BrokerConstants.H2DB_PATH, "./h2db/wfchat");
boolean autoCleanMsgs = "true".equals(config.getProperty(BrokerConstants.DB_AUTO_CLEAN_HISTORY_MESSAGES)); boolean autoCleanMsgs = "true".equals(config.getProperty(BrokerConstants.DB_AUTO_CLEAN_HISTORY_MESSAGES));
if (embedDB != null && embedDB.equals("1")) { if (embedDB != null && embedDB.equals("1")) {
IsEmbedDB = true; IsEmbedDB = true;
@@ -68,7 +68,7 @@ public class DBUtil {
migrateLocation = "filesystem:./migrate/h2"; migrateLocation = "filesystem:./migrate/h2";
comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setJdbcUrl( "jdbc:h2:./h2db/wfchat;AUTO_SERVER=TRUE;MODE=MySQL" ); comboPooledDataSource.setJdbcUrl( "jdbc:h2:" + h2dbPath + ";AUTO_SERVER=TRUE;MODE=MySQL" );
comboPooledDataSource.setUser("SA"); comboPooledDataSource.setUser("SA");
comboPooledDataSource.setPassword("SA"); comboPooledDataSource.setPassword("SA");
comboPooledDataSource.setMinPoolSize(5); comboPooledDataSource.setMinPoolSize(5);
@@ -68,7 +68,9 @@ public class GitRepositoryState {
public static GitRepositoryState getGitRepositoryState() throws IOException { public static GitRepositoryState getGitRepositoryState() throws IOException {
Properties properties = new Properties(); Properties properties = new Properties();
try { try {
InputStream inputStream = new FileInputStream("config/git.properties"); String configPath = System.getProperty("wildfirechat.path", null);
File f = new File(configPath, "config/git.properties");
InputStream inputStream = new FileInputStream(f);
BufferedReader bf = new BufferedReader(new InputStreamReader(inputStream)); BufferedReader bf = new BufferedReader(new InputStreamReader(inputStream));
properties.load(bf); properties.load(bf);
} catch (IOException e) { } catch (IOException e) {
@@ -0,0 +1,8 @@
package win.liyufan.im;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GsonUtil {
public static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
}
@@ -87,8 +87,8 @@ public class HttpUtils {
} }
System.out.println(logText); System.out.println(logText);
e.printStackTrace(); e.printStackTrace();
LOG.info(logText); LOG.error(logText);
LOG.info("POST to {} with data {} failure", url, jsonStr); LOG.error("POST to {} failed", url);
if(httpCallback != null) { if(httpCallback != null) {
httpCallback.onFailure(-1, e.getMessage()); httpCallback.onFailure(-1, e.getMessage());
} }

Some files were not shown because too many files have changed in this diff Show More