Compare commits

...
117 Commits
Author SHA1 Message Date
heavyrian2012 d4c6b1f616 update release note 2022-10-25 19:28:42 +08:00
imndx ae3eb9c2df linux 多端登录处理错误 2022-10-25 15:03:12 +08:00
heavyrian2012 771a4d751f version 0.97 and release note 2022-10-24 10:54:11 +08:00
heavyrian2012 2bed06cf43 更新升级说明 2022-10-24 10:49:30 +08:00
heavyrian2012 50f0330884 解决版本信息错误问题 2022-10-24 10:25:01 +08:00
heavyrian2012 acd21faa9c 优化野火IM服务安装目录 2022-10-24 09:53:44 +08:00
heavyrian2012 6e570c74ae 更新docker说明 2022-10-23 20:55:30 +08:00
heavyrian2012 893b58cef4 update dockerfile 2022-10-23 20:26:09 +08:00
heavyrian2012 bd2b037edb 删除旧的好友欢迎语的多语言处理方式 2022-10-23 19:22:54 +08:00
heavyrian2012 6acb1c968a 添加h2db路径的配置 2022-10-23 09:48:13 +08:00
heavyrian2012 6048748300 更新readme 2022-10-22 11:07:58 +08:00
heavyrian2012 9be49c3d41 update readme 2022-10-22 09:47:00 +08:00
heavyrian2012 d9eccd39de update readme 2022-10-22 08:52:35 +08:00
heavyrian2012 ffaffc8fc1 update systemctl service file 2022-10-21 19:46:10 +08:00
heavyrian2012 5e1f4fe6c9 update systectl service readme 2022-10-21 19:36:43 +08:00
heavyrian2012 b5d884f9f6 优化deb和rpm包 2022-10-21 19:12:11 +08:00
heavyrian2012 c0840f3a73 添加RPM打包方式 2022-10-21 16:38:58 +08:00
heavyrian2012 34bd59f195 打包输出deb包 2022-10-21 11:07:30 +08:00
heavyrian2012 8e1d56b2ca 添加service运行方式 2022-10-21 09:31:39 +08:00
heavyrian2012 a4eb64edd5 添加配置注释说明 2022-10-20 15:47:59 +08:00
heavyrian2012 b141990661 应用平台的部分错误日志改成error级别 2022-10-20 15:44:47 +08:00
imndx 0f9df0659f 名片消息推送 2022-10-18 22:07:19 +08:00
heavyrian2012 a2d4bf7d43 http回调失败用error打印日志 2022-10-12 17:08:35 +08:00
heavyrain.lee 898c4f2419 Merge pull request #379 from wildfirechat/dependabot/maven/broker/org.apache.httpcomponents-httpclient-4.5.13
Bump httpclient from 4.4.1 to 4.5.13 in /broker
2022-10-12 15:54:59 +08:00
heavyrain.lee e1a8a040df Merge pull request #381 from wildfirechat/dependabot/maven/sdk/org.apache.httpcomponents-httpclient-4.5.13
Bump httpclient from 4.5.3 to 4.5.13 in /sdk
2022-10-12 15:54:14 +08:00
heavyrain.lee adb4f082f1 Merge pull request #364 from wildfirechat/dependabot/maven/sdk/commons-io-commons-io-2.7
Bump commons-io from 2.5 to 2.7 in /sdk
2022-10-12 15:52:40 +08:00
heavyrain.lee 2724c79a79 Merge pull request #363 from wildfirechat/dependabot/maven/broker/commons-io-commons-io-2.7
Bump commons-io from 2.5 to 2.7 in /broker
2022-10-12 15:52:12 +08:00
heavyrain.lee ba01c90171 Merge pull request #544 from ren-jq101/oscs_fix_cd3626oau51of7vbl0vg
fix(sec): upgrade commons-codec:commons-codec to 1.13
2022-10-12 15:49:19 +08:00
ren-jq101 b8967ccaff update commons-codec:commons-codec 1.10 to 1.13 2022-10-12 14:39:29 +08:00
heavyrian2012 0e6e23da13 更新版本号和release note 2022-10-06 13:39:04 +08:00
heavyrian2012 88652af565 添加会议录制接口 2022-10-06 11:41:51 +08:00
heavyrian2012 af58bf91a4 单聊消息可以定向发送,只发送给其中一个用户 2022-09-29 08:36:01 +08:00
heavyrian2012 9d86539d01 update issue template 2022-09-23 22:25:48 +08:00
heavyrian2012 2559fa7e48 文章消息添加摘要信息 2022-09-20 11:03:14 +08:00
heavyrian2012 d8cdcfc09d udpate version and release not 2022-09-20 10:46:07 +08:00
heavyrian2012 209b1950b6 公众号消息添加摘要字段 2022-09-20 10:41:45 +08:00
heavyrian2012 7ea1a2b146 升级netty版本为4.1.68.Final 2022-09-18 22:05:22 +08:00
heavyrian2012 279530ade2 update github issue template 2022-09-08 21:28:42 +08:00
heavyrian2012 5a0fd95fc7 update readme 2022-09-08 13:34:18 +08:00
heavyrian2012 ec72ab6baf fix build error 2022-09-06 16:14:20 +08:00
heavyrian2012 cd6c7d2da9 添加按照用户会话删除消息接口 2022-09-06 12:54:24 +08:00
heavyrian2012 39371797d2 update readme 2022-09-05 16:24:13 +08:00
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
heavyrian2012 30cd56aae2 version 0.92 2022-08-19 08:23:19 +08:00
heavyrian2012 6fbecfb0fb 去掉无效的警告 2022-08-19 08:15:03 +08:00
heavyrian2012 d2cb8427bb 解决下载zip包编译失败的问题 2022-08-19 08:05:54 +08:00
heavyrian2012 826bad2d66 add menuid and extra 2022-08-16 20:10:56 +08:00
heavyrian2012 aaccec00e9 add article content 2022-08-16 15:05:30 +08:00
heavyrian2012 3c1c5ce6d3 menu 2022-08-14 22:08:35 +08:00
heavyrian2012 b0fe1636a7 添加配置,在关闭消息漫游时,客户端首次登陆同步多久时间以内的消息 2022-08-14 15:25:00 +08:00
heavyrian2012 014cd3844d 频道只有auto为1时才会回调消息和订阅事件。频道只回调用户发送的消息,自己发送的消息不回调 2022-08-09 15:39:34 +08:00
heavyrian2012 6cc00950f0 SDK部分pojo对象添加无参数构造函数 2022-08-09 12:17:59 +08:00
heavyrian2012 2918c06a01 version 0.91 2022-08-03 12:41:35 +08:00
heavyrian2012 c3b9b9fccf 群组添加成员时,检查是否已经存在群组成员, fix issue #501 2022-08-03 12:19:03 +08:00
heavyrian2012 8c88d5a13f 添加注释 2022-07-29 20:37:53 +08:00
heavyrian2012 967526f5ec 添加发送消息payload错误时的提醒 2022-07-29 20:33:18 +08:00
heavyrian2012 109078abca 参数有效性检查 2022-07-29 20:24:41 +08:00
heavyrian2012 79a58e99aa 添加配置运行回调消息带上客户端信息 2022-07-27 11:36:04 +08:00
heavyrian2012 a394444c86 创建群组时,检查群组类型的有效性 2022-07-21 22:10:39 +08:00
heavyrian2012 dece1d7a1c sdk 添加获取会话阅读时间和消息送达时间接口 2022-07-20 22:25:46 +08:00
heavyrian2012 2b09a7bf84 version 0.90 2022-07-14 20:25:52 +08:00
heavyrian2012 200a6ebd04 添加注释 2022-07-14 20:22:36 +08:00
heavyrian2012 54db1f3b06 触发限频后多调用了一次callback,去掉无用的调用 2022-07-06 10:05:57 +08:00
heavyrian2012 3b90d145a4 去掉绑定IP地址的配置,防止客户误操作 2022-06-28 08:34:17 +08:00
heavyrain.lee e16a116fe4 Merge pull request #487 from wildfirechat/dependabot/maven/broker/mysql-mysql-connector-java-8.0.28
Bump mysql-connector-java from 8.0.19 to 8.0.28 in /broker
2022-06-26 18:34:33 +08:00
dependabot[bot] 7bd0937069 Bump mysql-connector-java from 8.0.19 to 8.0.28 in /broker
Bumps [mysql-connector-java](https://github.com/mysql/mysql-connector-j) from 8.0.19 to 8.0.28.
- [Release notes](https://github.com/mysql/mysql-connector-j/releases)
- [Changelog](https://github.com/mysql/mysql-connector-j/blob/release/8.0/CHANGES)
- [Commits](https://github.com/mysql/mysql-connector-j/compare/8.0.19...8.0.28)

---
updated-dependencies:
- dependency-name: mysql:mysql-connector-java
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-06-21 00:59:19 +00:00
heavyrian2012 c085057032 修正拼写错误 2022-06-09 15:03:56 +08:00
heavyrian2012 eac6648fc0 version 0.89 2022-06-09 09:42:34 +08:00
heavyrian2012 300f9aa0e6 update readme 2022-06-07 21:12:33 +08:00
heavyrian2012 dd40b72ec4 添加允许pc多端的配置 2022-06-06 09:37:37 +08:00
heavyrian2012 556e6ad2a0 解决某些java版本下反序列化IMResult<Void>失败问题 2022-05-27 13:25:21 +08:00
heavyrian2012 473ae32f57 update readme 2022-05-27 10:14:00 +08:00
heavyrian2012 d57ae2ed58 删除群组时清除相关设置 2022-05-27 08:35:12 +08:00
heavyrian2012 1a6907efd3 log4j version 2.17.2 2022-05-25 10:16:40 +08:00
heavyrain.lee 1624c470d6 Merge pull request #480 from wildfirechat/dependabot/maven/common/com.google.code.gson-gson-2.8.9
Bump gson from 2.8.2 to 2.8.9 in /common
2022-05-25 10:10:17 +08:00
heavyrain.lee 5ea6eefe67 Merge pull request #481 from wildfirechat/dependabot/maven/sdk/com.google.code.gson-gson-2.8.9
Bump gson from 2.8.2 to 2.8.9 in /sdk
2022-05-25 10:10:02 +08:00
heavyrian2012 8aebb2537a version 0.88 2022-05-23 14:53:40 +08:00
heavyrian2012 250b0ccebe 解决server api撤回单聊消息无法撤回的问题 2022-05-23 14:20:30 +08:00
heavyrian2012 91669c634f add send articles message builder 2022-05-22 11:26:01 +08:00
dependabot[bot] bf51cb9829 Bump gson from 2.8.2 to 2.8.9 in /sdk
Bumps [gson](https://github.com/google/gson) from 2.8.2 to 2.8.9.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.8.2...gson-parent-2.8.9)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-05-20 21:09:22 +00:00
dependabot[bot] baadcac2c6 Bump gson from 2.8.2 to 2.8.9 in /common
Bumps [gson](https://github.com/google/gson) from 2.8.2 to 2.8.9.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.8.2...gson-parent-2.8.9)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-05-20 21:09:06 +00:00
heavyrian2012 6c69a5ac96 null 判断 2022-05-19 12:37:01 +08:00
heavyrian2012 eb19ed3301 添加富通知消息的发送测试 2022-05-19 10:29:24 +08:00
heavyrian2012 5be7576bb8 管理端口获取authCode时使用默认的applicationId 2022-05-15 17:16:37 +08:00
heavyrian2012 86d552cd82 管理端口也添加获取应用authCode的方法 2022-05-15 15:07:51 +08:00
heavyrian2012 391f7345c0 add authcode and config api for open platform 2022-05-15 10:19:07 +08:00
heavyrian2012 92fa40f1fd 添加server api和机器人api获取机器人信息的接口 2022-05-13 14:38:26 +08:00
heavyrian2012 3b3c05260c 频道automic状态为1所有消息都不发送到owner,无论callback是否存在 2022-05-12 08:47:26 +08:00
heavyrian2012 4e9cf86302 机器人和频道回调消息带上messageid和时间戳 2022-05-11 09:28:06 +08:00
heavyrian2012 cbb89bbfb5 解决删除用户时,没有回调取消订阅通知的问题 2022-05-07 09:16:15 +08:00
heavyrian2012 537221b0b4 add application token 2022-05-02 22:07:54 +08:00
heavyrian2012 9d23f9c996 优化频道订阅关系缓存逻辑 2022-05-02 11:12:19 +08:00
heavyrian2012 6a25a83857 删除用户或删除频道时,删除对于的订阅关系 2022-05-02 11:05:56 +08:00
heavyrian2012 d0e533ad21 channel add member index 2022-05-02 10:57:46 +08:00
heavyrian2012 cd0e07f32d 优化sql语句 2022-05-01 12:14:07 +08:00
heavyrian2012 a6869c9072 消息表添加会话索引 2022-04-30 21:30:06 +08:00
heavyrian2012 03b3bb2093 同步专业版数据库及错误码 2022-04-30 08:01: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
188 changed files with 11574 additions and 1306 deletions
+7 -1
View File
@@ -11,7 +11,13 @@
你认为正确的表现应该是什么样的。 你认为正确的表现应该是什么样的。
#### 补充条件 #### 补充条件
是否是毕现的,还是偶现的?是否只有在特殊的网络/设备/平台上出现,还是所有的都出现。还有您用的版本是什么时候的,是不是最新版 是否是必现的,还是偶现的?是否只有在特殊的网络/设备/平台上出现,还是所有的都出现。还有您用的版本是什么时候的,是不是最新版
#### demo对比结果 #### demo对比结果
请用demo对比测试,demo上是什么状态。 请用demo对比测试,demo上是什么状态。
#### 检索已有问题
除了可以检索已有issue外,还可以去野火论坛查找已有问题,论坛地址为 https://bbs.wildfirechat.cn
#### star 我们
给我们点个star,可以及时接收到我们回复信息,另外也是对我们支持人员辛苦工作的认可,激励我们更好更快地为大家服务。
+62 -33
View File
@@ -2,12 +2,13 @@
野火IM是专业级的即时通讯和实时音视频整体解决方案,由北京野火无限网络科技有限公司维护和支持。 野火IM是专业级的即时通讯和实时音视频整体解决方案,由北京野火无限网络科技有限公司维护和支持。
## 功能特性 ## 功能特性
* 超低硬件配置要求,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,31 +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 ) | 高级音视频媒体服务。 |
| [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码。 | [open-platform](https://github.com/wildfirechat/open-platform) | [open-platform](https://gitee.com/wfchat/open-platform) | 野火开放平台服务。 |
| [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS SDK源码和App源码。 | [daily-report](https://github.com/wildfirechat/daily-report) | [daily-report](https://github.com/wildfirechat/daily-report) | 野火开放平台日报 demo服务。 |
| [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)。 | | [channel-platform](https://github.com/wildfirechat/channel-platform) | [channel-platform](https://gitee.com/wfchat/channel-platform) | 野火频道(公众号)平台服务。 |
| [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](https://web.wildfirechat.cn) | | [android-chat](https://github.com/wildfirechat/android-chat) | [android-chat](https://gitee.com/wfchat/android-chat) | 野火IM Android SDK源码和App源码。 |
| [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台) | | [ios-chat](https://github.com/wildfirechat/ios-chat) | [ios-chat](https://gitee.com/wfchat/ios-chat) | 野火IM iOS SDK源码和App源码。|
| [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.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)。 |
| [web-chat](https://github.com/wildfirechat/vue-chat) | [web-chat](https://gitee.com/wfchat/vue-chat) | 野火IM Web 端, [体验地址](https://web.wildfirechat.cn)。 |
| [wx-chat](https://github.com/wildfirechat/wx-chat) | [wx-chat](https://gitee.com/wfchat/wx-chat) | 小程序平台的Demo(支持微信、百度、阿里、字节、QQ 等小程序平台)。 |
| [uni-chat](https://github.com/wildfirechat/uni-chat) | [uni-chat](https://gitee.com/wfchat/uni-chat) | UniApp平台移动端应用(使用原生插件)。 |
| [uni-wfc-client](https://github.com/wildfirechat/uni-wfc-client) | [uni-wfc-client](https://gitee.com/wfchat/uni-wfc-client) | UniApp平台原生插件。 |
| [flutter_imclient](https://github.com/wildfirechat/flutter_imclient) | [flutter_imclient](https://gitee.com/wfchat/flutter_imclient) | Flutter平台原生插件。 |
| [docs](https://github.com/wildfirechat/docs) | [docs](https://gitee.com/wfchat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明,[在线查看](https://docs.wildfirechat.cn/)。 |
## 野火开发文档 ## 野火开发文档
[在线文档](https://docs.wildfirechat.cn/) [在线文档](https://docs.wildfirechat.cn/)
## 野火性能测试报告及测试方法
[Github](https://github.com/wildfirechat/Performance_Test), [码云](https://gitee.com/wfchat/Performance_Test)。
## 野火Demo ## 野火Demo
请使用微信扫码下载安装体验野火IM移动客户端 请使用微信扫码下载安装体验野火IM移动客户端
@@ -53,6 +68,10 @@ PC客户端点[这里](https://github.com/wildfirechat/vue-pc-chat/releases)下
![野火IM](http://static.wildfirechat.net/wx.jpg) ![野火IM](http://static.wildfirechat.net/wx.jpg)
## 快速使用
可以按照[快速开始](https://docs.wildfirechat.cn/quick_start/)来部署应用服务和IM服务,然后打包移动端,就可以完成聊天和通话功能。
之后再部署开放平台服务、频道(公众号)服务、机器人服务、推送服务、Turn服务(音视频服务)来实现完整的应用功能,详情可以详细阅读文档。
## 应用截图 ## 应用截图
![ios-demo](http://static.wildfirechat.cn/ios-demo.gif) ![ios-demo](http://static.wildfirechat.cn/ios-demo.gif)
@@ -79,46 +98,56 @@ PC客户端点[这里](https://github.com/wildfirechat/vue-pc-chat/releases)下
## 编译 ## 编译
在安装JDK1.8以上及maven的前提下,在命令行中执行```mvn clean package```,生成的目标文件在```./distribution/target/distribution-xxxx-bundle-tar.tar.gz``` 由于```distribution/pom.xml```中使用了生成RPM包的plugin,所以需要本地安装有rpm。如果不需要RPM包,可以删除掉```distribution/pom.xml```文件中的RPM plugin。
> 由于使用了一个git的maven插件,如果本地没有git信息就会编译出错,请使用```git clone```的方法下载代码,或者下载压缩包解压后在根目录创建```.git```的空目录。建议用```git clone```的方式下载代码。
在安装RPM后或者删除```distribution/pom.xml```文件中的RPM plugin后,执行下面命令:
```
mvn clean package
```
生成的目标文件在```./distribution/target/distribution-xxxx-bundle-tar.tar.gz```
## 配置 ## 配置
解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```。修改如下部分: 解压```distribution-xxxx-bundle-tar.tar.gz```,修改解压出来的```config```目录下的```wildfirechat.conf```,可以阅读配置文件注释和文档,对其它配置项进行调整。
1. 修改```server.ip```为您的服务器的公网IP,或者域名。如果用域名需要做好域名解析。
2. 可以阅读配置文件注释和文档,对其它配置项进行调整。
## 运行 ## 运行
在程序目录运行如下命令: 在解压```distribution-xxxx-bundle-tar.tar.gz```后的目录下运行如下命令:
``` ```
./bin/wildfirechat.sh ./bin/wildfirechat.sh
``` ```
## 验证 ## 验证
1. 在浏览器中输入地址 ```http://${ip}/api/version```可以看到返回一个json文件。 1. 在浏览器中输入地址 ```http://${ip}/api/version```可以看到返回一个json文件。
2. 部署[应用服务](应用服务),配置和编译[Android客户端](https://github.com/wildfirechat/android-chat)和[iOS客户端](https://github.com/wildfirechat/ios-chat)进行验证。详情可参考[快速开始](https://docs.wildfirechat.cn/quick_start/)。 2. 部署[应用服务](https://github.com/wildfirechat/app-server),配置和编译[Android客户端](https://github.com/wildfirechat/android-chat)和[iOS客户端](https://github.com/wildfirechat/ios-chat)进行验证。详情可参考[快速开始](https://docs.wildfirechat.cn/quick_start/)。
## 升级说明 ## 升级说明
1. 从0.42 版本增加了群成员数限制,默认为2000。如果您从之前的版本升级到这个版本或以后,需要注意到群成员数的限制。升级之后超出限制的群不受影响,但不能继续加人,如果您想修改默认值,可以在升级版本之后,修改t_setting表,把默认的大小改为您期望的人数。另外修改t_group表,把已经存在的群组max_member_count改成您期望的,然后重启。 1. 从0.42 版本增加了群成员数限制,默认为2000。如果您从之前的版本升级到这个版本或以后,需要注意到群成员数的限制。升级之后超出限制的群不受影响,但不能继续加人,如果您想修改默认值,可以在升级版本之后,修改t_setting表,把默认的大小改为您期望的人数。另外修改t_group表,把已经存在的群组max_member_count改成您期望的,然后重启。
2. 0.46和0.47版本升级到0.48及以后版本时,可能会提示flyway migrate 38错误,请执行 [修复脚本](https://github.com/wildfirechat/server/blob/wildfirechat/flyway_repaire_migrate_38.sql) 进行修复。0.46和0.47版本之外的版本不会出现此问题。 2. 0.50版本添加了是否允许客户端发送群操作通知的配置。如果您在客户端自定义群通知,需要在服务器端配置允许,没有使用自定义群操作通知的不受影响。***
3. 从0.54之前版本升级到0.54及以后版本时,会提示flyway migrate错误。因为0.54版本删除了sql脚本中默认敏感词的内容,flyway checksum失败。请执行```update flyway_schema_history set checksum = 0 where script = 'V17__add_default_sensitive_word.sql';```来修复。 3. 0.46和0.47版本升级到0.48及以后版本时,可能会提示flyway migrate 38错误,请执行 [修复脚本](https://github.com/wildfirechat/server/blob/wildfirechat/flyway_repaire_migrate_38.sql) 进行修复。0.46和0.47版本之外的版本不会出现此问题。
4. 从0.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。 4. 从0.54之前版本升级到0.54及以后版本时,会提示flyway migrate错误。因为0.54版本删除了sql脚本中默认敏感词的内容,flyway checksum失败。请执行```update flyway_schema_history set checksum = 0 where script = 'V17__add_default_sensitive_word.sql';```来修复。
5. 从0.59之前的版本升级到之后的版本执行数据库升级时间比较长,请耐心等待提示运行成功,避免中途中断。
6. 0.62/0.63 版本有严重的问题,请使用0.64及以后版本,或者0.61版。
7. 从0.68 版本起添加了pc在线是否默认手机接收推送的开关,默认为开,与以前版本作用相反,请注意兼容(可以关掉与之前保持一致或者升级客户端)
8. 从0.78 版本起把MySQL数据库中关键字都改为大小写敏感,另外生成id的方法也做了改变,只生成小写的id,避免出现id重复的问题,建议所有客户都升级
9. 从0.79 版本起把log4j升级到log4j2,因为log4j已经不再维护而且还有已知的漏洞,建议所有客户都升级,升级时注意更新log4j2的配置文件
10. 0.97版本更改了启动脚本```wildfirechat.sh```,如果是升级服务,请注意更新启动脚本。
## 联系我们 ## 联系我们
商务合作请使用如下邮箱和微信联系: 商务合作请微信联系:
邮箱: support@wildfirechat.cn 微信1:wfchat 微信2:wildfirechat * 微信1:wildfirechat
* 微信2:wfchat
## 问题交流 ## 问题交流
1. 如果大家发现bug,请在GitHub提issue;如果有需求也请给我们提issue。 1. 如果大家发现bug,请在GitHub或码云提issue;如果有需求也请给我们提issue。
2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习 2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习
3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。 3. 关注我们的公众号。我们有新版本发布或者有重大更新会通过公众号通知大家,另外我们也会不定期的发布一些关于野火IM的技术介绍。
<img src="http://static.wildfirechat.cn/wx_wfc_qrcode.jpg" width = 50% height = 50% /> <img src="http://static.wildfirechat.cn/wx_wfc_qrcode.jpg" width = 50% height = 50% />
> 我们有研发工程师轮流值班处理issue和论坛,一般简单问题几个小时就会回复一遍,疑难Bug的修改和新需求的开发我们也会尽快解决。 我们有核心研发工程师轮流值班处理issue和论坛,会及时处理的,疑难Bug的修改和新需求的开发我们也会尽快解决。
## 特别感谢 ## 特别感谢
1. [moquette](https://github.com/moquette-io/moquette) 本项目是基于此项目二次开发而来,处理MQTT相关业务。 1. [moquette](https://github.com/moquette-io/moquette) 本项目是基于此项目二次开发而来,处理MQTT相关业务。
-3
View File
@@ -1,3 +0,0 @@
#native2ascii -encoding UTF-8 messages.properties temp_messages.properties
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
@@ -1,2 +0,0 @@
Above_Greeting_Message=This's the greeting
Friend_Can_Start_Chat=You're already friend, start chat now.
@@ -1,2 +0,0 @@
Above_Greeting_Message=This's the greeting
Friend_Can_Start_Chat=You're already friend, start chat now.
@@ -1,2 +0,0 @@
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
@@ -1,2 +0,0 @@
Above_Greeting_Message=\u4ee5\u4e0a\u662f\u6253\u62db\u547c\u4fe1\u606f
Friend_Can_Start_Chat=\u4f60\u4eec\u5df2\u7ecf\u6210\u4e3a\u597d\u53cb\u4e86\uff0c\u73b0\u5728\u53ef\u4ee5\u5f00\u59cb\u804a\u5929\u4e86
+25 -14
View File
@@ -26,8 +26,8 @@ http.admin.port 18080
##节点ID,当集群部署时,一定不能有重复。 ##节点ID,当集群部署时,一定不能有重复。
node_id 1 node_id 1
##绑定IP,保持不变即可 ##绑定IP,不要打开,除非有特殊需求才可以打开
host 0.0.0.0 #host 0.0.0.0
##本地绑定端口 ##本地绑定端口
local_port 80 local_port 80
@@ -38,9 +38,13 @@ embed.db 1
## 是否自动清理历史消息记录,当为true时,IM服务会定时扫描t_messages_x和t_user_messages_x表,清除掉3年前的历史消息。 ## 是否自动清理历史消息记录,当为true时,IM服务会定时扫描t_messages_x和t_user_messages_x表,清除掉3年前的历史消息。
## 如果不开启自动清理,需要手动清理t_messages_x和t_user_messages_x表内的历史数据,保存数据不能超过3年。 ## 如果不开启自动清理,需要手动清理t_messages_x和t_user_messages_x表内的历史数据,保存数据不能超过3年。
## 如果需要消息保存更长时间,可以把临近销毁的消息转储到别的数据库,客户端在拉取不到服务器消息时,再去转储的数据库拉取。
## 使用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,10 +60,13 @@ token.key testim
##token.expire_time 2592000000 ##token.expire_time 2592000000
##首次登录,是否接收之前的历史消息。 ##首次登录,是否接收之前的历史消息。
##0 不接收历史消息,只接收5分钟以内的消息,由于服务器没有保存已经收取记录,所以如果有超过5分钟之前未收取的消息也不会收取下拉; ##0 不接收历史消息,只接收${message.compensate_time_limit}毫秒以内的消息,由于服务器没有保存已经收取记录,所以如果有超过${message.compensate_time_limit}毫秒之前未收取的消息也不会收取下拉;
##1接收,会接收 message.max_queue 配置的条数的历史消息 ##1接收,会接收 message.max_queue 配置的条数的历史消息
message.roaming 0 message.roaming 0
## 消息补偿时限,当${message.roaming}为0时,会同步时限以内且小于${message.max_queue}的消息,默认时限为5分钟。当${message.roaming}为1时,会接收 message.max_queue 配置的条数的历史消息。
message.compensate_time_limit 300000
##是否开启拉取远程历史消息。如果为1,客户端在会话内如果本地消息读取完了,可以下拉继续加载在服务器上的该会话的消息;如果为0则不能。 ##是否开启拉取远程历史消息。如果为1,客户端在会话内如果本地消息读取完了,可以下拉继续加载在服务器上的该会话的消息;如果为0则不能。
message.remote_history_message 0 message.remote_history_message 0
@@ -108,6 +115,14 @@ message.global_mute_exception_types 401,402,403,404,405,407,408,410,411
## 当为true时,不转发server api发送的消息;当为false时,转发server api发送的消息。 ## 当为true时,不转发server api发送的消息;当为false时,转发server api发送的消息。
message.no_forward_admin_message false message.no_forward_admin_message false
## 消息转发功能开启时,转发信息是否带上用户clientId和平台类型。
message.forward_with_client_info false
## 机器人回调信息是否带上用户clientId和平台类型。
robot.callback_with_client_info false
## 频道回调信息是否带上用户clientId和平台类型。
channel.callback_with_client_info false
##禁止搜索用户 ##禁止搜索用户
friend.disable_search false friend.disable_search false
@@ -127,12 +142,6 @@ friend.reject_request_duration 2592000000
##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。 ##好友请求过期时间,单位是毫秒,默认是7天,0为无限长期限。
friend.request_expiration_duration 604800000 friend.request_expiration_duration 604800000
##添加好友后会发送欢迎消息,比如"你们已经是好友了,可以聊天了"。这个开关控制是用旧的方式还是用新的方式。
##旧的方式会发送notification消息(contenttype是80),内容会根据好友接收者的语言选择对应的语言,如果双方语言不一致会导致另外一方看到的不是本地语言。
##新的方式会发送添加好友消息(contenttype是93)和欢迎消息(contenttype是92),需要客户端实现这两种消息。
##如果客户端已经实现这两种消息可以打开这个开关(客户端2020.6.30添加了此消息类型)。
friend.new_welcome_message true
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出 ## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000 chatroom.participant_idle_time 900000
@@ -178,11 +187,6 @@ http.channel.rate_limit 1000
##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。 ##客户端请求频率限制,默认是5秒100次。通常情况下不要修改这个值。
client.request_rate_limit 100 client.request_rate_limit 100
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#********************************************************************* #*********************************************************************
# Media server configuration # Media server configuration
#********************************************************************* #*********************************************************************
@@ -238,6 +242,9 @@ local.media.storage.root media
# 建议使用false # 建议使用false
server.multi_endpoint false server.multi_endpoint false
## 是否支持PC多端登陆,当server.multi_endpoint为true时,此开关无意义,当为false时,可以单独打开PC端多端登录。
server.multi_pc_endpoint false
# 多平台连接状态通知(仅当multi_endpoint为false时有效),true时移动端可以收到pc或web端登录的通知。 # 多平台连接状态通知(仅当multi_endpoint为false时有效),true时移动端可以收到pc或web端登录的通知。
server.multi_platform_notification true server.multi_platform_notification true
@@ -289,6 +296,10 @@ push.ios.server.address http://localhost:8085/ios/push
##需要转发的消息类型,当有多个时以英文逗号分割。如果转发所有消息,请注释掉配置或者设置为空 ##需要转发的消息类型,当有多个时以英文逗号分割。如果转发所有消息,请注释掉配置或者设置为空
#message.forward.types 1,2,3 #message.forward.types 1,2,3
##敏感消息转发地址
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.sensitive.forward.url http://localhost:8888/im_event/message
##提醒消息转发地址,@全体用户和@某个用户消息会回调此地址。此转发不受${message.forward.types}限制。 ##提醒消息转发地址,@全体用户和@某个用户消息会回调此地址。此转发不受${message.forward.types}限制。
##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!! ##注意回调是单线程回调,接收服务必须在同一内网,且异步处理快速返回,否则会有延迟问题!!
#message.mentionmsg.forward.url http://localhost:8888/im_event/message #message.mentionmsg.forward.url http://localhost:8888/im_event/message
+13
View File
@@ -0,0 +1,13 @@
DROP TABLE IF EXISTS `t_secret_chat`;
CREATE TABLE `t_secret_chat` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_from` varchar(64) DEFAULT '',
`_from_cid` varchar(64) DEFAULT '',
`_to` varchar(64) DEFAULT '',
`_to_cid` varchar(64) DEFAULT '',
`_state` tinyint NOT NULL DEFAULT 0,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `secret_chat_uid_index` (`_uid` DESC)
);
@@ -0,0 +1 @@
alter table `t_messages` add INDEX `messages_conv_index` ( `_type`, `_target`, `_line`);
@@ -0,0 +1 @@
alter table `t_channel_listener` add INDEX `channel_mid_index` ( `_mid`);
+1
View File
@@ -0,0 +1 @@
alter table `t_channel` add column `_menu` BLOB DEFAULT NULL;
+16
View File
@@ -0,0 +1,16 @@
DROP TABLE IF EXISTS `t_secret_chat`;
CREATE TABLE `t_secret_chat` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_from` varchar(64) DEFAULT '',
`_from_cid` varchar(64) DEFAULT '',
`_to` varchar(64) DEFAULT '',
`_to_cid` varchar(64) DEFAULT '',
`_state` tinyint NOT NULL DEFAULT 0,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `secret_chat_uid_index` (`_uid` DESC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_bin;
@@ -0,0 +1,37 @@
alter table `t_messages` add INDEX `messages_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_0` add INDEX `messages0_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_1` add INDEX `messages1_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_2` add INDEX `messages2_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_3` add INDEX `messages3_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_4` add INDEX `messages4_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_5` add INDEX `messages5_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_6` add INDEX `messages6_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_7` add INDEX `messages7_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_8` add INDEX `messages8_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_9` add INDEX `messages9_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_10` add INDEX `messages10_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_11` add INDEX `messages11_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_12` add INDEX `messages12_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_13` add INDEX `messages13_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_14` add INDEX `messages14_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_15` add INDEX `messages15_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_16` add INDEX `messages16_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_17` add INDEX `messages17_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_18` add INDEX `messages18_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_19` add INDEX `messages19_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_20` add INDEX `messages20_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_21` add INDEX `messages21_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_22` add INDEX `messages22_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_23` add INDEX `messages23_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_24` add INDEX `messages24_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_25` add INDEX `messages25_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_26` add INDEX `messages26_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_27` add INDEX `messages27_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_28` add INDEX `messages28_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_29` add INDEX `messages29_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_30` add INDEX `messages30_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_31` add INDEX `messages31_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_32` add INDEX `messages32_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_33` add INDEX `messages33_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_34` add INDEX `messages34_conv_index` (`_type`, `_target`, `_line`);
alter table `t_messages_35` add INDEX `messages35_conv_index` (`_type`, `_target`, `_line`);
@@ -0,0 +1 @@
alter table `t_channel_listener` add INDEX `channel_mid_index` (`_mid`);
+1
View File
@@ -0,0 +1 @@
alter table `t_channel` add column `_menu` BLOB DEFAULT NULL;
+13 -8
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.87</version> <version>0.97</version>
</parent> </parent>
<artifactId>moquette-broker</artifactId> <artifactId>moquette-broker</artifactId>
@@ -27,18 +27,18 @@
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>2.17.1</version> <version>2.17.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>2.17.1</version> <version>2.17.2</version>
</dependency> </dependency>
<!--用于与slf4j保持桥接--> <!--用于与slf4j保持桥接-->
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId> <artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.1</version> <version>2.17.2</version>
</dependency> </dependency>
<!-- slf4j核心包--> <!-- slf4j核心包-->
<dependency> <dependency>
@@ -56,7 +56,7 @@
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version> <version>8.0.28</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -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));
} }
} }
@@ -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\"}");
@@ -34,6 +34,10 @@ abstract public class AdminAction extends Action {
SECRET_KEY = secretKey; SECRET_KEY = secretKey;
} }
public static String getSecretKey() {
return SECRET_KEY;
}
public static void setNoCheckTime(String noCheckTime) { public static void setNoCheckTime(String noCheckTime) {
try { try {
NO_CHECK_TIME = Boolean.parseBoolean(noCheckTime); NO_CHECK_TIME = Boolean.parseBoolean(noCheckTime);
@@ -95,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();
} }
} }
@@ -108,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) {
@@ -0,0 +1,58 @@
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputApplicationGetUserInfo;
import cn.wildfirechat.pojos.OutputApplicationUserInfo;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.hazelcast.util.StringUtil;
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.handler.codec.http.HttpResponseStatus;
@Route(APIPath.User_Application_Get_UserInfo)
@HttpMethod("POST")
public class ApplicationGetUserInfoAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputApplicationGetUserInfo inputUserToken = getRequestBody(request.getNettyRequest(), InputApplicationGetUserInfo.class);
RestResult result;
if (inputUserToken == null || StringUtil.isNullOrEmpty(inputUserToken.getAuthCode())) {
result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
} else {
String userId = messagesStore.verifyApplicationAuthCode(inputUserToken.getAuthCode(), "admin", ProtoConstants.ApplicationType.ApplicationType_Admin);
if(userId != null) {
OutputApplicationUserInfo outputVerifyApplicationUser = new OutputApplicationUserInfo();
outputVerifyApplicationUser.setUserId(userId);
WFCMessage.User user = messagesStore.getUserInfo(userId);
if(user != null) {
outputVerifyApplicationUser.setDisplayName(user.getDisplayName());
outputVerifyApplicationUser.setPortraitUrl(user.getPortrait());
}
result = RestResult.ok(outputVerifyApplicationUser);
} else {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_TOKEN_ERROR);
}
}
response.setStatus(HttpResponseStatus.OK);
response.setContent(gson.toJson(result));
}
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,63 @@
/*
* 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.InputGetUserInfo;
import cn.wildfirechat.pojos.InputOutputUserInfo;
import cn.wildfirechat.pojos.InputRobotId;
import cn.wildfirechat.pojos.OutputRobot;
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.User_Get_Robot_Info)
@HttpMethod("POST")
public class GetRobotAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputRobotId inputUserId = getRequestBody(request.getNettyRequest(), InputRobotId.class);
if (inputUserId != null
&& (!StringUtil.isNullOrEmpty(inputUserId.getRobotId()))) {
WFCMessage.User user = messagesStore.getUserInfo(inputUserId.getRobotId());
WFCMessage.Robot robot = messagesStore.getRobot(inputUserId.getRobotId());
RestResult result;
if (user == null || robot == null) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else {
OutputRobot outputRobot = new OutputRobot();
outputRobot.fromUser(user);
outputRobot.fromRobot(robot, true);
result = RestResult.ok(outputRobot);
}
setResponseContent(result, response);
} else {
setResponseContent(RestResult.resultOf(ErrorCode.INVALID_PARAMETER), response);
}
}
return true;
}
}
@@ -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;
@@ -0,0 +1,59 @@
/*
* 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.channel;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputApplicationGetUserInfo;
import cn.wildfirechat.pojos.OutputApplicationUserInfo;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
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.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
@Route(APIPath.Channel_Application_Get_UserInfo)
@HttpMethod("POST")
public class ApplicationGetUserInfoAction extends ChannelAction {
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputApplicationGetUserInfo inputUserToken = getRequestBody(request.getNettyRequest(), InputApplicationGetUserInfo.class);
RestResult result;
if (inputUserToken == null || StringUtil.isNullOrEmpty(inputUserToken.getAuthCode())) {
result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
} else {
String userId = messagesStore.verifyApplicationAuthCode(inputUserToken.getAuthCode(), channelInfo.getTargetId(), ProtoConstants.ApplicationType.ApplicationType_Channel);
if(userId != null) {
OutputApplicationUserInfo outputVerifyApplicationUser = new OutputApplicationUserInfo();
outputVerifyApplicationUser.setUserId(userId);
WFCMessage.User user = messagesStore.getUserInfo(userId);
if(user != null) {
outputVerifyApplicationUser.setDisplayName(user.getDisplayName());
outputVerifyApplicationUser.setPortraitUrl(user.getPortrait());
}
result = RestResult.ok(outputVerifyApplicationUser);
} else {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_APPLICATION_TOKEN_ERROR_OR_TIMEOUT);
}
}
response.setStatus(HttpResponseStatus.OK);
response.setContent(gson.toJson(result));
}
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;
} }
@@ -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();
@@ -0,0 +1,59 @@
/*
* 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.robot;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputApplicationGetUserInfo;
import cn.wildfirechat.pojos.OutputApplicationUserInfo;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
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.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
@Route(APIPath.Robot_Application_Get_UserInfo)
@HttpMethod("POST")
public class ApplicationGetUserInfoAction extends RobotAction {
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputApplicationGetUserInfo inputUserToken = getRequestBody(request.getNettyRequest(), InputApplicationGetUserInfo.class);
RestResult result;
if (inputUserToken == null || StringUtil.isNullOrEmpty(inputUserToken.getAuthCode())) {
result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
} else {
String userId = messagesStore.verifyApplicationAuthCode(inputUserToken.getAuthCode(), robot.getUid(), ProtoConstants.ApplicationType.ApplicationType_Robot);
if(userId != null) {
OutputApplicationUserInfo outputVerifyApplicationUser = new OutputApplicationUserInfo();
outputVerifyApplicationUser.setUserId(userId);
WFCMessage.User user = messagesStore.getUserInfo(userId);
if(user != null) {
outputVerifyApplicationUser.setDisplayName(user.getDisplayName());
outputVerifyApplicationUser.setPortraitUrl(user.getPortrait());
}
result = RestResult.ok(outputVerifyApplicationUser);
} else {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_APPLICATION_TOKEN_ERROR_OR_TIMEOUT);
}
}
response.setStatus(HttpResponseStatus.OK);
response.setContent(gson.toJson(result));
}
return true;
}
}
@@ -0,0 +1,46 @@
/*
* 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.robot;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputGetUserInfo;
import cn.wildfirechat.pojos.InputOutputUserInfo;
import cn.wildfirechat.pojos.OutputRobot;
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.Robot_Get_Profile)
@HttpMethod("POST")
public class GetProfileAction extends RobotAction {
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
WFCMessage.User user = messagesStore.getUserInfo(robot.getUid());
RestResult result;
if (user == null) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else {
OutputRobot outputRobot = new OutputRobot();
outputRobot.fromUser(user);
outputRobot.fromRobot(robot, false);
result = RestResult.ok(outputRobot);
}
setResponseContent(result, response);
}
return true;
}
}
@@ -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) {
@@ -114,6 +114,13 @@ 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());
@@ -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";
@@ -112,6 +112,8 @@ public final class BrokerConstants {
public static final String EMBED_DB_PROPERTY_NAME = "embed.db"; public static final String EMBED_DB_PROPERTY_NAME = "embed.db";
public static final String DB_AUTO_CLEAN_HISTORY_MESSAGES = "db.auto_clean_history_messages"; public static final String DB_AUTO_CLEAN_HISTORY_MESSAGES = "db.auto_clean_history_messages";
public static final String H2DB_PATH = "h2db.path";
public static final String SENSITIVE_Filter_Type = "sensitive.filter.type"; public static final String SENSITIVE_Filter_Type = "sensitive.filter.type";
@@ -120,16 +122,19 @@ 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_MentionMsg_Forward_Url = "message.mentionmsg.forward.url"; public static final String MESSAGE_MentionMsg_Forward_Url = "message.mentionmsg.forward.url";
public static final String SERVER_MULTI_ENDPOINT = "server.multi_endpoint"; public static final String SERVER_MULTI_ENDPOINT = "server.multi_endpoint";
public static final String SERVER_MULTI_PC_ENDPOINT = "server.multi_pc_endpoint";
public static final String SERVER_MULTI_PLATFROM_NOTIFICATION = "server.multi_platform_notification"; public static final String SERVER_MULTI_PLATFROM_NOTIFICATION = "server.multi_platform_notification";
public static final String SERVER_MOBILE_DEFAULT_SILENT_WHEN_PC_ONLINE = "server.mobile_default_silent_when_pc_online"; public static final String SERVER_MOBILE_DEFAULT_SILENT_WHEN_PC_ONLINE = "server.mobile_default_silent_when_pc_online";
public static final String SERVER_CLIENT_SUPPORT_KICKOFF_EVENT = "server.client_support_kickoff_event"; public static final String SERVER_CLIENT_SUPPORT_KICKOFF_EVENT = "server.client_support_kickoff_event";
public static final String MESSAGE_ROAMING = "message.roaming"; public static final String MESSAGE_ROAMING = "message.roaming";
public static final String MESSAGE_Compensate_Time_Limit = "message.compensate_time_limit";
public static final String MESSAGE_Remote_History_Message = "message.remote_history_message"; public static final String MESSAGE_Remote_History_Message = "message.remote_history_message";
public static final String MESSAGE_Remote_Chatroom_History_Message = "message.chatroom_remote_history_message"; public static final String MESSAGE_Remote_Chatroom_History_Message = "message.chatroom_remote_history_message";
@@ -141,13 +146,16 @@ public final class BrokerConstants {
public static final String MESSAGE_NO_Forward_Admin_Message = "message.no_forward_admin_message"; public static final String MESSAGE_NO_Forward_Admin_Message = "message.no_forward_admin_message";
public static final String MESSAGE_Forward_With_Client_Info = "message.forward_with_client_info";
public static final String ROBOT_Callback_With_Client_Info = "robot.callback_with_client_info";
public static final String CHANNEL_Callback_With_Client_Info = "channel.callback_with_client_info";
public static final String FRIEND_Disable_Search = "friend.disable_search"; public static final String FRIEND_Disable_Search = "friend.disable_search";
public static final String FRIEND_Disable_NickName_Search = "friend.disable_nick_name_search"; public static final String FRIEND_Disable_NickName_Search = "friend.disable_nick_name_search";
public static final String FRIEND_Disable_Friend_Request = "friend.disable_friend_request"; public static final String FRIEND_Disable_Friend_Request = "friend.disable_friend_request";
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";
@@ -27,8 +27,6 @@ public class ChannelListenMember extends IMHandler<WFCMessage.ListenChannel> {
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) { if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
WFCMessage.ModifyUserSettingReq modifyUserSettingReq = WFCMessage.ModifyUserSettingReq.newBuilder().setScope(kUserSettingListenedChannels).setKey(request.getChannelId()).setValue(request.getListen() > 0 ? "1" : "0").build(); WFCMessage.ModifyUserSettingReq modifyUserSettingReq = WFCMessage.ModifyUserSettingReq.newBuilder().setScope(kUserSettingListenedChannels).setKey(request.getChannelId()).setValue(request.getListen() > 0 ? "1" : "0").build();
mServer.onApiMessage(fromUser, null, modifyUserSettingReq.toByteArray(), 0, fromUser, PutUserSettingTopic, requestSourceType); mServer.onApiMessage(fromUser, null, modifyUserSettingReq.toByteArray(), 0, fromUser, PutUserSettingTopic, requestSourceType);
publisher.notifyChannelListenStatusChanged(m_messagesStore.getChannelInfo(request.getChannelId()), fromUser, request.getListen() > 0);
} }
return errorCode; return errorCode;
} }
@@ -0,0 +1,28 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <heavyrain.lee@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package 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 static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS;
@Handler(IMTopic.ApplicationConfigRequestTopic)
public class ConfigApplicationHandler extends IMHandler<WFCMessage.ApplicationConfigRequest> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.ApplicationConfigRequest request, Qos1PublishHandler.IMCallback callback) {
ErrorCode errorCode = m_messagesStore.configApplication(request.getAppId(), request.getAppType(), request.getTimestamp(), request.getNonce(), request.getSignature());
return errorCode;
}
}
@@ -21,6 +21,9 @@ import win.liyufan.im.IMTopic;
public class CreateGroupHandler extends GroupHandler<WFCMessage.CreateGroupRequest> { public class CreateGroupHandler extends GroupHandler<WFCMessage.CreateGroupRequest> {
@Override @Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.CreateGroupRequest request, Qos1PublishHandler.IMCallback callback) { public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.CreateGroupRequest request, Qos1PublishHandler.IMCallback callback) {
if(request.getGroup().getGroupInfo().getType() < 0 || request.getGroup().getGroupInfo().getType() > 2) {
return ErrorCode.ERROR_CODE_INVALID_DATA;
}
if (!StringUtil.isNullOrEmpty(request.getGroup().getGroupInfo().getTargetId())) { if (!StringUtil.isNullOrEmpty(request.getGroup().getGroupInfo().getTargetId())) {
WFCMessage.GroupInfo existGroupInfo = m_messagesStore.getGroupInfo(request.getGroup().getGroupInfo().getTargetId()); WFCMessage.GroupInfo existGroupInfo = m_messagesStore.getGroupInfo(request.getGroup().getGroupInfo().getTargetId());
@@ -19,11 +19,11 @@ import static win.liyufan.im.IMTopic.PutUserSettingTopic;
import static win.liyufan.im.UserSettingScope.kUserSettingMyChannels; import static win.liyufan.im.UserSettingScope.kUserSettingMyChannels;
@Handler(value = IMTopic.DestroyChannelInfoTopic) @Handler(value = IMTopic.DestroyChannelInfoTopic)
public class DistroyChannelHandler extends GroupHandler<WFCMessage.IDBuf> { public class DestroyChannelHandler extends GroupHandler<WFCMessage.IDBuf> {
@Override @Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.IDBuf request, Qos1PublishHandler.IMCallback callback) { public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.IDBuf request, Qos1PublishHandler.IMCallback callback) {
boolean isAdmin = requestSourceType == ProtoConstants.RequestSourceType.Request_From_Admin; boolean isAdmin = requestSourceType == ProtoConstants.RequestSourceType.Request_From_Admin;
ErrorCode errorCode = m_messagesStore.distoryChannel(fromUser, request.getId(), isAdmin); ErrorCode errorCode = m_messagesStore.destroyChannel(fromUser, request.getId(), isAdmin);
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) { if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
WFCMessage.ModifyUserSettingReq modifyUserSettingReq = WFCMessage.ModifyUserSettingReq.newBuilder().setScope(kUserSettingMyChannels).setKey(request.getId()).setValue("0").build(); WFCMessage.ModifyUserSettingReq modifyUserSettingReq = WFCMessage.ModifyUserSettingReq.newBuilder().setScope(kUserSettingMyChannels).setKey(request.getId()).setValue("0").build();
mServer.onApiMessage(fromUser, null, modifyUserSettingReq.toByteArray(), 0, fromUser, PutUserSettingTopic, requestSourceType); mServer.onApiMessage(fromUser, null, modifyUserSettingReq.toByteArray(), 0, fromUser, PutUserSettingTopic, requestSourceType);
@@ -0,0 +1,33 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <heavyrain.lee@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package 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 static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS;
@Handler(IMTopic.GetApplicationTokenRequestTopic)
public class GetApplicationTokenHandler extends IMHandler<WFCMessage.AuthCodeRequest> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, ProtoConstants.RequestSourceType requestSourceType, WFCMessage.AuthCodeRequest request, Qos1PublishHandler.IMCallback callback) {
String authCode = m_messagesStore.getApplicationAuthCode(fromUser, request.getTargetId(), request.getType(), request.getHost());
if(authCode != null) {
byte[] data = WFCMessage.IDBuf.newBuilder().setId(authCode).build().toByteArray();
ackPayload.ensureWritable(data.length).writeBytes(data);
return ERROR_CODE_SUCCESS;
}
return ErrorCode.INVALID_PARAMETER;
}
}
@@ -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,18 +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)) {
ByteBuf ackPayload = Unpooled.buffer();
ackPayload.ensureWritable(1).writeByte(ERROR_CODE_OVER_FREQUENCY.getCode());
try {
callback.onIMHandled(ERROR_CODE_OVER_FREQUENCY, ackPayload);
} catch (Exception e) {
e.printStackTrace();
Utility.printExecption(LOG, e);
}
return ErrorCode.ERROR_CODE_OVER_FREQUENCY; return ErrorCode.ERROR_CODE_OVER_FREQUENCY;
} }
return ErrorCode.ERROR_CODE_SUCCESS; return ErrorCode.ERROR_CODE_SUCCESS;
@@ -34,7 +34,7 @@ public class RecallMessageHandler extends IMHandler<WFCMessage.INT64Buf> {
return ErrorCode.ERROR_CODE_NOT_EXIST; return ErrorCode.ERROR_CODE_NOT_EXIST;
} }
publish(fromUser, clientID, message, requestSourceType); publish(message.getFromUser(), clientID, message, requestSourceType);
return errorCode; return errorCode;
} }
@@ -9,19 +9,18 @@
package io.moquette.imhandler; package io.moquette.imhandler;
import cn.wildfirechat.pojos.MessagePayload; import cn.wildfirechat.pojos.MessagePayload;
import cn.wildfirechat.pojos.OutputClient;
import cn.wildfirechat.proto.ProtoConstants; import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage; import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.hazelcast.util.StringUtil; import com.hazelcast.util.StringUtil;
import io.moquette.BrokerConstants; import io.moquette.BrokerConstants;
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.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.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -32,6 +31,7 @@ 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 String mMentionForwardUrl = null; private String mMentionForwardUrl = null;
private int mBlacklistStrategy = 0; //黑名单中时,0失败,1吞掉。 private int mBlacklistStrategy = 0; //黑名单中时,0失败,1吞掉。
@@ -54,7 +54,7 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
} }
} }
} }
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 {
@@ -171,12 +171,20 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
} }
message = message.toBuilder().setFromUser(fromUser).setMessageId(messageId).setServerTimestamp(timestamp).build(); message = message.toBuilder().setFromUser(fromUser).setMessageId(messageId).setServerTimestamp(timestamp).build();
OutputClient outputClient = null;
if(m_messagesStore.isForwardMessageWithClientInfo() && requestSourceType == ProtoConstants.RequestSourceType.Request_From_User && !StringUtil.isNullOrEmpty(clientID)) {
MemorySessionStore.Session session = m_sessionsStore.getSession(clientID);
if(session != null && session.getUsername().equals(fromUser)) {
outputClient = new OutputClient(session.getPlatform(), clientID);
}
}
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)) {
publisher.forwardMessage(message, mForwardUrl); publisher.forwardMessage(message, mForwardUrl, outputClient);
} }
if(!StringUtil.isNullOrEmpty(mMentionForwardUrl) && message.hasContent() && message.getContent().getMentionedType() != 0 && !(isAdmin && mNoForwardAdminMessage)) { if(!StringUtil.isNullOrEmpty(mMentionForwardUrl) && message.hasContent() && message.getContent().getMentionedType() != 0 && !(isAdmin && mNoForwardAdminMessage)) {
publisher.forwardMessage(message, mMentionForwardUrl); publisher.forwardMessage(message, mMentionForwardUrl, outputClient);
} }
if (!isAdmin) { if (!isAdmin) {
@@ -184,6 +192,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) {
@@ -205,7 +216,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() {
@@ -994,7 +994,7 @@ public class DatabaseStore {
} }
sb.append(")"); sb.append(")");
sb.append(" and _scope = 6 and _key = ?"); sb.append(" and _scope in (5,6,26) and _key = ?");
statement = connection.prepareStatement(sb.toString()); statement = connection.prepareStatement(sb.toString());
int index = 1; int index = 1;
@@ -1142,23 +1142,21 @@ public class DatabaseStore {
try { try {
connection = DBUtil.getConnection(); connection = DBUtil.getConnection();
StringBuilder sb = new StringBuilder("delete from t_user_setting where _scope in (1,3,5,7) and _uid in ("); StringBuilder sb = new StringBuilder("delete from t_user_setting where _scope in (1,3,5,6,7,19) and _uid in (");
for (int i = 0; i < users.size(); i++) { for (int i = 0; i < users.size(); i++) {
sb.append("?"); sb.append("?");
if (i != users.size() - 1) { if (i != users.size() - 1) {
sb.append(","); sb.append(",");
} }
} }
sb.append(") and _key like '1-_-"); sb.append(") and _key like ?");
sb.append(groupId);
sb.append("'");
statement = connection.prepareStatement(sb.toString()); statement = connection.prepareStatement(sb.toString());
int index = 1; int index = 1;
for (String userId:users) { for (String userId:users) {
statement.setString(index++, userId); statement.setString(index++, userId);
} }
statement.setString(index++, "1-_-" + groupId);
int count = statement.executeUpdate(); int count = statement.executeUpdate();
LOG.info("Update rows {}", count); LOG.info("Update rows {}", count);
@@ -2974,7 +2972,8 @@ public class DatabaseStore {
", `_secret`" + ", `_secret`" +
", `_callback`" + ", `_callback`" +
", `_automatic`" + ", `_automatic`" +
", `_dt`) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + ", `_menu`" +
", `_dt`) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" +
" ON DUPLICATE KEY UPDATE `_name`=?" + " ON DUPLICATE KEY UPDATE `_name`=?" +
", `_portrait`=?" + ", `_portrait`=?" +
", `_owner`=?" + ", `_owner`=?" +
@@ -2984,12 +2983,20 @@ public class DatabaseStore {
", `_secret`=?" + ", `_secret`=?" +
", `_callback`=?" + ", `_callback`=?" +
", `_automatic`=?" + ", `_automatic`=?" +
", `_menu`=?" +
", `_dt`=?"; ", `_dt`=?";
statement = connection.prepareStatement(sql); statement = connection.prepareStatement(sql);
int index = 1; WFCMessage.ChannelMenuList.Builder builder = WFCMessage.ChannelMenuList.newBuilder();
if (!channelInfo.getMenuList().isEmpty()) {
for (WFCMessage.ChannelMenu menuBtn:channelInfo.getMenuList()) {
builder.addMenu(menuBtn);
}
}
byte[] menuBytes = builder.build().toByteArray();
int index = 1;
statement.setString(index++, channelInfo.getTargetId()); statement.setString(index++, channelInfo.getTargetId());
statement.setString(index++, channelInfo.getName()); statement.setString(index++, channelInfo.getName());
statement.setString(index++, channelInfo.getPortrait()); statement.setString(index++, channelInfo.getPortrait());
@@ -3000,6 +3007,7 @@ public class DatabaseStore {
statement.setString(index++, channelInfo.getSecret()); statement.setString(index++, channelInfo.getSecret());
statement.setString(index++, channelInfo.getCallback()); statement.setString(index++, channelInfo.getCallback());
statement.setInt(index++, channelInfo.getAutomatic()); statement.setInt(index++, channelInfo.getAutomatic());
statement.setBytes(index++, menuBytes);
statement.setLong(index++, channelInfo.getUpdateDt() == 0 ? System.currentTimeMillis() : channelInfo.getUpdateDt()); statement.setLong(index++, channelInfo.getUpdateDt() == 0 ? System.currentTimeMillis() : channelInfo.getUpdateDt());
statement.setString(index++, channelInfo.getName()); statement.setString(index++, channelInfo.getName());
@@ -3011,6 +3019,7 @@ public class DatabaseStore {
statement.setString(index++, channelInfo.getSecret()); statement.setString(index++, channelInfo.getSecret());
statement.setString(index++, channelInfo.getCallback()); statement.setString(index++, channelInfo.getCallback());
statement.setInt(index++, channelInfo.getAutomatic()); statement.setInt(index++, channelInfo.getAutomatic());
statement.setBytes(index++, menuBytes);
statement.setLong(index++, channelInfo.getUpdateDt() == 0 ? System.currentTimeMillis() : channelInfo.getUpdateDt()); statement.setLong(index++, channelInfo.getUpdateDt() == 0 ? System.currentTimeMillis() : channelInfo.getUpdateDt());
int count = statement.executeUpdate(); int count = statement.executeUpdate();
@@ -3063,6 +3072,7 @@ public class DatabaseStore {
", `_secret`" + ", `_secret`" +
", `_callback`" + ", `_callback`" +
", `_automatic`" + ", `_automatic`" +
", `_menu`" +
", `_dt` from t_channel where `_cid` = ?"; ", `_dt` from t_channel where `_cid` = ?";
statement = connection.prepareStatement(sql); statement = connection.prepareStatement(sql);
@@ -3113,6 +3123,21 @@ public class DatabaseStore {
intValue = rs.getInt(index++); intValue = rs.getInt(index++);
builder.setAutomatic(intValue); builder.setAutomatic(intValue);
try {
byte[] bytes = null;
Blob blob = rs.getBlob(index++);
if (blob != null) {
bytes = toByteArray(blob.getBinaryStream());
}
WFCMessage.ChannelMenuList menuButtonList = WFCMessage.ChannelMenuList.parseFrom(bytes);
if (menuButtonList.getMenuCount() > 0) {
builder.addAllMenu(menuButtonList.getMenuList());
}
} catch (IOException e) {
e.printStackTrace();
}
long longValue = rs.getLong(index++); long longValue = rs.getLong(index++);
builder.setUpdateDt(longValue); builder.setUpdateDt(longValue);
@@ -3334,6 +3359,57 @@ public class DatabaseStore {
}); });
} }
List<String> getUserChannels(String userId) {
List<String> out = new ArrayList<>();
Connection connection = null;
PreparedStatement statement = null;
ResultSet rs = null;
try {
connection = DBUtil.getConnection();
String sql = "select _cid from t_channel_listener where _mid = ?";
statement = connection.prepareStatement(sql);
int index = 1;
statement.setString(index++, userId);
rs = statement.executeQuery();
while (rs.next()) {
String value = rs.getString(1);
if (!StringUtil.isNullOrEmpty(value)) {
out.add(value);
}
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Utility.printExecption(LOG, e, RDBS_Exception);
} finally {
DBUtil.closeDB(connection, statement, rs);
}
return out;
}
void clearChannelListener(final String channelId) {
LOG.info("Database remove channel {}", channelId);
Connection connection = null;
PreparedStatement statement = null;
try {
connection = DBUtil.getConnection();
String sql = "delete from t_channel_listener where _cid=?";
statement = connection.prepareStatement(sql);
statement.setString(1, channelId);
int count = statement.executeUpdate();
LOG.info("Update rows {}", count);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Utility.printExecption(LOG, e, RDBS_Exception);
} finally {
DBUtil.closeDB(connection, statement);
}
}
public Set<String> getSensitiveWord() { public Set<String> getSensitiveWord() {
Connection connection = null; Connection connection = null;
PreparedStatement statement = null; PreparedStatement statement = null;
@@ -23,6 +23,7 @@ import com.google.gson.Gson;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.hazelcast.core.*; import com.hazelcast.core.*;
import com.hazelcast.util.StringUtil; import com.hazelcast.util.StringUtil;
import com.xiaoleilu.loServer.action.admin.AdminAction;
import com.xiaoleilu.loServer.model.FriendData; import com.xiaoleilu.loServer.model.FriendData;
import cn.wildfirechat.common.ErrorCode; import cn.wildfirechat.common.ErrorCode;
import io.moquette.BrokerConstants; import io.moquette.BrokerConstants;
@@ -31,14 +32,18 @@ import io.moquette.server.Constants;
import io.moquette.server.Server; import io.moquette.server.Server;
import io.moquette.spi.IMatchingCondition; import io.moquette.spi.IMatchingCondition;
import io.moquette.spi.IMessagesStore; import io.moquette.spi.IMessagesStore;
import io.moquette.spi.impl.security.AES;
import io.moquette.spi.security.Tokenor; import io.moquette.spi.security.Tokenor;
import io.moquette.spi.impl.subscriptions.Topic; import io.moquette.spi.impl.subscriptions.Topic;
import io.netty.handler.codec.mqtt.MqttVersion; import io.netty.handler.codec.mqtt.MqttVersion;
import org.apache.commons.codec.digest.DigestUtils;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import win.liyufan.im.*; import win.liyufan.im.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -91,6 +96,7 @@ public class MemoryMessagesStore implements IMessagesStore {
private static final String CHANNEL_LISTENERS = "channel_listeners"; private static final String CHANNEL_LISTENERS = "channel_listeners";
private static boolean IS_MESSAGE_ROAMING = true; private static boolean IS_MESSAGE_ROAMING = true;
private static long msgCompensateTimeLimit = 300000;
private static boolean IS_MESSAGE_REMOTE_HISTORY_MESSAGE = true; private static boolean IS_MESSAGE_REMOTE_HISTORY_MESSAGE = true;
private static boolean IS_CHATROOM_MESSAGE_REMOTE_HISTORY_MESSAGE = true; private static boolean IS_CHATROOM_MESSAGE_REMOTE_HISTORY_MESSAGE = true;
@@ -132,7 +138,6 @@ public class MemoryMessagesStore implements IMessagesStore {
private long mFriendRequestDuration = 7 * 24 * 60 * 60 * 1000; private long mFriendRequestDuration = 7 * 24 * 60 * 60 * 1000;
private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000; private long mFriendRejectDuration = 30 * 24 * 60 * 60 * 1000;
private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000; private long mFriendRequestExpiration = 7 * 24 * 60 * 60 * 1000;
private boolean mFriendNewWelcomeMessage = true;
private boolean mMultiPlatformNotification = false; private boolean mMultiPlatformNotification = false;
private boolean mMobileDefaultSilentWhenPCOnline = true; private boolean mMobileDefaultSilentWhenPCOnline = true;
@@ -162,6 +167,10 @@ public class MemoryMessagesStore implements IMessagesStore {
private int mSyncDataPartSize = 0; private int mSyncDataPartSize = 0;
private boolean keepDisplayNameWhenDestroyUser = true; private boolean keepDisplayNameWhenDestroyUser = true;
private boolean mForwardMessageWithClientInfo = false;
private boolean mRobotCallbackWithClientInfo = false;
private boolean mChannelCallbackWithClientInfo = false;
private Set<Integer> mUserHideProperties = new HashSet<>(); private Set<Integer> mUserHideProperties = new HashSet<>();
MemoryMessagesStore(Server server, DatabaseStore databaseStore) { MemoryMessagesStore(Server server, DatabaseStore databaseStore) {
@@ -169,6 +178,11 @@ public class MemoryMessagesStore implements IMessagesStore {
this.databaseStore = databaseStore; this.databaseStore = databaseStore;
IS_MESSAGE_ROAMING = "1".equals(m_Server.getConfig().getProperty(MESSAGE_ROAMING)); IS_MESSAGE_ROAMING = "1".equals(m_Server.getConfig().getProperty(MESSAGE_ROAMING));
try {
msgCompensateTimeLimit = Long.parseLong(m_Server.getConfig().getProperty(MESSAGE_Compensate_Time_Limit, "300000"));
} catch (NumberFormatException e) {
}
IS_MESSAGE_REMOTE_HISTORY_MESSAGE = "1".equals(m_Server.getConfig().getProperty(MESSAGE_Remote_History_Message)); IS_MESSAGE_REMOTE_HISTORY_MESSAGE = "1".equals(m_Server.getConfig().getProperty(MESSAGE_Remote_History_Message));
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));
@@ -241,15 +255,6 @@ public class MemoryMessagesStore implements IMessagesStore {
printMissConfigLog(FRIEND_Request_Expiration_Duration, mFriendRequestExpiration + ""); printMissConfigLog(FRIEND_Request_Expiration_Duration, mFriendRequestExpiration + "");
} }
try {
mFriendNewWelcomeMessage = Boolean.parseBoolean(m_Server.getConfig().getProperty(FRIEND_New_Welcome_Message));
} catch (Exception e) {
e.printStackTrace();
Utility.printExecption(LOG, e);
printMissConfigLog(FRIEND_New_Welcome_Message, mFriendNewWelcomeMessage + "");
}
try { try {
mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active)); mChatroomRejoinWhenActive = Boolean.parseBoolean(m_Server.getConfig().getProperty(BrokerConstants.CHATROOM_Rejoin_When_Active));
} catch (Exception e) { } catch (Exception e) {
@@ -442,6 +447,21 @@ public class MemoryMessagesStore implements IMessagesStore {
e.printStackTrace(); e.printStackTrace();
} }
try {
mForwardMessageWithClientInfo = Boolean.parseBoolean(server.getConfig().getProperty(BrokerConstants.MESSAGE_Forward_With_Client_Info, "false"));
} catch (Exception e) {
}
try {
mRobotCallbackWithClientInfo = Boolean.parseBoolean(server.getConfig().getProperty(BrokerConstants.ROBOT_Callback_With_Client_Info, "false"));
} catch (Exception e) {
}
try {
mChannelCallbackWithClientInfo = Boolean.parseBoolean(server.getConfig().getProperty(BrokerConstants.CHANNEL_Callback_With_Client_Info, "false"));
} catch (Exception e) {
}
} }
@@ -509,8 +529,17 @@ public class MemoryMessagesStore implements IMessagesStore {
int pullType = ProtoConstants.PullType.Pull_Normal; int pullType = ProtoConstants.PullType.Pull_Normal;
if (type == ProtoConstants.ConversationType.ConversationType_Private) { if (type == ProtoConstants.ConversationType.ConversationType_Private) {
notifyReceivers.add(fromUser); if(message.getToCount() > 0) {
notifyReceivers.add(message.getConversation().getTarget()); if(message.getToList().contains(fromUser)) {
notifyReceivers.add(fromUser);
}
if(message.getToList().contains(message.getConversation().getTarget())) {
notifyReceivers.add(message.getConversation().getTarget());
}
} else {
notifyReceivers.add(fromUser);
notifyReceivers.add(message.getConversation().getTarget());
}
pullType = ProtoConstants.PullType.Pull_Normal; pullType = ProtoConstants.PullType.Pull_Normal;
} else if (type == ProtoConstants.ConversationType.ConversationType_Group) { } else if (type == ProtoConstants.ConversationType.ConversationType_Group) {
notifyReceivers.add(fromUser); notifyReceivers.add(fromUser);
@@ -564,8 +593,7 @@ public class MemoryMessagesStore implements IMessagesStore {
if (channelInfo != null) { if (channelInfo != null) {
if (channelInfo.getOwner().equals(fromUser)) { if (channelInfo.getOwner().equals(fromUser)) {
if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_Channel if(requestSourceType == ProtoConstants.RequestSourceType.Request_From_Channel
&& !StringUtil.isNullOrEmpty(channelInfo.getCallback()) && channelInfo.getAutomatic() != 0) {
&& channelInfo.getAutomatic() == 1) {
LOG.info("Channel api message not send to the owner when automatic"); LOG.info("Channel api message not send to the owner when automatic");
} else { } else {
notifyReceivers.add(fromUser); notifyReceivers.add(fromUser);
@@ -595,8 +623,10 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
} else { } else {
notifyReceivers.add(fromUser); notifyReceivers.add(fromUser);
if (StringUtil.isNullOrEmpty(channelInfo.getCallback()) || channelInfo.getAutomatic() == 0) { if (channelInfo.getAutomatic() == 0) {
notifyReceivers.add(channelInfo.getOwner()); notifyReceivers.add(channelInfo.getOwner());
} else {
LOG.info("Channel api message not send to the owner when automatic");
} }
} }
} else { } else {
@@ -714,7 +744,7 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
} }
if (noRoaming && (System.currentTimeMillis() - bundle.getMessage().getServerTimestamp() > 5 * 60 * 1000)) { if (noRoaming && (System.currentTimeMillis() - bundle.getMessage().getServerTimestamp() > msgCompensateTimeLimit)) {
continue; continue;
} }
@@ -1112,7 +1142,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);
@@ -1131,7 +1161,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);
@@ -1149,7 +1179,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);
@@ -1162,7 +1192,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);
@@ -1179,7 +1209,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);
@@ -1195,7 +1225,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);
@@ -1213,7 +1243,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);
@@ -1275,11 +1305,31 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
} }
MultiMap<String, WFCMessage.GroupMember> groupMembers = hzInstance.getMultiMap(GROUP_MEMBERS);
Collection<WFCMessage.GroupMember> members = groupMembers.get(groupId);
if (members == null || members.size() == 0) {
members = loadGroupMemberFromDB(hzInstance, groupId);
}
ArrayList<String> existMemberIds = new ArrayList<>();
for (WFCMessage.GroupMember member : members) {
if (member.getType() != GroupMemberType_Removed) {
existMemberIds.add(member.getMemberId());
}
}
long updateDt = System.currentTimeMillis(); long updateDt = System.currentTimeMillis();
List<WFCMessage.GroupMember> tmp = new ArrayList<>(); List<WFCMessage.GroupMember> tmp = new ArrayList<>();
ArrayList<String> newInviteUsers = new ArrayList<>(); int newMemberCount = 0;
for (WFCMessage.GroupMember member : memberList) { for (WFCMessage.GroupMember member : memberList) {
if (existMemberIds.contains(member.getMemberId()) && member.getType() != GroupMemberType_Removed) {
if(!isAdmin)
continue;
} else {
newMemberCount++;
}
WFCMessage.GroupMember.Builder builder = member.toBuilder(); WFCMessage.GroupMember.Builder builder = member.toBuilder();
builder.setUpdateDt(updateDt).setCreateDt(updateDt); builder.setUpdateDt(updateDt).setCreateDt(updateDt);
if (member.getMemberId().equals(groupInfo.getOwner())) { if (member.getMemberId().equals(groupInfo.getOwner())) {
@@ -1299,29 +1349,23 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
member = builder.build(); member = builder.build();
tmp.add(member); tmp.add(member);
newInviteUsers.add(member.getMemberId());
updateDt++; updateDt++;
} }
memberList = tmp; memberList = tmp;
MultiMap<String, WFCMessage.GroupMember> groupMembers = hzInstance.getMultiMap(GROUP_MEMBERS); if (memberList.size() == 0) {
Collection<WFCMessage.GroupMember> members = groupMembers.get(groupId); if (!isAdmin) {
if (members == null || members.size() == 0) { return ErrorCode.ERROR_CODE_ALREADY_IN_GROUP;
members = loadGroupMemberFromDB(hzInstance, groupId); } else {
} return ErrorCode.ERROR_CODE_SUCCESS;
if (maxCount != Integer.MAX_VALUE) {
int existCount = 0;
for (WFCMessage.GroupMember member : members) {
if (member.getType() != GroupMemberType_Removed) {
existCount++;
}
} }
if (existCount + newInviteUsers.size() > maxCount) { } else {
if (existMemberIds.size() + newMemberCount > maxCount) {
return ErrorCode.ERROR_CODE_GROUP_EXCEED_MAX_MEMBER_COUNT; return ErrorCode.ERROR_CODE_GROUP_EXCEED_MAX_MEMBER_COUNT;
} }
} }
databaseStore.persistGroupMember(groupId, memberList, true); databaseStore.persistGroupMember(groupId, memberList, true);
databaseStore.updateGroupMemberCountDt(groupId); databaseStore.updateGroupMemberCountDt(groupId);
@@ -3301,9 +3345,158 @@ public class MemoryMessagesStore implements IMessagesStore {
return true; return true;
} }
private String ensureSecretLength(String secret) {
while ( secret.length() < 16) {
secret += "w";
secret += secret;
}
return secret;
}
@Override @Override
public boolean isNewFriendWelcomeMessage() { public String getApplicationAuthCode(String fromUser, String applicationId, int type, String host) {
return mFriendNewWelcomeMessage; String secret = null;
if(type == ProtoConstants.ApplicationType.ApplicationType_Robot) {
WFCMessage.Robot robotData = getRobot(applicationId);
if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) {
try {
URL url = new URL(robotData.getCallback());
if(url.getHost() == null) {
LOG.error("get application auth code error, application callback is invalid url");
} else if(url.getHost().equals(host)) {
secret = robotData.getSecret();
} else {
LOG.error("get application auth code error, request host is not the same host with callback host");
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
} else {
String errorReason;
if(robotData == null) {
errorReason = "Robot not exist.";
} else if(StringUtil.isNullOrEmpty(robotData.getCallback())) {
errorReason = "Robot no callback address";
} else {
errorReason = "Robot no secret";
}
LOG.warn("get application auth code error, reason {}", errorReason);
}
} else if(type == ProtoConstants.ApplicationType.ApplicationType_Channel) {
WFCMessage.ChannelInfo channelData = getChannelInfo(applicationId);
if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) {
try {
URL url = new URL(channelData.getCallback());
if(url.getHost() == null) {
LOG.error("get application auth code error, application callback is invalid url");
} else if(url.getHost().equals(host)) {
secret = channelData.getSecret();
} else {
LOG.error("get application auth code error, request host is not the same host with callback host");
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
} else {
String errorReason;
if(channelData == null) {
errorReason = "Channel not exist.";
} else if(StringUtil.isNullOrEmpty(channelData.getCallback())) {
errorReason = "Channel no callback address";
} else {
errorReason = "Channel no secret";
}
LOG.warn("get application auth code error, reason {}", errorReason);
}
} else if(type == ProtoConstants.ApplicationType.ApplicationType_Admin) {
secret = AdminAction.getSecretKey();
applicationId = "wfadmin";
}
if(!StringUtil.isNullOrEmpty(secret)) {
secret = ensureSecretLength(secret);
byte[] token = AES.AESEncrypt(fromUser + "?|?" + System.currentTimeMillis() + "?|?" + applicationId + "?|?" + type, secret);
return new String(Base64.getEncoder().encode(token));
}
return null;
}
@Override
public String verifyApplicationAuthCode(String authCode, String applicationId, int type) {
String secret = null;
if(type == ProtoConstants.ApplicationType.ApplicationType_Robot) {
WFCMessage.Robot robotData = getRobot(applicationId);
if(robotData != null && !StringUtil.isNullOrEmpty(robotData.getCallback()) && !StringUtil.isNullOrEmpty(robotData.getSecret())) {
secret = robotData.getSecret();
}
} else if(type == ProtoConstants.ApplicationType.ApplicationType_Channel) {
WFCMessage.ChannelInfo channelData = getChannelInfo(applicationId);
if(channelData != null && !StringUtil.isNullOrEmpty(channelData.getCallback()) && !StringUtil.isNullOrEmpty(channelData.getSecret())) {
secret = channelData.getSecret();
}
} else if(type == ProtoConstants.ApplicationType.ApplicationType_Admin) {
secret = AdminAction.getSecretKey();
applicationId = "wfadmin";
}
secret = ensureSecretLength(secret);
byte[] data = Base64.getDecoder().decode(authCode);
data = AES.AESDecrypt(data, secret, true);
if (data == null || data.length == 0) {
return null;
} else {
String str = new String(data);
String[] strArr = str.split("\\?\\|\\?");
if (strArr.length == 4) {
if (applicationId.equals(strArr[2])) {
long timestamp= Long.parseLong(strArr[1]);
if(System.currentTimeMillis() - timestamp > 60*1000) {
return null;
}
return strArr[0];
}
}
}
return null;
}
@Override
public ErrorCode configApplication(String appId, int appType, long timestamp, String nonce, String signature) {
String secret;
if(System.currentTimeMillis()/1000 - timestamp > 300) {
return ErrorCode.ERROR_CODE_SIGN_EXPIRED;
}
if(appType == ProtoConstants.ApplicationType.ApplicationType_Robot) {
WFCMessage.Robot robotData = getRobot(appId);
if(robotData != null) {
secret = robotData.getSecret();
} else {
return ErrorCode.ERROR_CODE_NOT_EXIST;
}
} else if(appType == ProtoConstants.ApplicationType.ApplicationType_Channel) {
WFCMessage.ChannelInfo channelData = getChannelInfo(appId);
if(channelData != null) {
secret = channelData.getSecret();
} else {
return ErrorCode.ERROR_CODE_NOT_EXIST;
}
} else {
LOG.error("Config application type only support 0 or 1");
return ErrorCode.INVALID_PARAMETER;
}
String str = nonce + "|" + appId + "|" + timestamp + "|" + secret;
String sign = DigestUtils.sha1Hex(str);
if(sign.equals(signature)) {
return ErrorCode.ERROR_CODE_SUCCESS;
}
return ErrorCode.ERROR_CODE_AUTH_FAILURE;
} }
@Override @Override
@@ -3651,7 +3844,7 @@ public class MemoryMessagesStore implements IMessagesStore {
@Override @Override
public void clearUserChannels(String userId) { public void clearUserChannels(String userId) {
databaseStore.getUserChannels(userId).forEach(s -> listenChannel(userId, s, false));
} }
@@ -3693,9 +3886,15 @@ public class MemoryMessagesStore implements IMessagesStore {
e.printStackTrace(); e.printStackTrace();
Utility.printExecption(LOG, e); Utility.printExecption(LOG, e);
} }
} else if(modifyType == Modify_Channel_Menu) {
OutputGetChannelInfo.OutputMenuList outputMenuButtons = GsonUtil.gson.fromJson(value, OutputGetChannelInfo.OutputMenuList.class);
if (!outputMenuButtons.isEmpty()) {
for (OutputGetChannelInfo.OutputMenu outputMenuButton : outputMenuButtons) {
newInfoBuilder.addMenu(outputMenuButton.toPbInfo());
}
}
} }
newInfoBuilder.setUpdateDt(System.currentTimeMillis()); newInfoBuilder.setUpdateDt(System.currentTimeMillis());
mIMap.put(channelId, newInfoBuilder.build()); mIMap.put(channelId, newInfoBuilder.build());
callbackChannelInfoUpdateEvent(operator, channelId, Channel_Event_Update); callbackChannelInfoUpdateEvent(operator, channelId, Channel_Event_Update);
@@ -3731,7 +3930,7 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
@Override @Override
public ErrorCode distoryChannel(String operator, String channelId, boolean isAdmin) { public ErrorCode destroyChannel(String operator, String channelId, boolean isAdmin) {
HazelcastInstance hzInstance = m_Server.getHazelcastInstance(); HazelcastInstance hzInstance = m_Server.getHazelcastInstance();
IMap<String, WFCMessage.ChannelInfo> mIMap = hzInstance.getMap(CHANNELS); IMap<String, WFCMessage.ChannelInfo> mIMap = hzInstance.getMap(CHANNELS);
@@ -3751,6 +3950,8 @@ public class MemoryMessagesStore implements IMessagesStore {
newInfoBuilder.setStatus(oldInfo.getStatus() | Channel_State_Mask_Deleted); newInfoBuilder.setStatus(oldInfo.getStatus() | Channel_State_Mask_Deleted);
newInfoBuilder.setUpdateDt(System.currentTimeMillis()); newInfoBuilder.setUpdateDt(System.currentTimeMillis());
mIMap.put(channelId, newInfoBuilder.build()); mIMap.put(channelId, newInfoBuilder.build());
databaseStore.clearChannelListener(channelId);
hzInstance.getMultiMap(CHANNEL_LISTENERS).remove(channelId);
callbackChannelInfoUpdateEvent(operator, channelId, Channel_Event_Destroy); callbackChannelInfoUpdateEvent(operator, channelId, Channel_Event_Destroy);
return ErrorCode.ERROR_CODE_SUCCESS; return ErrorCode.ERROR_CODE_SUCCESS;
@@ -3768,18 +3969,18 @@ public class MemoryMessagesStore implements IMessagesStore {
IMap<String, WFCMessage.ChannelInfo> mIMap = hzInstance.getMap(CHANNELS); IMap<String, WFCMessage.ChannelInfo> mIMap = hzInstance.getMap(CHANNELS);
WFCMessage.ChannelInfo channelInfo = mIMap.get(channelId); WFCMessage.ChannelInfo channelInfo = mIMap.get(channelId);
if(channelInfo != null && (channelInfo.getStatus() & ProtoConstants.ChannelState.Channel_State_Mask_Private) > 0) { if(channelInfo != null && (channelInfo.getStatus() & ProtoConstants.ChannelState.Channel_State_Mask_Private) > 0) {
return ErrorCode.ERROR_CODE_NOT_RIGHT; return ErrorCode.ERROR_CODE_NOT_EXIST;
} }
getChannelListener(channelId); try {
mWriteLock.lock();
if (listen) { databaseStore.updateChannelListener(channelId, operator, listen);
listeners.put(channelId, operator); listeners.remove(channelId);
} else { } finally {
listeners.remove(channelId, operator); mWriteLock.unlock();
} }
databaseStore.updateChannelListener(channelId, operator, listen);
IMHandler.getPublisher().notifyChannelListenStatusChanged(channelInfo, operator, listen);
return ErrorCode.ERROR_CODE_SUCCESS; return ErrorCode.ERROR_CODE_SUCCESS;
} }
@@ -3788,9 +3989,14 @@ public class MemoryMessagesStore implements IMessagesStore {
MultiMap<String, String> listeners = hzInstance.getMultiMap(CHANNEL_LISTENERS); MultiMap<String, String> listeners = hzInstance.getMultiMap(CHANNEL_LISTENERS);
Collection<String> result = listeners.get(channelId); Collection<String> result = listeners.get(channelId);
if (result.isEmpty()) { if (result.isEmpty()) {
result = databaseStore.getChannelListener(channelId); try {
for (String userId : result) { mWriteLock.lock();
listeners.put(channelId, userId); result = databaseStore.getChannelListener(channelId);
for (String userId : result) {
listeners.put(channelId, userId);
}
} finally {
mWriteLock.unlock();
} }
} }
return result; return result;
@@ -3819,12 +4025,7 @@ public class MemoryMessagesStore implements IMessagesStore {
} }
Collection<String> channelMembers = getChannelListener(channelId); Collection<String> channelMembers = getChannelListener(channelId);
if (channelMembers == null) { if (channelMembers == null || !channelMembers.contains(user)) {
return false;
}
Collection<String> chatroomMembers = getChannelListener(channelId);
if (chatroomMembers == null || !chatroomMembers.contains(user)) {
return false; return false;
} }
@@ -3850,12 +4051,7 @@ public class MemoryMessagesStore implements IMessagesStore {
@Override @Override
public Collection<String> getChannelSubscriber(String channelId) { public Collection<String> getChannelSubscriber(String channelId) {
MultiMap<String, String> channelMembers = m_Server.getHazelcastInstance().getMultiMap(CHANNEL_LISTENERS); return getChannelListener(channelId);
if (channelMembers == null) {
return new ArrayList<>();
}
return channelMembers.get(channelId);
} }
@Override @Override
@@ -3915,6 +4111,20 @@ public class MemoryMessagesStore implements IMessagesStore {
return mGroupVisibleQuitKickoffNotification; return mGroupVisibleQuitKickoffNotification;
} }
@Override
public boolean isForwardMessageWithClientInfo() {
return mForwardMessageWithClientInfo;
}
@Override
public boolean isRobotCallbackWithClientInfo() {
return mRobotCallbackWithClientInfo;
}
@Override
public boolean isChannelCallbackWithClientInfo() {
return mChannelCallbackWithClientInfo;
}
@Override @Override
public List<Integer> getClientForbiddenSendTypes() { public List<Integer> getClientForbiddenSendTypes() {
@@ -39,6 +39,7 @@ public class MemorySessionStore implements ISessionsStore {
private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class); private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class);
private boolean supportMultiEndpoint = false; private boolean supportMultiEndpoint = false;
private boolean supportMultiPCEndpoint = false;
private boolean clientSupportKickoff = false; private boolean clientSupportKickoff = false;
public static class Session implements Comparable<Session>{ public static class Session implements Comparable<Session>{
@@ -287,6 +288,11 @@ public class MemorySessionStore implements ISessionsStore {
Utility.printExecption(LOG, e); Utility.printExecption(LOG, e);
} }
try {
supportMultiPCEndpoint = Boolean.parseBoolean(server.getConfig().getProperty(BrokerConstants.SERVER_MULTI_PC_ENDPOINT, "false"));
} catch (Exception e) {
}
try { try {
clientSupportKickoff = Boolean.parseBoolean(server.getConfig().getProperty(BrokerConstants.SERVER_CLIENT_SUPPORT_KICKOFF_EVENT, "false")); clientSupportKickoff = Boolean.parseBoolean(server.getConfig().getProperty(BrokerConstants.SERVER_CLIENT_SUPPORT_KICKOFF_EVENT, "false"));
} catch (Exception e) { } catch (Exception e) {
@@ -385,7 +391,12 @@ public class MemorySessionStore implements ISessionsStore {
} }
databaseStore.clearMultiUser(username, clientID); databaseStore.clearMultiUser(username, clientID);
if (!supportMultiEndpoint && platform > 0) { if (!supportMultiEndpoint
&& platform > 0
&& !(platform == ProtoConstants.Platform.Platform_Windows && supportMultiPCEndpoint)
&& !(platform == ProtoConstants.Platform.Platform_LINUX && supportMultiPCEndpoint)
&& !(platform == ProtoConstants.Platform.Platform_OSX && supportMultiPCEndpoint)
) {
databaseStore.clearMultiEndpoint(username, clientID, platform); databaseStore.clearMultiEndpoint(username, clientID, platform);
if (userSessions.get(username) != null) { if (userSessions.get(username) != null) {
Iterator<String> it = userSessions.get(username).iterator(); Iterator<String> it = userSessions.get(username).iterator();
@@ -404,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 {
@@ -369,9 +369,6 @@ public class Server {
private String getServerIp(IConfig config) { private String getServerIp(IConfig config) {
String serverIp = config.getProperty(BrokerConstants.SERVER_IP_PROPERTY_NAME); String serverIp = config.getProperty(BrokerConstants.SERVER_IP_PROPERTY_NAME);
if (serverIp == null || serverIp.equals("0.0.0.0")) { if (serverIp == null || serverIp.equals("0.0.0.0")) {
String warning = "警告:无效配置,server.ip不能为空,需要配置服务的公网IP地址或者域名才能在公网使用!!!\nIM服务将使用本地地址,只能在局域网内使用!";
System.out.println(warning);
LOG.error(warning);
serverIp = Utility.getLocalAddress().getHostAddress(); serverIp = Utility.getLocalAddress().getHostAddress();
} }
return serverIp; return serverIp;
@@ -381,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
@@ -160,7 +160,7 @@ public class NettyAcceptor implements ServerAcceptor {
IConfig props) throws IOException { IConfig props) throws IOException {
LOG.info("Configuring TCP MQTT transport"); LOG.info("Configuring TCP MQTT transport");
final MoquetteIdleTimeoutHandler timeoutHandler = new MoquetteIdleTimeoutHandler(); final MoquetteIdleTimeoutHandler timeoutHandler = new MoquetteIdleTimeoutHandler();
String host = props.getProperty(BrokerConstants.HOST_PROPERTY_NAME); String host = props.getProperty(BrokerConstants.HOST_PROPERTY_NAME, "0.0.0.0");
String tcpPortProp = props.getProperty(PORT_PROPERTY_NAME, DISABLED_PORT_BIND); String tcpPortProp = props.getProperty(PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
if (DISABLED_PORT_BIND.equals(tcpPortProp)) { if (DISABLED_PORT_BIND.equals(tcpPortProp)) {
LOG.info("Property {} has been set to {}. TCP MQTT will be disabled", BrokerConstants.PORT_PROPERTY_NAME, LOG.info("Property {} has been set to {}. TCP MQTT will be disabled", BrokerConstants.PORT_PROPERTY_NAME,
@@ -179,7 +179,6 @@ public interface IMessagesStore {
ErrorCode saveAddFriendRequest(String userId, WFCMessage.AddFriendRequest request, long[] head, boolean isAdmin); ErrorCode saveAddFriendRequest(String userId, WFCMessage.AddFriendRequest request, long[] head, boolean isAdmin);
ErrorCode handleFriendRequest(String userId, WFCMessage.HandleFriendRequest request, WFCMessage.Message.Builder msgBuilder, long[] heads, boolean isAdmin); ErrorCode handleFriendRequest(String userId, WFCMessage.HandleFriendRequest request, WFCMessage.Message.Builder msgBuilder, long[] heads, boolean isAdmin);
boolean isNewFriendWelcomeMessage();
ErrorCode deleteFriend(String userId, String friendUid, long[] head); ErrorCode deleteFriend(String userId, String friendUid, long[] head);
ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head); ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head);
FriendData getFriendData(String fromUser, String targetUserId); FriendData getFriendData(String fromUser, String targetUserId);
@@ -193,6 +192,11 @@ public interface IMessagesStore {
boolean checkChatroomParticipantIdelTime(MemorySessionStore.Session session); boolean checkChatroomParticipantIdelTime(MemorySessionStore.Session session);
String getApplicationAuthCode(String fromUser, String applicationId, int appType, String host);
String verifyApplicationAuthCode(String token, String applicationId, int type);
ErrorCode configApplication(String appId, int appType, long timestamp, String nonce, String signature);
ErrorCode getUserSettings(String userId, long version, WFCMessage.GetUserSettingResult.Builder builder); ErrorCode getUserSettings(String userId, long version, WFCMessage.GetUserSettingResult.Builder builder);
WFCMessage.UserSettingEntry getUserSetting(String userId, int scope, String key); WFCMessage.UserSettingEntry getUserSetting(String userId, int scope, String key);
List<WFCMessage.UserSettingEntry> getUserSetting(String userId, int scope); List<WFCMessage.UserSettingEntry> getUserSetting(String userId, int scope);
@@ -209,7 +213,7 @@ public interface IMessagesStore {
void clearUserChannels(String userId); void clearUserChannels(String userId);
ErrorCode modifyChannelInfo(String operator, String channelId, int modifyType, String value); ErrorCode modifyChannelInfo(String operator, String channelId, int modifyType, String value);
ErrorCode transferChannel(String operator, String channelId, String newOwner); ErrorCode transferChannel(String operator, String channelId, String newOwner);
ErrorCode distoryChannel(String operator, String channelId, boolean isAdmin); ErrorCode destroyChannel(String operator, String channelId, boolean isAdmin);
List<WFCMessage.ChannelInfo> searchChannel(String keyword, boolean buzzy, int page); List<WFCMessage.ChannelInfo> searchChannel(String keyword, boolean buzzy, int page);
ErrorCode listenChannel(String operator, String channelId, boolean listen); ErrorCode listenChannel(String operator, String channelId, boolean listen);
WFCMessage.ChannelInfo getChannelInfo(String channelId); WFCMessage.ChannelInfo getChannelInfo(String channelId);
@@ -228,6 +232,10 @@ public interface IMessagesStore {
boolean isAllowRobotCustomGroupNotification(); boolean isAllowRobotCustomGroupNotification();
int getVisibleQuitKickoffNotification(); int getVisibleQuitKickoffNotification();
boolean isForwardMessageWithClientInfo();
boolean isRobotCallbackWithClientInfo();
boolean isChannelCallbackWithClientInfo();
List<Integer> getClientForbiddenSendTypes(); List<Integer> getClientForbiddenSendTypes();
List<Integer> getBlackListExceptionTypes(); List<Integer> getBlackListExceptionTypes();
List<Integer> getGroupMuteExceptionTypes(); List<Integer> getGroupMuteExceptionTypes();
@@ -16,6 +16,7 @@
package io.moquette.spi.impl; package io.moquette.spi.impl;
import cn.wildfirechat.pojos.OutputClient;
import cn.wildfirechat.pojos.OutputMessageData; import cn.wildfirechat.pojos.OutputMessageData;
import cn.wildfirechat.proto.ProtoConstants; import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage; import cn.wildfirechat.proto.WFCMessage;
@@ -24,7 +25,6 @@ import com.google.gson.Gson;
import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.util.StringUtil; import com.hazelcast.util.StringUtil;
import cn.wildfirechat.pojos.OutputNotifyChannelSubscribeStatus; import cn.wildfirechat.pojos.OutputNotifyChannelSubscribeStatus;
import cn.wildfirechat.pojos.SendMessageData;
import com.xiaoleilu.loServer.model.FriendData; import com.xiaoleilu.loServer.model.FriendData;
import io.moquette.persistence.*; import io.moquette.persistence.*;
import io.moquette.persistence.MemorySessionStore.Session; import io.moquette.persistence.MemorySessionStore.Session;
@@ -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;
@@ -176,10 +177,19 @@ public class MessagesPublisher {
if (message == null) { if (message == null) {
message = m_messagesStore.getMessage(messageId); message = m_messagesStore.getMessage(messageId);
} }
OutputClient outputClient = null;
if(m_messagesStore.isRobotCallbackWithClientInfo() && !StringUtil.isNullOrEmpty(exceptClientId)) {
Session session = m_sessionsStore.getSession(exceptClientId);
if(session != null && session.getUsername().equals(sender)) {
outputClient = new OutputClient(session.getPlatform(), exceptClientId);
}
}
final WFCMessage.Message finalMsg = message; final WFCMessage.Message finalMsg = message;
OutputClient finalOutputClient = outputClient;
Server.getServer().getCallbackScheduler().execute(() -> { Server.getServer().getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(robot.getCallback(), new Gson().toJson(SendMessageData.fromProtoMessage(finalMsg), SendMessageData.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);
@@ -595,10 +605,19 @@ public class MessagesPublisher {
public void publish2Receivers(WFCMessage.Message message, Set<String> receivers, String exceptClientId, int pullType) { public void publish2Receivers(WFCMessage.Message message, Set<String> receivers, String exceptClientId, int pullType) {
if (message.getConversation().getType() == ProtoConstants.ConversationType.ConversationType_Channel) { if (message.getConversation().getType() == ProtoConstants.ConversationType.ConversationType_Channel) {
WFCMessage.ChannelInfo channelInfo = m_messagesStore.getChannelInfo(message.getConversation().getTarget()); WFCMessage.ChannelInfo channelInfo = m_messagesStore.getChannelInfo(message.getConversation().getTarget());
if (channelInfo != null && !StringUtil.isNullOrEmpty(channelInfo.getCallback())) { if (channelInfo != null && !StringUtil.isNullOrEmpty(channelInfo.getCallback()) && channelInfo.getAutomatic() == 1 && !message.getFromUser().equals(channelInfo.getOwner())) {
OutputClient outputClient = null;
if(m_messagesStore.isChannelCallbackWithClientInfo() && !StringUtil.isNullOrEmpty(exceptClientId)) {
Session session = m_sessionsStore.getSession(exceptClientId);
if(session != null && session.getUsername().equals(message.getFromUser())) {
outputClient = new OutputClient(session.getPlatform(), exceptClientId);
}
}
OutputClient finalOutputClient = outputClient;
Server.getServer().getCallbackScheduler().execute(() -> { Server.getServer().getCallbackScheduler().execute(() -> {
try { try {
HttpUtils.httpJsonPost(channelInfo.getCallback() + "/message", new Gson().toJson(SendMessageData.fromProtoMessage(message), SendMessageData.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);
@@ -627,12 +646,14 @@ public class MessagesPublisher {
pushContent = "[语音]"; pushContent = "[语音]";
} else if(type == ProtoConstants.ContentType.Video) { } else if(type == ProtoConstants.ContentType.Video) {
pushContent = "[视频]"; pushContent = "[视频]";
} else if(type == ProtoConstants.ContentType.RichMedia) { } else if(type == ProtoConstants.ContentType.Link) {
pushContent = "[图文]"; pushContent = "[链接]";
} else if(type == ProtoConstants.ContentType.File) { } else if(type == ProtoConstants.ContentType.File) {
pushContent = "[文件]"; pushContent = "[文件]";
} else if(type == ProtoConstants.ContentType.Sticker) { } else if(type == ProtoConstants.ContentType.Sticker) {
pushContent = "[表情]"; pushContent = "[表情]";
} else if (type == ProtoConstants.ContentType.Name_Card) {
pushContent = "[名片]";
} }
} }
@@ -648,10 +669,10 @@ public class MessagesPublisher {
} }
public void forwardMessage(final WFCMessage.Message message, String forwardUrl) { 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), 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);
@@ -661,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(()->{
@@ -683,13 +704,13 @@ public class MessagesPublisher {
} }
public void notifyChannelListenStatusChanged(WFCMessage.ChannelInfo channelInfo, String user, boolean listen) { public void notifyChannelListenStatusChanged(WFCMessage.ChannelInfo channelInfo, String user, boolean listen) {
if (channelInfo == null || StringUtil.isNullOrEmpty(channelInfo.getCallback())) { if (channelInfo == null || StringUtil.isNullOrEmpty(channelInfo.getCallback()) || channelInfo.getAutomatic() == 0) {
return; return;
} }
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,6 +43,7 @@ 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;
@@ -264,7 +265,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 +308,7 @@ public class ProtocolProcessor {
} }
byte[] pwd = null; byte[] pwd = null;
if (msg.variableHeader().hasPassword()) { if (msg.variableHeader().hasPassword()) {
pwd = msg.payload().password(); pwd = msg.payload().passwordInBytes();
MemorySessionStore.Session session = m_sessionsStore.getSession(clientId); MemorySessionStore.Session session = m_sessionsStore.getSession(clientId);
if (session == null) { if (session == null) {
@@ -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 {
+65 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,6 +16,9 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import java.util.Arrays;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
/** /**
@@ -24,18 +27,55 @@ import io.netty.util.internal.StringUtil;
public final class MqttConnectPayload { public final class MqttConnectPayload {
private final String clientIdentifier; private final String clientIdentifier;
private final MqttProperties willProperties;
private final String willTopic; private final String willTopic;
private final String willMessage; private final byte[] willMessage;
private final String userName; private final String userName;
private final byte[] password; private final byte[] password;
/**
* @deprecated use {@link MqttConnectPayload#MqttConnectPayload(String,
* MqttProperties, String, byte[], String, byte[])} instead
*/
@Deprecated
public MqttConnectPayload( public MqttConnectPayload(
String clientIdentifier, String clientIdentifier,
String willTopic, String willTopic,
String willMessage, String willMessage,
String userName, String userName,
String password) {
this(
clientIdentifier,
MqttProperties.NO_PROPERTIES,
willTopic,
willMessage.getBytes(CharsetUtil.UTF_8),
userName,
password.getBytes(CharsetUtil.UTF_8));
}
public MqttConnectPayload(
String clientIdentifier,
String willTopic,
byte[] willMessage,
String userName,
byte[] password) {
this(clientIdentifier,
MqttProperties.NO_PROPERTIES,
willTopic,
willMessage,
userName,
password);
}
public MqttConnectPayload(
String clientIdentifier,
MqttProperties willProperties,
String willTopic,
byte[] willMessage,
String userName,
byte[] password) { byte[] password) {
this.clientIdentifier = clientIdentifier; this.clientIdentifier = clientIdentifier;
this.willProperties = MqttProperties.withEmptyDefaults(willProperties);
this.willTopic = willTopic; this.willTopic = willTopic;
this.willMessage = willMessage; this.willMessage = willMessage;
this.userName = userName; this.userName = userName;
@@ -46,11 +86,23 @@ public final class MqttConnectPayload {
return clientIdentifier; return clientIdentifier;
} }
public MqttProperties willProperties() {
return willProperties;
}
public String willTopic() { public String willTopic() {
return willTopic; return willTopic;
} }
/**
* @deprecated use {@link MqttConnectPayload#willMessageInBytes()} instead
*/
@Deprecated
public String willMessage() { public String willMessage() {
return willMessage == null ? null : new String(willMessage, CharsetUtil.UTF_8);
}
public byte[] willMessageInBytes() {
return willMessage; return willMessage;
} }
@@ -58,7 +110,15 @@ public final class MqttConnectPayload {
return userName; return userName;
} }
public byte[] password() { /**
* @deprecated use {@link MqttConnectPayload#passwordInBytes()} instead
*/
@Deprecated
public String password() {
return password == null ? null : new String(password, CharsetUtil.UTF_8);
}
public byte[] passwordInBytes() {
return password; return password;
} }
@@ -68,9 +128,9 @@ public final class MqttConnectPayload {
.append('[') .append('[')
.append("clientIdentifier=").append(clientIdentifier) .append("clientIdentifier=").append(clientIdentifier)
.append(", willTopic=").append(willTopic) .append(", willTopic=").append(willTopic)
.append(", willMessage=").append(willMessage) .append(", willMessage=").append(Arrays.toString(willMessage))
.append(", userName=").append(userName) .append(", userName=").append(userName)
.append(", password=").append(password) .append(", password=").append(Arrays.toString(password))
.append(']') .append(']')
.toString(); .toString();
} }
+42 -14
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,31 +16,52 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/** /**
* Return Code of {@link MqttConnAckMessage} * Return Code of {@link MqttConnAckMessage}
*/ */
public enum MqttConnectReturnCode { public enum MqttConnectReturnCode {
CONNECTION_ACCEPTED((byte) 0x00), CONNECTION_ACCEPTED((byte) 0x00),
//MQTT 3 codes
CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION((byte) 0X01), CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION((byte) 0X01),
CONNECTION_REFUSED_IDENTIFIER_REJECTED((byte) 0x02), CONNECTION_REFUSED_IDENTIFIER_REJECTED((byte) 0x02),
CONNECTION_REFUSED_SERVER_UNAVAILABLE((byte) 0x03), CONNECTION_REFUSED_SERVER_UNAVAILABLE((byte) 0x03),
CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD((byte) 0x04), CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD((byte) 0x04),
CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05), CONNECTION_REFUSED_NOT_AUTHORIZED((byte) 0x05),
CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06), CONNECTION_REFUSED_UNEXPECT_NODE((byte) 0X06),
CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07); CONNECTION_REFUSED_SESSION_NOT_EXIST((byte) 0x07),
//MQTT 5 codes
CONNECTION_REFUSED_UNSPECIFIED_ERROR((byte) 0x80),
CONNECTION_REFUSED_MALFORMED_PACKET((byte) 0x81),
CONNECTION_REFUSED_PROTOCOL_ERROR((byte) 0x82),
CONNECTION_REFUSED_IMPLEMENTATION_SPECIFIC((byte) 0x83),
CONNECTION_REFUSED_UNSUPPORTED_PROTOCOL_VERSION((byte) 0x84),
CONNECTION_REFUSED_CLIENT_IDENTIFIER_NOT_VALID((byte) 0x85),
CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD((byte) 0x86),
CONNECTION_REFUSED_NOT_AUTHORIZED_5((byte) 0x87),
CONNECTION_REFUSED_SERVER_UNAVAILABLE_5((byte) 0x88),
CONNECTION_REFUSED_SERVER_BUSY((byte) 0x89),
CONNECTION_REFUSED_BANNED((byte) 0x8A),
CONNECTION_REFUSED_BAD_AUTHENTICATION_METHOD((byte) 0x8C),
CONNECTION_REFUSED_TOPIC_NAME_INVALID((byte) 0x90),
CONNECTION_REFUSED_PACKET_TOO_LARGE((byte) 0x95),
CONNECTION_REFUSED_QUOTA_EXCEEDED((byte) 0x97),
CONNECTION_REFUSED_PAYLOAD_FORMAT_INVALID((byte) 0x99),
CONNECTION_REFUSED_RETAIN_NOT_SUPPORTED((byte) 0x9A),
CONNECTION_REFUSED_QOS_NOT_SUPPORTED((byte) 0x9B),
CONNECTION_REFUSED_USE_ANOTHER_SERVER((byte) 0x9C),
CONNECTION_REFUSED_SERVER_MOVED((byte) 0x9D),
CONNECTION_REFUSED_CONNECTION_RATE_EXCEEDED((byte) 0x9F);
private static final Map<Byte, MqttConnectReturnCode> VALUE_TO_CODE_MAP; private static final MqttConnectReturnCode[] VALUES;
static { static {
final Map<Byte, MqttConnectReturnCode> valueMap = new HashMap<Byte, MqttConnectReturnCode>(); MqttConnectReturnCode[] values = values();
for (MqttConnectReturnCode code: values()) { VALUES = new MqttConnectReturnCode[160];
valueMap.put(code.byteValue, code); for (MqttConnectReturnCode code : values) {
final int unsignedByte = code.byteValue & 0xFF;
// Suppress a warning about out of bounds access since the enum contains only correct values
VALUES[unsignedByte] = code; // lgtm [java/index-out-of-bounds]
} }
VALUE_TO_CODE_MAP = Collections.unmodifiableMap(valueMap);
} }
private final byte byteValue; private final byte byteValue;
@@ -54,9 +75,16 @@ public enum MqttConnectReturnCode {
} }
public static MqttConnectReturnCode valueOf(byte b) { public static MqttConnectReturnCode valueOf(byte b) {
if (VALUE_TO_CODE_MAP.containsKey(b)) { final int unsignedByte = b & 0xFF;
return VALUE_TO_CODE_MAP.get(b); MqttConnectReturnCode mqttConnectReturnCode = null;
try {
mqttConnectReturnCode = VALUES[unsignedByte];
} catch (ArrayIndexOutOfBoundsException ignored) {
// no op
} }
throw new IllegalArgumentException("unknown connect return code: " + (b & 0xFF)); if (mqttConnectReturnCode == null) {
throw new IllegalArgumentException("unknown connect return code: " + unsignedByte);
}
return mqttConnectReturnCode;
} }
} }
+30 -1
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -32,6 +32,7 @@ public final class MqttConnectVariableHeader {
private final boolean isWillFlag; private final boolean isWillFlag;
private final boolean isCleanSession; private final boolean isCleanSession;
private final int keepAliveTimeSeconds; private final int keepAliveTimeSeconds;
private final MqttProperties properties;
public MqttConnectVariableHeader( public MqttConnectVariableHeader(
String name, String name,
@@ -43,6 +44,29 @@ public final class MqttConnectVariableHeader {
boolean isWillFlag, boolean isWillFlag,
boolean isCleanSession, boolean isCleanSession,
int keepAliveTimeSeconds) { int keepAliveTimeSeconds) {
this(name,
version,
hasUserName,
hasPassword,
isWillRetain,
willQos,
isWillFlag,
isCleanSession,
keepAliveTimeSeconds,
MqttProperties.NO_PROPERTIES);
}
public MqttConnectVariableHeader(
String name,
int version,
boolean hasUserName,
boolean hasPassword,
boolean isWillRetain,
int willQos,
boolean isWillFlag,
boolean isCleanSession,
int keepAliveTimeSeconds,
MqttProperties properties) {
this.name = name; this.name = name;
this.version = version; this.version = version;
this.hasUserName = hasUserName; this.hasUserName = hasUserName;
@@ -52,6 +76,7 @@ public final class MqttConnectVariableHeader {
this.isWillFlag = isWillFlag; this.isWillFlag = isWillFlag;
this.isCleanSession = isCleanSession; this.isCleanSession = isCleanSession;
this.keepAliveTimeSeconds = keepAliveTimeSeconds; this.keepAliveTimeSeconds = keepAliveTimeSeconds;
this.properties = MqttProperties.withEmptyDefaults(properties);
} }
public String name() { public String name() {
@@ -90,6 +115,10 @@ public final class MqttConnectVariableHeader {
return keepAliveTimeSeconds; return keepAliveTimeSeconds;
} }
public MqttProperties properties() {
return properties;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
@@ -0,0 +1,39 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
public final class MqttConstant {
private MqttConstant() {
}
/**
* Default max bytes in message
*/
public static final int DEFAULT_MAX_BYTES_IN_MESSAGE = 262144; //256KB
/**
* min client id length
*/
public static final int MIN_CLIENT_ID_LENGTH = 1;
/**
* Default max client id length,In the mqtt3.1 protocol,
* the default maximum Client Identifier length is 23
*/
public static final int DEFAULT_MAX_CLIENT_ID_LENGTH = 23;
}
+436 -112
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -20,26 +20,22 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.ReplayingDecoder; import io.netty.handler.codec.ReplayingDecoder;
import io.netty.handler.codec.mqtt.MqttDecoder.DecoderState; import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import io.netty.util.internal.ObjectUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidClientId;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidMessageId;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.isValidPublishTopicName;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.resetUnusedFields;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.validateFixedHeader;
/** /**
* Decodes Mqtt messages from bytes, following * Decodes Mqtt messages from bytes, following
* <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html"> * the MQTT protocol specification
* the MQTT protocol specification v3.1</a> * <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">v3.1</a>
* or
* <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html">v5.0</a>, depending on the
* version specified in the CONNECT message that first goes through the channel.
*/ */
public final class MqttDecoder extends ReplayingDecoder<DecoderState> { public final class MqttDecoder extends ReplayingDecoder<MqttDecoder.DecoderState> {
private static final int DEFAULT_MAX_BYTES_IN_MESSAGE = 262144; //256K
/** /**
* States of the decoder. * States of the decoder.
@@ -58,31 +54,42 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private int bytesRemainingInVariablePart; private int bytesRemainingInVariablePart;
private final int maxBytesInMessage; private final int maxBytesInMessage;
private final int maxClientIdLength;
public MqttDecoder() { public MqttDecoder() {
this(DEFAULT_MAX_BYTES_IN_MESSAGE); this(MqttConstant.DEFAULT_MAX_BYTES_IN_MESSAGE, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH);
} }
public MqttDecoder(int maxBytesInMessage) { public MqttDecoder(int maxBytesInMessage) {
this(maxBytesInMessage, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH);
}
public MqttDecoder(int maxBytesInMessage, int maxClientIdLength) {
super(DecoderState.READ_FIXED_HEADER); super(DecoderState.READ_FIXED_HEADER);
this.maxBytesInMessage = maxBytesInMessage; this.maxBytesInMessage = ObjectUtil.checkPositive(maxBytesInMessage, "maxBytesInMessage");
this.maxClientIdLength = ObjectUtil.checkPositive(maxClientIdLength, "maxClientIdLength");
} }
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
switch (state()) { switch (state()) {
case READ_FIXED_HEADER: case READ_FIXED_HEADER: try {
mqttFixedHeader = decodeFixedHeader(buffer); mqttFixedHeader = decodeFixedHeader(ctx, buffer);
bytesRemainingInVariablePart = mqttFixedHeader.remainingLength(); bytesRemainingInVariablePart = mqttFixedHeader.remainingLength();
checkpoint(DecoderState.READ_VARIABLE_HEADER); checkpoint(DecoderState.READ_VARIABLE_HEADER);
// fall through // fall through
} catch (Exception cause) {
out.add(invalidMessage(cause));
return;
}
case READ_VARIABLE_HEADER: try { case READ_VARIABLE_HEADER: try {
if (bytesRemainingInVariablePart > maxBytesInMessage) { final Result<?> decodedVariableHeader = decodeVariableHeader(ctx, buffer, mqttFixedHeader);
throw new DecoderException("too large message: " + bytesRemainingInVariablePart + " bytes");
}
final Result<?> decodedVariableHeader = decodeVariableHeader(buffer, mqttFixedHeader);
variableHeader = decodedVariableHeader.value; variableHeader = decodedVariableHeader.value;
if (bytesRemainingInVariablePart > maxBytesInMessage) {
buffer.skipBytes(actualReadableBytes());
throw new TooLongFrameException("too large message: " + bytesRemainingInVariablePart + " bytes");
}
bytesRemainingInVariablePart -= decodedVariableHeader.numberOfBytesConsumed; bytesRemainingInVariablePart -= decodedVariableHeader.numberOfBytesConsumed;
checkpoint(DecoderState.READ_PAYLOAD); checkpoint(DecoderState.READ_PAYLOAD);
// fall through // fall through
@@ -94,9 +101,11 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
case READ_PAYLOAD: try { case READ_PAYLOAD: try {
final Result<?> decodedPayload = final Result<?> decodedPayload =
decodePayload( decodePayload(
ctx,
buffer, buffer,
mqttFixedHeader.messageType(), mqttFixedHeader.messageType(),
bytesRemainingInVariablePart, bytesRemainingInVariablePart,
maxClientIdLength,
variableHeader); variableHeader);
bytesRemainingInVariablePart -= decodedPayload.numberOfBytesConsumed; bytesRemainingInVariablePart -= decodedPayload.numberOfBytesConsumed;
if (bytesRemainingInVariablePart != 0) { if (bytesRemainingInVariablePart != 0) {
@@ -129,16 +138,20 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private MqttMessage invalidMessage(Throwable cause) { private MqttMessage invalidMessage(Throwable cause) {
checkpoint(DecoderState.BAD_MESSAGE); checkpoint(DecoderState.BAD_MESSAGE);
return MqttMessageFactory.newInvalidMessage(cause); return MqttMessageFactory.newInvalidMessage(mqttFixedHeader, variableHeader, cause);
} }
/** /**
* Decodes the fixed header. It's one byte for the flags and then variable bytes for the remaining length. * Decodes the fixed header. It's one byte for the flags and then variable
* bytes for the remaining length.
*
* @see
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180841
* *
* @param buffer the buffer to decode from * @param buffer the buffer to decode from
* @return the fixed header * @return the fixed header
*/ */
private static MqttFixedHeader decodeFixedHeader(ByteBuf buffer) { private static MqttFixedHeader decodeFixedHeader(ChannelHandlerContext ctx, ByteBuf buffer) {
short b1 = buffer.readUnsignedByte(); short b1 = buffer.readUnsignedByte();
MqttMessageType messageType = MqttMessageType.valueOf(b1 >> 4); MqttMessageType messageType = MqttMessageType.valueOf(b1 >> 4);
@@ -146,6 +159,59 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
int qosLevel = (b1 & 0x06) >> 1; int qosLevel = (b1 & 0x06) >> 1;
boolean retain = (b1 & 0x01) != 0; boolean retain = (b1 & 0x01) != 0;
switch (messageType) {
case PUBLISH:
if (qosLevel == 3) {
throw new DecoderException("Illegal QOS Level in fixed header of PUBLISH message ("
+ qosLevel + ')');
}
break;
case PUBREL:
case SUBSCRIBE:
case UNSUBSCRIBE:
if (dupFlag) {
throw new DecoderException("Illegal BIT 3 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
if (qosLevel != 1) {
throw new DecoderException("Illegal QOS Level in fixed header of " + messageType
+ " message, must be 1, found " + qosLevel);
}
if (retain) {
throw new DecoderException("Illegal BIT 0 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
break;
case AUTH:
case CONNACK:
case CONNECT:
case DISCONNECT:
case PINGREQ:
case PINGRESP:
case PUBACK:
case PUBCOMP:
case PUBREC:
case SUBACK:
case UNSUBACK:
if (dupFlag) {
throw new DecoderException("Illegal BIT 3 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
if (qosLevel != 0) {
throw new DecoderException("Illegal BIT 2 or 1 in fixed header of " + messageType
+ " message, must be 0, found " + qosLevel);
}
if (retain) {
throw new DecoderException("Illegal BIT 0 in fixed header of " + messageType
+ " message, must be 0, found 1");
}
break;
default:
throw new DecoderException("Unknown message type, do not know how to validate fixed header");
}
int remainingLength = 0; int remainingLength = 0;
int multiplier = 1; int multiplier = 1;
short digit; short digit;
@@ -163,7 +229,7 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
} }
MqttFixedHeader decodedFixedHeader = MqttFixedHeader decodedFixedHeader =
new MqttFixedHeader(messageType, dupFlag, MqttQoS.valueOf(qosLevel), retain, remainingLength); new MqttFixedHeader(messageType, dupFlag, MqttQoS.valueOf(qosLevel), retain, remainingLength);
return validateFixedHeader(resetUnusedFields(decodedFixedHeader)); return MqttCodecUtil.validateFixedHeader(ctx, MqttCodecUtil.resetUnusedFields(decodedFixedHeader));
} }
/** /**
@@ -172,50 +238,60 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
* @param mqttFixedHeader MqttFixedHeader of the same message * @param mqttFixedHeader MqttFixedHeader of the same message
* @return the variable header * @return the variable header
*/ */
private static Result<?> decodeVariableHeader(ByteBuf buffer, MqttFixedHeader mqttFixedHeader) { private Result<?> decodeVariableHeader(ChannelHandlerContext ctx, ByteBuf buffer, MqttFixedHeader mqttFixedHeader) {
switch (mqttFixedHeader.messageType()) { switch (mqttFixedHeader.messageType()) {
case CONNECT: case CONNECT:
return decodeConnectionVariableHeader(buffer); return decodeConnectionVariableHeader(ctx, buffer);
case CONNACK: case CONNACK:
return decodeConnAckVariableHeader(buffer); return decodeConnAckVariableHeader(ctx, buffer);
case SUBSCRIBE:
case UNSUBSCRIBE: case UNSUBSCRIBE:
case SUBSCRIBE:
case SUBACK: case SUBACK:
case UNSUBACK: case UNSUBACK:
return decodeMessageIdAndPropertiesVariableHeader(ctx, buffer);
case PUBACK: case PUBACK:
case PUBREC: case PUBREC:
case PUBCOMP: case PUBCOMP:
case PUBREL: case PUBREL:
return decodeMessageIdVariableHeader(buffer); return decodePubReplyMessage(buffer);
case PUBLISH: case PUBLISH:
return decodePublishVariableHeader(buffer, mqttFixedHeader); return decodePublishVariableHeader(ctx, buffer, mqttFixedHeader);
case DISCONNECT:
case AUTH:
return decodeReasonCodeAndPropertiesVariableHeader(buffer);
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
case DISCONNECT:
// Empty variable header // Empty variable header
return new Result<Object>(null, 0); return new Result<Object>(null, 0);
default:
//shouldn't reach here
throw new DecoderException("Unknown message type: " + mqttFixedHeader.messageType());
} }
return new Result<Object>(null, 0); //should never reach here
} }
private static Result<MqttConnectVariableHeader> decodeConnectionVariableHeader(ByteBuf buffer) { private static Result<MqttConnectVariableHeader> decodeConnectionVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer) {
final Result<String> protoString = decodeString(buffer); final Result<String> protoString = decodeString(buffer);
int numberOfBytesConsumed = protoString.numberOfBytesConsumed; int numberOfBytesConsumed = protoString.numberOfBytesConsumed;
final byte protocolLevel = buffer.readByte(); final byte protocolLevel = buffer.readByte();
numberOfBytesConsumed += 1; numberOfBytesConsumed += 1;
final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(protoString.value, protocolLevel); MqttVersion version = MqttVersion.fromProtocolNameAndLevel(protoString.value, protocolLevel);
MqttCodecUtil.setMqttVersion(ctx, version);
final int b1 = buffer.readUnsignedByte(); final int b1 = buffer.readUnsignedByte();
numberOfBytesConsumed += 1; numberOfBytesConsumed += 1;
final Result<Integer> keepAlive = decodeMsbLsb(buffer); final int keepAlive = decodeMsbLsb(buffer);
numberOfBytesConsumed += keepAlive.numberOfBytesConsumed; numberOfBytesConsumed += 2;
final boolean hasUserName = (b1 & 0x80) == 0x80; final boolean hasUserName = (b1 & 0x80) == 0x80;
final boolean hasPassword = (b1 & 0x40) == 0x40; final boolean hasPassword = (b1 & 0x40) == 0x40;
@@ -223,69 +299,178 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
final int willQos = (b1 & 0x18) >> 3; final int willQos = (b1 & 0x18) >> 3;
final boolean willFlag = (b1 & 0x04) == 0x04; final boolean willFlag = (b1 & 0x04) == 0x04;
final boolean cleanSession = (b1 & 0x02) == 0x02; final boolean cleanSession = (b1 & 0x02) == 0x02;
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) { if (version == MqttVersion.MQTT_3_1_1 || version == MqttVersion.MQTT_5) {
final boolean zeroReservedFlag = (b1 & 0x01) == 0x0; final boolean zeroReservedFlag = (b1 & 0x01) == 0x0;
if (!zeroReservedFlag) { if (!zeroReservedFlag) {
// MQTT v3.1.1: The Server MUST validate that the reserved flag in the CONNECT Control Packet is // MQTT v3.1.1: The Server MUST validate that the reserved flag in the CONNECT Control Packet is
// set to zero and disconnect the Client if it is not zero. // set to zero and disconnect the Client if it is not zero.
// See http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230 // See https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349230
throw new DecoderException("non-zero reserved flag"); throw new DecoderException("non-zero reserved flag");
} }
} }
final MqttProperties properties;
if (version == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
properties = MqttProperties.NO_PROPERTIES;
}
final MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader( final MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader(
mqttVersion.protocolName(), version.protocolName(),
mqttVersion.protocolLevel(), version.protocolLevel(),
hasUserName, hasUserName,
hasPassword, hasPassword,
willRetain, willRetain,
willQos, willQos,
willFlag, willFlag,
cleanSession, cleanSession,
keepAlive.value); keepAlive,
properties);
return new Result<MqttConnectVariableHeader>(mqttConnectVariableHeader, numberOfBytesConsumed); return new Result<MqttConnectVariableHeader>(mqttConnectVariableHeader, numberOfBytesConsumed);
} }
private static Result<MqttConnAckVariableHeader> decodeConnAckVariableHeader(ByteBuf buffer) { private static Result<MqttConnAckVariableHeader> decodeConnAckVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer) {
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
final boolean sessionPresent = (buffer.readUnsignedByte() & 0x01) == 0x01; final boolean sessionPresent = (buffer.readUnsignedByte() & 0x01) == 0x01;
byte returnCode = buffer.readByte(); byte returnCode = buffer.readByte();
final int numberOfBytesConsumed = 2; int numberOfBytesConsumed = 2;
final MqttProperties properties;
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
properties = MqttProperties.NO_PROPERTIES;
}
final MqttConnAckVariableHeader mqttConnAckVariableHeader = final MqttConnAckVariableHeader mqttConnAckVariableHeader =
new MqttConnAckVariableHeader(MqttConnectReturnCode.valueOf(returnCode), sessionPresent); new MqttConnAckVariableHeader(MqttConnectReturnCode.valueOf(returnCode), sessionPresent, properties);
return new Result<MqttConnAckVariableHeader>(mqttConnAckVariableHeader, numberOfBytesConsumed); return new Result<MqttConnAckVariableHeader>(mqttConnAckVariableHeader, numberOfBytesConsumed);
} }
private static Result<MqttMessageIdVariableHeader> decodeMessageIdVariableHeader(ByteBuf buffer) { private static Result<MqttMessageIdAndPropertiesVariableHeader> decodeMessageIdAndPropertiesVariableHeader(
final Result<Integer> messageId = decodeMessageId(buffer); ChannelHandlerContext ctx,
return new Result<MqttMessageIdVariableHeader>( ByteBuf buffer) {
MqttMessageIdVariableHeader.from(messageId.value), final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
messageId.numberOfBytesConsumed); final int packetId = decodeMessageId(buffer);
final MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader;
final int mqtt5Consumed;
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> properties = decodeProperties(buffer);
mqttVariableHeader = new MqttMessageIdAndPropertiesVariableHeader(packetId, properties.value);
mqtt5Consumed = properties.numberOfBytesConsumed;
} else {
mqttVariableHeader = new MqttMessageIdAndPropertiesVariableHeader(packetId,
MqttProperties.NO_PROPERTIES);
mqtt5Consumed = 0;
}
return new Result<MqttMessageIdAndPropertiesVariableHeader>(mqttVariableHeader,
2 + mqtt5Consumed);
} }
private static Result<MqttPublishVariableHeader> decodePublishVariableHeader( private Result<MqttPubReplyMessageVariableHeader> decodePubReplyMessage(ByteBuf buffer) {
final int packetId = decodeMessageId(buffer);
final MqttPubReplyMessageVariableHeader mqttPubAckVariableHeader;
final int consumed;
final int packetIdNumberOfBytesConsumed = 2;
if (bytesRemainingInVariablePart > 3) {
final byte reasonCode = buffer.readByte();
final Result<MqttProperties> properties = decodeProperties(buffer);
mqttPubAckVariableHeader = new MqttPubReplyMessageVariableHeader(packetId,
reasonCode,
properties.value);
consumed = packetIdNumberOfBytesConsumed + 1 + properties.numberOfBytesConsumed;
} else if (bytesRemainingInVariablePart > 2) {
final byte reasonCode = buffer.readByte();
mqttPubAckVariableHeader = new MqttPubReplyMessageVariableHeader(packetId,
reasonCode,
MqttProperties.NO_PROPERTIES);
consumed = packetIdNumberOfBytesConsumed + 1;
} else {
mqttPubAckVariableHeader = new MqttPubReplyMessageVariableHeader(packetId,
(byte) 0,
MqttProperties.NO_PROPERTIES);
consumed = packetIdNumberOfBytesConsumed;
}
return new Result<MqttPubReplyMessageVariableHeader>(mqttPubAckVariableHeader, consumed);
}
private Result<MqttReasonCodeAndPropertiesVariableHeader> decodeReasonCodeAndPropertiesVariableHeader(
ByteBuf buffer) {
final byte reasonCode;
final MqttProperties properties;
final int consumed;
if (bytesRemainingInVariablePart > 1) {
reasonCode = buffer.readByte();
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
consumed = 1 + propertiesResult.numberOfBytesConsumed;
} else if (bytesRemainingInVariablePart > 0) {
reasonCode = buffer.readByte();
properties = MqttProperties.NO_PROPERTIES;
consumed = 1;
} else {
reasonCode = 0;
properties = MqttProperties.NO_PROPERTIES;
consumed = 0;
}
final MqttReasonCodeAndPropertiesVariableHeader mqttReasonAndPropsVariableHeader =
new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, properties);
return new Result<MqttReasonCodeAndPropertiesVariableHeader>(
mqttReasonAndPropsVariableHeader,
consumed);
}
private Result<MqttPublishVariableHeader> decodePublishVariableHeader(
ChannelHandlerContext ctx,
ByteBuf buffer, ByteBuf buffer,
MqttFixedHeader mqttFixedHeader) { MqttFixedHeader mqttFixedHeader) {
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
final Result<String> decodedTopic = decodeString(buffer); final Result<String> decodedTopic = decodeString(buffer);
if (!isValidPublishTopicName(decodedTopic.value)) { if (!MqttCodecUtil.isValidPublishTopicName(decodedTopic.value)) {
throw new DecoderException("invalid publish topic name: " + decodedTopic.value + " (contains wildcards)"); throw new DecoderException("invalid publish topic name: " + decodedTopic.value + " (contains wildcards)");
} }
int numberOfBytesConsumed = decodedTopic.numberOfBytesConsumed; int numberOfBytesConsumed = decodedTopic.numberOfBytesConsumed;
int messageId = -1; int messageId = -1;
if (mqttFixedHeader.qosLevel().value() > 0) { if (mqttFixedHeader.qosLevel().value() > 0) {
final Result<Integer> decodedMessageId = decodeMessageId(buffer); messageId = decodeMessageId(buffer);
messageId = decodedMessageId.value; numberOfBytesConsumed += 2;
numberOfBytesConsumed += decodedMessageId.numberOfBytesConsumed;
} }
final MqttProperties properties;
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
properties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
properties = MqttProperties.NO_PROPERTIES;
}
final MqttPublishVariableHeader mqttPublishVariableHeader = final MqttPublishVariableHeader mqttPublishVariableHeader =
new MqttPublishVariableHeader(decodedTopic.value, messageId); new MqttPublishVariableHeader(decodedTopic.value, messageId, properties);
return new Result<MqttPublishVariableHeader>(mqttPublishVariableHeader, numberOfBytesConsumed); return new Result<MqttPublishVariableHeader>(mqttPublishVariableHeader, numberOfBytesConsumed);
} }
private static Result<Integer> decodeMessageId(ByteBuf buffer) { /**
final Result<Integer> messageId = decodeMsbLsb(buffer); * @return messageId with numberOfBytesConsumed is 2
if (!isValidMessageId(messageId.value)) { */
throw new DecoderException("invalid messageId: " + messageId.value); private static int decodeMessageId(ByteBuf buffer) {
final int messageId = decodeMsbLsb(buffer);
if (!MqttCodecUtil.isValidMessageId(messageId)) {
throw new DecoderException("invalid messageId: " + messageId);
} }
return messageId; return messageId;
} }
@@ -300,13 +485,15 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
* @return the payload * @return the payload
*/ */
private static Result<?> decodePayload( private static Result<?> decodePayload(
ChannelHandlerContext ctx,
ByteBuf buffer, ByteBuf buffer,
MqttMessageType messageType, MqttMessageType messageType,
int bytesRemainingInVariablePart, int bytesRemainingInVariablePart,
int maxClientIdLength,
Object variableHeader) { Object variableHeader) {
switch (messageType) { switch (messageType) {
case CONNECT: case CONNECT:
return decodeConnectionPayload(buffer, (MqttConnectVariableHeader) variableHeader); return decodeConnectionPayload(buffer, maxClientIdLength, (MqttConnectVariableHeader) variableHeader);
case SUBSCRIBE: case SUBSCRIBE:
return decodeSubscribePayload(buffer, bytesRemainingInVariablePart); return decodeSubscribePayload(buffer, bytesRemainingInVariablePart);
@@ -317,6 +504,9 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
case UNSUBSCRIBE: case UNSUBSCRIBE:
return decodeUnsubscribePayload(buffer, bytesRemainingInVariablePart); return decodeUnsubscribePayload(buffer, bytesRemainingInVariablePart);
case UNSUBACK:
return decodeUnsubAckPayload(ctx, buffer, bytesRemainingInVariablePart);
case PUBLISH: case PUBLISH:
return decodePublishPayload(buffer, bytesRemainingInVariablePart); return decodePublishPayload(buffer, bytesRemainingInVariablePart);
@@ -328,42 +518,55 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private static Result<MqttConnectPayload> decodeConnectionPayload( private static Result<MqttConnectPayload> decodeConnectionPayload(
ByteBuf buffer, ByteBuf buffer,
int maxClientIdLength,
MqttConnectVariableHeader mqttConnectVariableHeader) { MqttConnectVariableHeader mqttConnectVariableHeader) {
final Result<String> decodedClientId = decodeString(buffer); final Result<String> decodedClientId = decodeString(buffer);
final String decodedClientIdValue = decodedClientId.value; final String decodedClientIdValue = decodedClientId.value;
final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(mqttConnectVariableHeader.name(), final MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(mqttConnectVariableHeader.name(),
(byte) mqttConnectVariableHeader.version()); (byte) mqttConnectVariableHeader.version());
if (!isValidClientId(mqttVersion, decodedClientIdValue)) { if (!MqttCodecUtil.isValidClientId(mqttVersion, maxClientIdLength, decodedClientIdValue)) {
throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + decodedClientIdValue); throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + decodedClientIdValue);
} }
int numberOfBytesConsumed = decodedClientId.numberOfBytesConsumed; int numberOfBytesConsumed = decodedClientId.numberOfBytesConsumed;
Result<String> decodedWillTopic = null; Result<String> decodedWillTopic = null;
Result<String> decodedWillMessage = null; byte[] decodedWillMessage = null;
final MqttProperties willProperties;
if (mqttConnectVariableHeader.isWillFlag()) { if (mqttConnectVariableHeader.isWillFlag()) {
if (mqttVersion == MqttVersion.MQTT_5) {
final Result<MqttProperties> propertiesResult = decodeProperties(buffer);
willProperties = propertiesResult.value;
numberOfBytesConsumed += propertiesResult.numberOfBytesConsumed;
} else {
willProperties = MqttProperties.NO_PROPERTIES;
}
decodedWillTopic = decodeString(buffer, 0, 32767); decodedWillTopic = decodeString(buffer, 0, 32767);
numberOfBytesConsumed += decodedWillTopic.numberOfBytesConsumed; numberOfBytesConsumed += decodedWillTopic.numberOfBytesConsumed;
decodedWillMessage = decodeAsciiString(buffer); decodedWillMessage = decodeByteArray(buffer);
numberOfBytesConsumed += decodedWillMessage.numberOfBytesConsumed; numberOfBytesConsumed += decodedWillMessage.length + 2;
} else {
willProperties = MqttProperties.NO_PROPERTIES;
} }
Result<String> decodedUserName = null; Result<String> decodedUserName = null;
Result<byte[]> decodedPassword = null; byte[] decodedPassword = null;
if (mqttConnectVariableHeader.hasUserName()) { if (mqttConnectVariableHeader.hasUserName()) {
decodedUserName = decodeString(buffer); decodedUserName = decodeString(buffer);
numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed; numberOfBytesConsumed += decodedUserName.numberOfBytesConsumed;
} }
if (mqttConnectVariableHeader.hasPassword()) { if (mqttConnectVariableHeader.hasPassword()) {
decodedPassword = decodeByte(buffer); decodedPassword = decodeByteArray(buffer);
numberOfBytesConsumed += decodedPassword.numberOfBytesConsumed; numberOfBytesConsumed += decodedPassword.length + 2;
} }
final MqttConnectPayload mqttConnectPayload = final MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload( new MqttConnectPayload(
decodedClientId.value, decodedClientId.value,
willProperties,
decodedWillTopic != null ? decodedWillTopic.value : null, decodedWillTopic != null ? decodedWillTopic.value : null,
decodedWillMessage != null ? decodedWillMessage.value : null, decodedWillMessage,
decodedUserName != null ? decodedUserName.value : null, decodedUserName != null ? decodedUserName.value : null,
decodedPassword != null ? decodedPassword.value : null); decodedPassword);
return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed); return new Result<MqttConnectPayload>(mqttConnectPayload, numberOfBytesConsumed);
} }
@@ -375,9 +578,21 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
while (numberOfBytesConsumed < bytesRemainingInVariablePart) { while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
final Result<String> decodedTopicName = decodeString(buffer); final Result<String> decodedTopicName = decodeString(buffer);
numberOfBytesConsumed += decodedTopicName.numberOfBytesConsumed; numberOfBytesConsumed += decodedTopicName.numberOfBytesConsumed;
int qos = buffer.readUnsignedByte() & 0x03; //See 3.8.3.1 Subscription Options of MQTT 5.0 specification for optionByte details
final short optionByte = buffer.readUnsignedByte();
MqttQoS qos = MqttQoS.valueOf(optionByte & 0x03);
boolean noLocal = ((optionByte & 0x04) >> 2) == 1;
boolean retainAsPublished = ((optionByte & 0x08) >> 3) == 1;
MqttSubscriptionOption.RetainedHandlingPolicy retainHandling = MqttSubscriptionOption.RetainedHandlingPolicy.valueOf((optionByte & 0x30) >> 4);
final MqttSubscriptionOption subscriptionOption = new MqttSubscriptionOption(qos,
noLocal,
retainAsPublished,
retainHandling);
numberOfBytesConsumed++; numberOfBytesConsumed++;
subscribeTopics.add(new MqttTopicSubscription(decodedTopicName.value, MqttQoS.valueOf(qos))); subscribeTopics.add(new MqttTopicSubscription(decodedTopicName.value, subscriptionOption));
} }
return new Result<MqttSubscribePayload>(new MqttSubscribePayload(subscribeTopics), numberOfBytesConsumed); return new Result<MqttSubscribePayload>(new MqttSubscribePayload(subscribeTopics), numberOfBytesConsumed);
} }
@@ -385,16 +600,30 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
private static Result<MqttSubAckPayload> decodeSubackPayload( private static Result<MqttSubAckPayload> decodeSubackPayload(
ByteBuf buffer, ByteBuf buffer,
int bytesRemainingInVariablePart) { int bytesRemainingInVariablePart) {
final List<Integer> grantedQos = new ArrayList<Integer>(); final List<Integer> grantedQos = new ArrayList<Integer>(bytesRemainingInVariablePart);
int numberOfBytesConsumed = 0; int numberOfBytesConsumed = 0;
while (numberOfBytesConsumed < bytesRemainingInVariablePart) { while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
int qos = buffer.readUnsignedByte() & 0x03; int reasonCode = buffer.readUnsignedByte();
numberOfBytesConsumed++; numberOfBytesConsumed++;
grantedQos.add(qos); grantedQos.add(reasonCode);
} }
return new Result<MqttSubAckPayload>(new MqttSubAckPayload(grantedQos), numberOfBytesConsumed); return new Result<MqttSubAckPayload>(new MqttSubAckPayload(grantedQos), numberOfBytesConsumed);
} }
private static Result<MqttUnsubAckPayload> decodeUnsubAckPayload(
ChannelHandlerContext ctx,
ByteBuf buffer,
int bytesRemainingInVariablePart) {
final List<Short> reasonCodes = new ArrayList<Short>(bytesRemainingInVariablePart);
int numberOfBytesConsumed = 0;
while (numberOfBytesConsumed < bytesRemainingInVariablePart) {
short reasonCode = buffer.readUnsignedByte();
numberOfBytesConsumed++;
reasonCodes.add(reasonCode);
}
return new Result<MqttUnsubAckPayload>(new MqttUnsubAckPayload(reasonCodes), numberOfBytesConsumed);
}
private static Result<MqttUnsubscribePayload> decodeUnsubscribePayload( private static Result<MqttUnsubscribePayload> decodeUnsubscribePayload(
ByteBuf buffer, ByteBuf buffer,
int bytesRemainingInVariablePart) { int bytesRemainingInVariablePart) {
@@ -419,25 +648,9 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
return decodeString(buffer, 0, Integer.MAX_VALUE); return decodeString(buffer, 0, Integer.MAX_VALUE);
} }
private static Result<String> decodeAsciiString(ByteBuf buffer) {
Result<String> result = decodeString(buffer, 0, Integer.MAX_VALUE);
final String s = result.value;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) > 127) {
return new Result<String>(null, result.numberOfBytesConsumed);
}
}
return new Result<String>(s, result.numberOfBytesConsumed);
}
private static Result<byte[]> decodeByte(ByteBuf buffer) {
return decodeByte(buffer, 0, Integer.MAX_VALUE);
}
private static Result<String> decodeString(ByteBuf buffer, int minBytes, int maxBytes) { private static Result<String> decodeString(ByteBuf buffer, int minBytes, int maxBytes) {
final Result<Integer> decodedSize = decodeMsbLsb(buffer); int size = decodeMsbLsb(buffer);
int size = decodedSize.value; int numberOfBytesConsumed = 2;
int numberOfBytesConsumed = decodedSize.numberOfBytesConsumed;
if (size < minBytes || size > maxBytes) { if (size < minBytes || size > maxBytes) {
buffer.skipBytes(size); buffer.skipBytes(size);
numberOfBytesConsumed += size; numberOfBytesConsumed += size;
@@ -449,35 +662,68 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
return new Result<String>(s, numberOfBytesConsumed); return new Result<String>(s, numberOfBytesConsumed);
} }
private static Result<byte[]> decodeByte(ByteBuf buffer, int minBytes, int maxBytes) { /**
final Result<Integer> decodedSize = decodeMsbLsb(buffer); *
int size = decodedSize.value; * @return the decoded byte[], numberOfBytesConsumed = byte[].length + 2
int numberOfBytesConsumed = decodedSize.numberOfBytesConsumed; */
if (size < minBytes || size > maxBytes) { private static byte[] decodeByteArray(ByteBuf buffer) {
buffer.skipBytes(size); int size = decodeMsbLsb(buffer);
numberOfBytesConsumed += size; byte[] bytes = new byte[size];
return new Result<>(null, numberOfBytesConsumed); buffer.readBytes(bytes);
} return bytes;
byte[] s = new byte[size];
buffer.getBytes(buffer.readerIndex(), s, 0, size);
buffer.skipBytes(size);
numberOfBytesConsumed += size;
return new Result<>(s, numberOfBytesConsumed);
} }
private static Result<Integer> decodeMsbLsb(ByteBuf buffer) { // packing utils to reduce the amount of garbage while decoding ints
return decodeMsbLsb(buffer, 0, 65535); private static long packInts(int a, int b) {
return (((long) a) << 32) | (b & 0xFFFFFFFFL);
} }
private static Result<Integer> decodeMsbLsb(ByteBuf buffer, int min, int max) { private static int unpackA(long ints) {
return (int) (ints >> 32);
}
private static int unpackB(long ints) {
return (int) ints;
}
/**
* numberOfBytesConsumed = 2. return decoded result.
*/
private static int decodeMsbLsb(ByteBuf buffer) {
int min = 0;
int max = 65535;
short msbSize = buffer.readUnsignedByte(); short msbSize = buffer.readUnsignedByte();
short lsbSize = buffer.readUnsignedByte(); short lsbSize = buffer.readUnsignedByte();
final int numberOfBytesConsumed = 2;
int result = msbSize << 8 | lsbSize; int result = msbSize << 8 | lsbSize;
if (result < min || result > max) { if (result < min || result > max) {
result = -1; result = -1;
} }
return new Result<Integer>(result, numberOfBytesConsumed); return result;
}
/**
* See 1.5.5 Variable Byte Integer section of MQTT 5.0 specification for encoding/decoding rules
*
* @param buffer the buffer to decode from
* @return result pack with a = decoded integer, b = numberOfBytesConsumed. Need to unpack to read them.
* @throws DecoderException if bad MQTT protocol limits Remaining Length
*/
private static long decodeVariableByteInteger(ByteBuf buffer) {
int remainingLength = 0;
int multiplier = 1;
short digit;
int loops = 0;
do {
digit = buffer.readUnsignedByte();
remainingLength += (digit & 127) * multiplier;
multiplier *= 128;
loops++;
} while ((digit & 128) != 0 && loops < 4);
if (loops == 4 && (digit & 128) != 0) {
throw new DecoderException("MQTT protocol limits Remaining Length to 4 bytes");
}
return packInts(remainingLength, loops);
} }
private static final class Result<T> { private static final class Result<T> {
@@ -490,4 +736,82 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
this.numberOfBytesConsumed = numberOfBytesConsumed; this.numberOfBytesConsumed = numberOfBytesConsumed;
} }
} }
private static Result<MqttProperties> decodeProperties(ByteBuf buffer) {
final long propertiesLength = decodeVariableByteInteger(buffer);
int totalPropertiesLength = unpackA(propertiesLength);
int numberOfBytesConsumed = unpackB(propertiesLength);
MqttProperties decodedProperties = new MqttProperties();
while (numberOfBytesConsumed < totalPropertiesLength) {
long propertyId = decodeVariableByteInteger(buffer);
final int propertyIdValue = unpackA(propertyId);
numberOfBytesConsumed += unpackB(propertyId);
MqttProperties.MqttPropertyType propertyType = MqttProperties.MqttPropertyType.valueOf(propertyIdValue);
switch (propertyType) {
case PAYLOAD_FORMAT_INDICATOR:
case REQUEST_PROBLEM_INFORMATION:
case REQUEST_RESPONSE_INFORMATION:
case MAXIMUM_QOS:
case RETAIN_AVAILABLE:
case WILDCARD_SUBSCRIPTION_AVAILABLE:
case SUBSCRIPTION_IDENTIFIER_AVAILABLE:
case SHARED_SUBSCRIPTION_AVAILABLE:
final int b1 = buffer.readUnsignedByte();
numberOfBytesConsumed++;
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, b1));
break;
case SERVER_KEEP_ALIVE:
case RECEIVE_MAXIMUM:
case TOPIC_ALIAS_MAXIMUM:
case TOPIC_ALIAS:
final int int2BytesResult = decodeMsbLsb(buffer);
numberOfBytesConsumed += 2;
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, int2BytesResult));
break;
case PUBLICATION_EXPIRY_INTERVAL:
case SESSION_EXPIRY_INTERVAL:
case WILL_DELAY_INTERVAL:
case MAXIMUM_PACKET_SIZE:
final int maxPacketSize = buffer.readInt();
numberOfBytesConsumed += 4;
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, maxPacketSize));
break;
case SUBSCRIPTION_IDENTIFIER:
long vbIntegerResult = decodeVariableByteInteger(buffer);
numberOfBytesConsumed += unpackB(vbIntegerResult);
decodedProperties.add(new MqttProperties.IntegerProperty(propertyIdValue, unpackA(vbIntegerResult)));
break;
case CONTENT_TYPE:
case RESPONSE_TOPIC:
case ASSIGNED_CLIENT_IDENTIFIER:
case AUTHENTICATION_METHOD:
case RESPONSE_INFORMATION:
case SERVER_REFERENCE:
case REASON_STRING:
final Result<String> stringResult = decodeString(buffer);
numberOfBytesConsumed += stringResult.numberOfBytesConsumed;
decodedProperties.add(new MqttProperties.StringProperty(propertyIdValue, stringResult.value));
break;
case USER_PROPERTY:
final Result<String> keyResult = decodeString(buffer);
final Result<String> valueResult = decodeString(buffer);
numberOfBytesConsumed += keyResult.numberOfBytesConsumed;
numberOfBytesConsumed += valueResult.numberOfBytesConsumed;
decodedProperties.add(new MqttProperties.UserProperty(keyResult.value, valueResult.value));
break;
case CORRELATION_DATA:
case AUTHENTICATION_DATA:
final byte[] binaryDataResult = decodeByteArray(buffer);
numberOfBytesConsumed += binaryDataResult.length + 2;
decodedProperties.add(new MqttProperties.BinaryProperty(propertyIdValue, binaryDataResult));
break;
default:
//shouldn't reach here
throw new DecoderException("Unknown property type: " + propertyType);
}
}
return new Result<MqttProperties>(decodedProperties, numberOfBytesConsumed);
}
} }
+517 -172
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -18,19 +18,22 @@ package io.netty.handler.codec.mqtt;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.EncoderException;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.EmptyArrays;
import java.util.List; import java.util.List;
import static io.netty.handler.codec.mqtt.MqttCodecUtil.*; import static io.netty.buffer.ByteBufUtil.*;
/** /**
* Encodes Mqtt messages into bytes following the protocol specification v3.1 * Encodes Mqtt messages into bytes following the protocol specification v3.1
* as described here <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">MQTTV3.1</a> * as described here <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html">MQTTV3.1</a>
* or v5.0 as described here <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html">MQTTv5.0</a> -
* depending on the version specified in the first CONNECT message that goes through the channel.
*/ */
@ChannelHandler.Sharable @ChannelHandler.Sharable
public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> { public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
@@ -41,49 +44,57 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
@Override @Override
protected void encode(ChannelHandlerContext ctx, MqttMessage msg, List<Object> out) throws Exception { protected void encode(ChannelHandlerContext ctx, MqttMessage msg, List<Object> out) throws Exception {
out.add(doEncode(ctx.alloc(), msg)); out.add(doEncode(ctx, msg));
} }
/** /**
* This is the main encoding method. * This is the main encoding method.
* It's only visible for testing. * It's only visible for testing.
* *
* @param byteBufAllocator Allocates ByteBuf
* @param message MQTT message to encode * @param message MQTT message to encode
* @return ByteBuf with encoded bytes * @return ByteBuf with encoded bytes
*/ */
static ByteBuf doEncode(ByteBufAllocator byteBufAllocator, MqttMessage message) { static ByteBuf doEncode(ChannelHandlerContext ctx,
MqttMessage message) {
switch (message.fixedHeader().messageType()) { switch (message.fixedHeader().messageType()) {
case CONNECT: case CONNECT:
return encodeConnectMessage(byteBufAllocator, (MqttConnectMessage) message); return encodeConnectMessage(ctx, (MqttConnectMessage) message);
case CONNACK: case CONNACK:
return encodeConnAckMessage(byteBufAllocator, (MqttConnAckMessage) message); return encodeConnAckMessage(ctx, (MqttConnAckMessage) message);
case PUBLISH: case PUBLISH:
return encodePublishMessage(byteBufAllocator, (MqttPublishMessage) message); return encodePublishMessage(ctx, (MqttPublishMessage) message);
case SUBSCRIBE: case SUBSCRIBE:
return encodeSubscribeMessage(byteBufAllocator, (MqttSubscribeMessage) message); return encodeSubscribeMessage(ctx, (MqttSubscribeMessage) message);
case UNSUBSCRIBE: case UNSUBSCRIBE:
return encodeUnsubscribeMessage(byteBufAllocator, (MqttUnsubscribeMessage) message); return encodeUnsubscribeMessage(ctx, (MqttUnsubscribeMessage) message);
case SUBACK: case SUBACK:
return encodeSubAckMessage(byteBufAllocator, (MqttSubAckMessage) message); return encodeSubAckMessage(ctx, (MqttSubAckMessage) message);
case UNSUBACK: case UNSUBACK:
if (message instanceof MqttUnsubAckMessage) {
return encodeUnsubAckMessage(ctx, (MqttUnsubAckMessage) message);
}
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
case PUBACK: case PUBACK:
case PUBREC: case PUBREC:
case PUBREL: case PUBREL:
case PUBCOMP: case PUBCOMP:
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(byteBufAllocator, message); return encodePubReplyMessage(ctx, message);
case DISCONNECT:
case AUTH:
return encodeReasonCodePlusPropertiesMessage(ctx, message);
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
case DISCONNECT: return encodeMessageWithOnlySingleByteFixedHeader(ctx.alloc(), message);
return encodeMessageWithOnlySingleByteFixedHeader(byteBufAllocator, message);
default: default:
throw new IllegalArgumentException( throw new IllegalArgumentException(
@@ -92,7 +103,7 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} }
private static ByteBuf encodeConnectMessage( private static ByteBuf encodeConnectMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttConnectMessage message) { MqttConnectMessage message) {
int payloadBufferSize = 0; int payloadBufferSize = 0;
@@ -101,70 +112,96 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
MqttConnectPayload payload = message.payload(); MqttConnectPayload payload = message.payload();
MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(variableHeader.name(), MqttVersion mqttVersion = MqttVersion.fromProtocolNameAndLevel(variableHeader.name(),
(byte) variableHeader.version()); (byte) variableHeader.version());
MqttCodecUtil.setMqttVersion(ctx, mqttVersion);
// as MQTT 3.1 & 3.1.1 spec, If the User Name Flag is set to 0, the Password Flag MUST be set to 0
if (!variableHeader.hasUserName() && variableHeader.hasPassword()) {
throw new EncoderException("Without a username, the password MUST be not set");
}
// Client id // Client id
String clientIdentifier = payload.clientIdentifier(); String clientIdentifier = payload.clientIdentifier();
if (!isValidClientId(mqttVersion, clientIdentifier)) { if (!MqttCodecUtil.isValidClientId(mqttVersion, MqttConstant.DEFAULT_MAX_CLIENT_ID_LENGTH, clientIdentifier)) {
throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + clientIdentifier); throw new MqttIdentifierRejectedException("invalid clientIdentifier: " + clientIdentifier);
} }
byte[] clientIdentifierBytes = encodeStringUtf8(clientIdentifier); int clientIdentifierBytes = utf8Bytes(clientIdentifier);
payloadBufferSize += 2 + clientIdentifierBytes.length; payloadBufferSize += 2 + clientIdentifierBytes;
// Will topic and message // Will topic and message
String willTopic = payload.willTopic(); String willTopic = payload.willTopic();
byte[] willTopicBytes = willTopic != null ? encodeStringUtf8(willTopic) : EmptyArrays.EMPTY_BYTES; int willTopicBytes = nullableUtf8Bytes(willTopic);
String willMessage = payload.willMessage(); byte[] willMessage = payload.willMessageInBytes();
byte[] willMessageBytes = willMessage != null ? encodeStringUtf8(willMessage) : EmptyArrays.EMPTY_BYTES; byte[] willMessageBytes = willMessage != null ? willMessage : EmptyArrays.EMPTY_BYTES;
if (variableHeader.isWillFlag()) { if (variableHeader.isWillFlag()) {
payloadBufferSize += 2 + willTopicBytes.length; payloadBufferSize += 2 + willTopicBytes;
payloadBufferSize += 2 + willMessageBytes.length; payloadBufferSize += 2 + willMessageBytes.length;
} }
String userName = payload.userName(); String userName = payload.userName();
byte[] userNameBytes = userName != null ? encodeStringUtf8(userName) : EmptyArrays.EMPTY_BYTES; int userNameBytes = nullableUtf8Bytes(userName);
if (variableHeader.hasUserName()) { if (variableHeader.hasUserName()) {
payloadBufferSize += 2 + userNameBytes.length; payloadBufferSize += 2 + userNameBytes;
} }
byte[] passwordBytes = payload.password(); byte[] password = payload.passwordInBytes();
byte[] passwordBytes = password != null ? password : EmptyArrays.EMPTY_BYTES;
if (variableHeader.hasPassword()) { if (variableHeader.hasPassword()) {
payloadBufferSize += 2 + passwordBytes.length; payloadBufferSize += 2 + passwordBytes.length;
} }
// Fixed header // Fixed and variable header
byte[] protocolNameBytes = mqttVersion.protocolNameBytes(); byte[] protocolNameBytes = mqttVersion.protocolNameBytes();
int variableHeaderBufferSize = 2 + protocolNameBytes.length + 4; ByteBuf propertiesBuf = encodePropertiesIfNeeded(
int variablePartSize = variableHeaderBufferSize + payloadBufferSize; mqttVersion,
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize); ctx.alloc(),
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize); message.variableHeader().properties());
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader)); try {
writeVariableLengthInt(buf, variablePartSize); final ByteBuf willPropertiesBuf;
if (variableHeader.isWillFlag()) {
willPropertiesBuf = encodePropertiesIfNeeded(mqttVersion, ctx.alloc(), payload.willProperties());
payloadBufferSize += willPropertiesBuf.readableBytes();
} else {
willPropertiesBuf = Unpooled.EMPTY_BUFFER;
}
try {
int variableHeaderBufferSize = 2 + protocolNameBytes.length + 4 + propertiesBuf.readableBytes();
buf.writeShort(protocolNameBytes.length); int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
buf.writeBytes(protocolNameBytes); int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
buf.writeByte(variableHeader.version()); buf.writeShort(protocolNameBytes.length);
buf.writeByte(getConnVariableHeaderFlag(variableHeader)); buf.writeBytes(protocolNameBytes);
buf.writeShort(variableHeader.keepAliveTimeSeconds());
// Payload buf.writeByte(variableHeader.version());
buf.writeShort(clientIdentifierBytes.length); buf.writeByte(getConnVariableHeaderFlag(variableHeader));
buf.writeBytes(clientIdentifierBytes, 0, clientIdentifierBytes.length); buf.writeShort(variableHeader.keepAliveTimeSeconds());
if (variableHeader.isWillFlag()) { buf.writeBytes(propertiesBuf);
buf.writeShort(willTopicBytes.length);
buf.writeBytes(willTopicBytes, 0, willTopicBytes.length); // Payload
buf.writeShort(willMessageBytes.length); writeExactUTF8String(buf, clientIdentifier, clientIdentifierBytes);
buf.writeBytes(willMessageBytes, 0, willMessageBytes.length); if (variableHeader.isWillFlag()) {
buf.writeBytes(willPropertiesBuf);
writeExactUTF8String(buf, willTopic, willTopicBytes);
buf.writeShort(willMessageBytes.length);
buf.writeBytes(willMessageBytes, 0, willMessageBytes.length);
}
if (variableHeader.hasUserName()) {
writeExactUTF8String(buf, userName, userNameBytes);
}
if (variableHeader.hasPassword()) {
buf.writeShort(passwordBytes.length);
buf.writeBytes(passwordBytes, 0, passwordBytes.length);
}
return buf;
} finally {
willPropertiesBuf.release();
}
} finally {
propertiesBuf.release();
} }
if (variableHeader.hasUserName()) {
buf.writeShort(userNameBytes.length);
buf.writeBytes(userNameBytes, 0, userNameBytes.length);
}
if (variableHeader.hasPassword()) {
buf.writeShort(passwordBytes.length);
buf.writeBytes(passwordBytes, 0, passwordBytes.length);
}
return buf;
} }
private static int getConnVariableHeaderFlag(MqttConnectVariableHeader variableHeader) { private static int getConnVariableHeaderFlag(MqttConnectVariableHeader variableHeader) {
@@ -189,149 +226,285 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} }
private static ByteBuf encodeConnAckMessage( private static ByteBuf encodeConnAckMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttConnAckMessage message) { MqttConnAckMessage message) {
int length = 4; final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttConnectAckPayload payload = null; ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
if (message.payload() != null) { ctx.alloc(),
payload = (MqttConnectAckPayload)message.payload(); message.variableHeader().properties());
if (payload.getData() != null && payload.getData().length > 0) {
length += payload.getData().length;
}
}
ByteBuf buf = byteBufAllocator.buffer(length);
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
buf.writeByte(length - 2);
buf.writeByte(message.variableHeader().isSessionPresent() ? 0x01 : 0x00);
buf.writeByte(message.variableHeader().connectReturnCode().byteValue());
if (length > 4) {
buf.writeBytes(payload.getData());
}
return buf; try {
int length = 4;
MqttConnectAckPayload payload = null;
if (message.payload() != null) {
payload = (MqttConnectAckPayload)message.payload();
if (payload.getData() != null && payload.getData().length > 0) {
length += payload.getData().length;
}
}
ByteBuf buf = ctx.alloc().buffer(length + propertiesBuf.readableBytes());
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, length - 2 + propertiesBuf.readableBytes());
buf.writeByte(message.variableHeader().isSessionPresent() ? 0x01 : 0x00);
buf.writeByte(message.variableHeader().connectReturnCode().byteValue());
if (length > 4) {
buf.writeBytes(payload.getData());
}
buf.writeBytes(propertiesBuf);
return buf;
} finally {
propertiesBuf.release();
}
} }
private static ByteBuf encodeSubscribeMessage( private static ByteBuf encodeSubscribeMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttSubscribeMessage message) { MqttSubscribeMessage message) {
int variableHeaderBufferSize = 2; MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
int payloadBufferSize = 0; ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
MqttFixedHeader mqttFixedHeader = message.fixedHeader(); try {
MqttMessageIdVariableHeader variableHeader = message.variableHeader(); final int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
MqttSubscribePayload payload = message.payload(); int payloadBufferSize = 0;
for (MqttTopicSubscription topic : payload.topicSubscriptions()) { MqttFixedHeader mqttFixedHeader = message.fixedHeader();
String topicName = topic.topicName(); MqttMessageIdVariableHeader variableHeader = message.variableHeader();
byte[] topicNameBytes = encodeStringUtf8(topicName); MqttSubscribePayload payload = message.payload();
payloadBufferSize += 2 + topicNameBytes.length;
payloadBufferSize += 1; for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
String topicName = topic.topicName();
int topicNameBytes = utf8Bytes(topicName);
payloadBufferSize += 2 + topicNameBytes;
payloadBufferSize += 1;
}
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
buf.writeBytes(propertiesBuf);
// Payload
for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
writeUnsafeUTF8String(buf, topic.topicName());
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) {
buf.writeByte(topic.qualityOfService().value());
} else {
final MqttSubscriptionOption option = topic.option();
int optionEncoded = option.retainHandling().value() << 4;
if (option.isRetainAsPublished()) {
optionEncoded |= 0x08;
}
if (option.isNoLocal()) {
optionEncoded |= 0x04;
}
optionEncoded |= option.qos().value();
buf.writeByte(optionEncoded);
}
}
return buf;
} finally {
propertiesBuf.release();
} }
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
// Payload
for (MqttTopicSubscription topic : payload.topicSubscriptions()) {
String topicName = topic.topicName();
byte[] topicNameBytes = encodeStringUtf8(topicName);
buf.writeShort(topicNameBytes.length);
buf.writeBytes(topicNameBytes, 0, topicNameBytes.length);
buf.writeByte(topic.qualityOfService().value());
}
return buf;
} }
private static ByteBuf encodeUnsubscribeMessage( private static ByteBuf encodeUnsubscribeMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttUnsubscribeMessage message) { MqttUnsubscribeMessage message) {
int variableHeaderBufferSize = 2; MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
int payloadBufferSize = 0; ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
MqttFixedHeader mqttFixedHeader = message.fixedHeader(); try {
MqttMessageIdVariableHeader variableHeader = message.variableHeader(); final int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
MqttUnsubscribePayload payload = message.payload(); int payloadBufferSize = 0;
for (String topicName : payload.topics()) { MqttFixedHeader mqttFixedHeader = message.fixedHeader();
byte[] topicNameBytes = encodeStringUtf8(topicName); MqttMessageIdVariableHeader variableHeader = message.variableHeader();
payloadBufferSize += 2 + topicNameBytes.length; MqttUnsubscribePayload payload = message.payload();
for (String topicName : payload.topics()) {
int topicNameBytes = utf8Bytes(topicName);
payloadBufferSize += 2 + topicNameBytes;
}
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
buf.writeBytes(propertiesBuf);
// Payload
for (String topicName : payload.topics()) {
writeUnsafeUTF8String(buf, topicName);
}
return buf;
} finally {
propertiesBuf.release();
} }
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
// Variable Header
int messageId = variableHeader.messageId();
buf.writeShort(messageId);
// Payload
for (String topicName : payload.topics()) {
byte[] topicNameBytes = encodeStringUtf8(topicName);
buf.writeShort(topicNameBytes.length);
buf.writeBytes(topicNameBytes, 0, topicNameBytes.length);
}
return buf;
} }
private static ByteBuf encodeSubAckMessage( private static ByteBuf encodeSubAckMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttSubAckMessage message) { MqttSubAckMessage message) {
int variableHeaderBufferSize = 2; MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
int payloadBufferSize = message.payload().grantedQoSLevels().size(); ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
int variablePartSize = variableHeaderBufferSize + payloadBufferSize; ctx.alloc(),
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize); message.idAndPropertiesVariableHeader().properties());
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize); try {
buf.writeByte(getFixedHeaderByte1(message.fixedHeader())); int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
writeVariableLengthInt(buf, variablePartSize); int payloadBufferSize = message.payload().grantedQoSLevels().size();
buf.writeShort(message.variableHeader().messageId()); int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
for (int qos : message.payload().grantedQoSLevels()) { int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
buf.writeByte(qos); ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
} buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, variablePartSize);
buf.writeShort(message.variableHeader().messageId());
buf.writeBytes(propertiesBuf);
for (int code: message.payload().reasonCodes()) {
buf.writeByte(code);
}
return buf; return buf;
} finally {
propertiesBuf.release();
}
}
private static ByteBuf encodeUnsubAckMessage(
ChannelHandlerContext ctx,
MqttUnsubAckMessage message) {
if (message.variableHeader() instanceof MqttMessageIdAndPropertiesVariableHeader) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
ctx.alloc(),
message.idAndPropertiesVariableHeader().properties());
try {
int variableHeaderBufferSize = 2 + propertiesBuf.readableBytes();
MqttUnsubAckPayload payload = message.payload();
int payloadBufferSize = payload == null ? 0 : payload.unsubscribeReasonCodes().size();
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(message.fixedHeader()));
writeVariableLengthInt(buf, variablePartSize);
buf.writeShort(message.variableHeader().messageId());
buf.writeBytes(propertiesBuf);
if (payload != null) {
for (Short reasonCode : payload.unsubscribeReasonCodes()) {
buf.writeByte(reasonCode);
}
}
return buf;
} finally {
propertiesBuf.release();
}
} else {
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
}
} }
private static ByteBuf encodePublishMessage( private static ByteBuf encodePublishMessage(
ByteBufAllocator byteBufAllocator, ChannelHandlerContext ctx,
MqttPublishMessage message) { MqttPublishMessage message) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttFixedHeader mqttFixedHeader = message.fixedHeader(); MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttPublishVariableHeader variableHeader = message.variableHeader(); MqttPublishVariableHeader variableHeader = message.variableHeader();
ByteBuf payload = message.payload().duplicate(); ByteBuf payload = message.payload().duplicate();
String topicName = variableHeader.topicName(); String topicName = variableHeader.topicName();
byte[] topicNameBytes = encodeStringUtf8(topicName); int topicNameBytes = utf8Bytes(topicName);
int variableHeaderBufferSize = 2 + topicNameBytes.length + ByteBuf propertiesBuf = encodePropertiesIfNeeded(mqttVersion,
(mqttFixedHeader.qosLevel().value() > 0 ? 2 : 0); ctx.alloc(),
int payloadBufferSize = payload.readableBytes(); message.variableHeader().properties());
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variablePartSize); try {
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader)); int variableHeaderBufferSize = 2 + topicNameBytes +
writeVariableLengthInt(buf, variablePartSize); (mqttFixedHeader.qosLevel().value() > 0 ? 2 : 0) + propertiesBuf.readableBytes();
buf.writeShort(topicNameBytes.length); int payloadBufferSize = payload.readableBytes();
buf.writeBytes(topicNameBytes); int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
if (mqttFixedHeader.qosLevel().value() > 0) { int fixedHeaderBufferSize = 1 + getVariableLengthInt(variablePartSize);
buf.writeShort(variableHeader.messageId());
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variablePartSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variablePartSize);
writeExactUTF8String(buf, topicName, topicNameBytes);
if (mqttFixedHeader.qosLevel().value() > 0) {
buf.writeShort(variableHeader.packetId());
}
buf.writeBytes(propertiesBuf);
buf.writeBytes(payload);
return buf;
} finally {
propertiesBuf.release();
} }
buf.writeBytes(payload); }
return buf; private static ByteBuf encodePubReplyMessage(ChannelHandlerContext ctx,
MqttMessage message) {
if (message.variableHeader() instanceof MqttPubReplyMessageVariableHeader) {
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttPubReplyMessageVariableHeader variableHeader =
(MqttPubReplyMessageVariableHeader) message.variableHeader();
int msgId = variableHeader.messageId();
final ByteBuf propertiesBuf;
final boolean includeReasonCode;
final int variableHeaderBufferSize;
final MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
if (mqttVersion == MqttVersion.MQTT_5 &&
(variableHeader.reasonCode() != MqttPubReplyMessageVariableHeader.REASON_CODE_OK ||
!variableHeader.properties().isEmpty())) {
propertiesBuf = encodeProperties(ctx.alloc(), variableHeader.properties());
includeReasonCode = true;
variableHeaderBufferSize = 3 + propertiesBuf.readableBytes();
} else {
propertiesBuf = Unpooled.EMPTY_BUFFER;
includeReasonCode = false;
variableHeaderBufferSize = 2;
}
try {
final int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variableHeaderBufferSize);
buf.writeShort(msgId);
if (includeReasonCode) {
buf.writeByte(variableHeader.reasonCode());
}
buf.writeBytes(propertiesBuf);
return buf;
} finally {
propertiesBuf.release();
}
} else {
return encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(ctx.alloc(), message);
}
} }
private static ByteBuf encodeMessageWithOnlySingleByteFixedHeaderAndMessageId( private static ByteBuf encodeMessageWithOnlySingleByteFixedHeaderAndMessageId(
@@ -342,12 +515,11 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
int msgId = variableHeader.messageId(); int msgId = variableHeader.messageId();
int variableHeaderBufferSize = 2; // variable part only has a message id int variableHeaderBufferSize = 2; // variable part only has a message id
ByteBuf payload = null; ByteBuf payload = null;
if (message.payload() != null) { if (message.payload() != null) {
payload = ((ByteBuf)message.payload()).duplicate(); payload = ((ByteBuf)message.payload()).duplicate();
variableHeaderBufferSize += payload.readableBytes(); variableHeaderBufferSize += payload.readableBytes();
} }
int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize); int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variableHeaderBufferSize); ByteBuf buf = byteBufAllocator.buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader)); buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
@@ -355,11 +527,53 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
buf.writeShort(msgId); buf.writeShort(msgId);
if (payload != null) { if (payload != null) {
buf.writeBytes(payload); buf.writeBytes(payload);
} }
return buf; return buf;
} }
private static ByteBuf encodeReasonCodePlusPropertiesMessage(
ChannelHandlerContext ctx,
MqttMessage message) {
if (message.variableHeader() instanceof MqttReasonCodeAndPropertiesVariableHeader) {
MqttVersion mqttVersion = MqttCodecUtil.getMqttVersion(ctx);
MqttFixedHeader mqttFixedHeader = message.fixedHeader();
MqttReasonCodeAndPropertiesVariableHeader variableHeader =
(MqttReasonCodeAndPropertiesVariableHeader) message.variableHeader();
final ByteBuf propertiesBuf;
final boolean includeReasonCode;
final int variableHeaderBufferSize;
if (mqttVersion == MqttVersion.MQTT_5 &&
(variableHeader.reasonCode() != MqttReasonCodeAndPropertiesVariableHeader.REASON_CODE_OK ||
!variableHeader.properties().isEmpty())) {
propertiesBuf = encodeProperties(ctx.alloc(), variableHeader.properties());
includeReasonCode = true;
variableHeaderBufferSize = 1 + propertiesBuf.readableBytes();
} else {
propertiesBuf = Unpooled.EMPTY_BUFFER;
includeReasonCode = false;
variableHeaderBufferSize = 0;
}
try {
final int fixedHeaderBufferSize = 1 + getVariableLengthInt(variableHeaderBufferSize);
ByteBuf buf = ctx.alloc().buffer(fixedHeaderBufferSize + variableHeaderBufferSize);
buf.writeByte(getFixedHeaderByte1(mqttFixedHeader));
writeVariableLengthInt(buf, variableHeaderBufferSize);
if (includeReasonCode) {
buf.writeByte(variableHeader.reasonCode());
}
buf.writeBytes(propertiesBuf);
return buf;
} finally {
propertiesBuf.release();
}
} else {
return encodeMessageWithOnlySingleByteFixedHeader(ctx.alloc(), message);
}
}
private static ByteBuf encodeMessageWithOnlySingleByteFixedHeader( private static ByteBuf encodeMessageWithOnlySingleByteFixedHeader(
ByteBufAllocator byteBufAllocator, ByteBufAllocator byteBufAllocator,
MqttMessage message) { MqttMessage message) {
@@ -371,6 +585,104 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
return buf; return buf;
} }
private static ByteBuf encodePropertiesIfNeeded(MqttVersion mqttVersion,
ByteBufAllocator byteBufAllocator,
MqttProperties mqttProperties) {
if (mqttVersion == MqttVersion.MQTT_5) {
return encodeProperties(byteBufAllocator, mqttProperties);
}
return Unpooled.EMPTY_BUFFER;
}
private static ByteBuf encodeProperties(ByteBufAllocator byteBufAllocator,
MqttProperties mqttProperties) {
ByteBuf propertiesHeaderBuf = byteBufAllocator.buffer();
// encode also the Properties part
try {
ByteBuf propertiesBuf = byteBufAllocator.buffer();
try {
for (MqttProperties.MqttProperty property : mqttProperties.listAll()) {
MqttProperties.MqttPropertyType propertyType =
MqttProperties.MqttPropertyType.valueOf(property.propertyId);
switch (propertyType) {
case PAYLOAD_FORMAT_INDICATOR:
case REQUEST_PROBLEM_INFORMATION:
case REQUEST_RESPONSE_INFORMATION:
case MAXIMUM_QOS:
case RETAIN_AVAILABLE:
case WILDCARD_SUBSCRIPTION_AVAILABLE:
case SUBSCRIPTION_IDENTIFIER_AVAILABLE:
case SHARED_SUBSCRIPTION_AVAILABLE:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final byte bytePropValue = ((MqttProperties.IntegerProperty) property).value.byteValue();
propertiesBuf.writeByte(bytePropValue);
break;
case SERVER_KEEP_ALIVE:
case RECEIVE_MAXIMUM:
case TOPIC_ALIAS_MAXIMUM:
case TOPIC_ALIAS:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final short twoBytesInPropValue =
((MqttProperties.IntegerProperty) property).value.shortValue();
propertiesBuf.writeShort(twoBytesInPropValue);
break;
case PUBLICATION_EXPIRY_INTERVAL:
case SESSION_EXPIRY_INTERVAL:
case WILL_DELAY_INTERVAL:
case MAXIMUM_PACKET_SIZE:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final int fourBytesIntPropValue = ((MqttProperties.IntegerProperty) property).value;
propertiesBuf.writeInt(fourBytesIntPropValue);
break;
case SUBSCRIPTION_IDENTIFIER:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final int vbi = ((MqttProperties.IntegerProperty) property).value;
writeVariableLengthInt(propertiesBuf, vbi);
break;
case CONTENT_TYPE:
case RESPONSE_TOPIC:
case ASSIGNED_CLIENT_IDENTIFIER:
case AUTHENTICATION_METHOD:
case RESPONSE_INFORMATION:
case SERVER_REFERENCE:
case REASON_STRING:
writeVariableLengthInt(propertiesBuf, property.propertyId);
writeEagerUTF8String(propertiesBuf, ((MqttProperties.StringProperty) property).value);
break;
case USER_PROPERTY:
final List<MqttProperties.StringPair> pairs =
((MqttProperties.UserProperties) property).value;
for (MqttProperties.StringPair pair : pairs) {
writeVariableLengthInt(propertiesBuf, property.propertyId);
writeEagerUTF8String(propertiesBuf, pair.key);
writeEagerUTF8String(propertiesBuf, pair.value);
}
break;
case CORRELATION_DATA:
case AUTHENTICATION_DATA:
writeVariableLengthInt(propertiesBuf, property.propertyId);
final byte[] binaryPropValue = ((MqttProperties.BinaryProperty) property).value;
propertiesBuf.writeShort(binaryPropValue.length);
propertiesBuf.writeBytes(binaryPropValue, 0, binaryPropValue.length);
break;
default:
//shouldn't reach here
throw new EncoderException("Unknown property type: " + propertyType);
}
}
writeVariableLengthInt(propertiesHeaderBuf, propertiesBuf.readableBytes());
propertiesHeaderBuf.writeBytes(propertiesBuf);
return propertiesHeaderBuf;
} finally {
propertiesBuf.release();
}
} catch (RuntimeException e) {
propertiesHeaderBuf.release();
throw e;
}
}
private static int getFixedHeaderByte1(MqttFixedHeader header) { private static int getFixedHeaderByte1(MqttFixedHeader header) {
int ret = 0; int ret = 0;
ret |= header.messageType().value() << 4; ret |= header.messageType().value() << 4;
@@ -395,6 +707,42 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
} while (num > 0); } while (num > 0);
} }
private static int nullableUtf8Bytes(String s) {
return s == null? 0 : utf8Bytes(s);
}
private static int nullableMaxUtf8Bytes(String s) {
return s == null? 0 : utf8MaxBytes(s);
}
private static void writeExactUTF8String(ByteBuf buf, String s, int utf8Length) {
buf.ensureWritable(utf8Length + 2);
buf.writeShort(utf8Length);
if (utf8Length > 0) {
final int writtenUtf8Length = reserveAndWriteUtf8(buf, s, utf8Length);
assert writtenUtf8Length == utf8Length;
}
}
private static void writeEagerUTF8String(ByteBuf buf, String s) {
final int maxUtf8Length = nullableMaxUtf8Bytes(s);
buf.ensureWritable(maxUtf8Length + 2);
final int writerIndex = buf.writerIndex();
final int startUtf8String = writerIndex + 2;
buf.writerIndex(startUtf8String);
final int utf8Length = s != null? reserveAndWriteUtf8(buf, s, maxUtf8Length) : 0;
buf.setShort(writerIndex, utf8Length);
}
private static void writeUnsafeUTF8String(ByteBuf buf, String s) {
final int writerIndex = buf.writerIndex();
final int startUtf8String = writerIndex + 2;
// no need to reserve any capacity here, already done earlier: that's why is Unsafe
buf.writerIndex(startUtf8String);
final int utf8Length = s != null? reserveAndWriteUtf8(buf, s, 0) : 0;
buf.setShort(writerIndex, utf8Length);
}
private static int getVariableLengthInt(int num) { private static int getVariableLengthInt(int num) {
int count = 0; int count = 0;
do { do {
@@ -404,7 +752,4 @@ public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {
return count; return count;
} }
private static byte[] encodeStringUtf8(String s) {
return s.getBytes(CharsetUtil.UTF_8);
}
} }
+2 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -20,7 +20,7 @@ import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#fixed-header"> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#fixed-header">
* MQTTV3.1/fixed-header</a> * MQTTV3.1/fixed-header</a>
*/ */
public final class MqttFixedHeader { public final class MqttFixedHeader {
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+12 -1
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -29,6 +29,17 @@ public class MqttMessage {
private final Object payload; private final Object payload;
private final DecoderResult decoderResult; private final DecoderResult decoderResult;
// Constants for fixed-header only message types with all flags set to 0 (see
// https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_2.2_-)
public static final MqttMessage PINGREQ = new MqttMessage(new MqttFixedHeader(MqttMessageType.PINGREQ, false,
MqttQoS.AT_MOST_ONCE, false, 0));
public static final MqttMessage PINGRESP = new MqttMessage(new MqttFixedHeader(MqttMessageType.PINGRESP, false,
MqttQoS.AT_MOST_ONCE, false, 0));
public static final MqttMessage DISCONNECT = new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false,
MqttQoS.AT_MOST_ONCE, false, 0));
public MqttMessage(MqttFixedHeader mqttFixedHeader) { public MqttMessage(MqttFixedHeader mqttFixedHeader) {
this(mqttFixedHeader, null, null); this(mqttFixedHeader, null, null);
} }
+493 -14
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -15,10 +15,14 @@
*/ */
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
public final class MqttMessageBuilders { public final class MqttMessageBuilders {
@@ -29,6 +33,7 @@ public final class MqttMessageBuilders {
private MqttQoS qos; private MqttQoS qos;
private ByteBuf payload; private ByteBuf payload;
private int messageId; private int messageId;
private MqttProperties mqttProperties;
PublishBuilder() { PublishBuilder() {
} }
@@ -58,9 +63,15 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public PublishBuilder properties(MqttProperties properties) {
this.mqttProperties = properties;
return this;
}
public MqttPublishMessage build() { public MqttPublishMessage build() {
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retained, 0); MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retained, 0);
MqttPublishVariableHeader mqttVariableHeader = new MqttPublishVariableHeader(topic, messageId); MqttPublishVariableHeader mqttVariableHeader =
new MqttPublishVariableHeader(topic, messageId, mqttProperties);
return new MqttPublishMessage(mqttFixedHeader, mqttVariableHeader, Unpooled.buffer().writeBytes(payload)); return new MqttPublishMessage(mqttFixedHeader, mqttVariableHeader, Unpooled.buffer().writeBytes(payload));
} }
} }
@@ -73,13 +84,15 @@ public final class MqttMessageBuilders {
private boolean hasUser; private boolean hasUser;
private boolean hasPassword; private boolean hasPassword;
private int keepAliveSecs; private int keepAliveSecs;
private MqttProperties willProperties = MqttProperties.NO_PROPERTIES;
private boolean willFlag; private boolean willFlag;
private boolean willRetain; private boolean willRetain;
private MqttQoS willQos = MqttQoS.AT_MOST_ONCE; private MqttQoS willQos = MqttQoS.AT_MOST_ONCE;
private String willTopic; private String willTopic;
private String willMessage; private byte[] willMessage;
private String username; private String username;
private byte[] password; private byte[] password;
private MqttProperties properties = MqttProperties.NO_PROPERTIES;
ConnectBuilder() { ConnectBuilder() {
} }
@@ -119,7 +132,16 @@ public final class MqttMessageBuilders {
return this; return this;
} }
/**
* @deprecated use {@link ConnectBuilder#willMessage(byte[])} instead
*/
@Deprecated
public ConnectBuilder willMessage(String willMessage) { public ConnectBuilder willMessage(String willMessage) {
willMessage(willMessage == null ? null : willMessage.getBytes(CharsetUtil.UTF_8));
return this;
}
public ConnectBuilder willMessage(byte[] willMessage) {
this.willMessage = willMessage; this.willMessage = willMessage;
return this; return this;
} }
@@ -129,6 +151,11 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public ConnectBuilder willProperties(MqttProperties willProperties) {
this.willProperties = willProperties;
return this;
}
public ConnectBuilder hasUser(boolean value) { public ConnectBuilder hasUser(boolean value) {
this.hasUser = value; this.hasUser = value;
return this; return this;
@@ -140,17 +167,31 @@ public final class MqttMessageBuilders {
} }
public ConnectBuilder username(String username) { public ConnectBuilder username(String username) {
this.hasUser = true; this.hasUser = username != null;
this.username = username; this.username = username;
return this; return this;
} }
/**
* @deprecated use {@link ConnectBuilder#password(byte[])} instead
*/
@Deprecated
public ConnectBuilder password(String password) {
password(password == null ? null : password.getBytes(CharsetUtil.UTF_8));
return this;
}
public ConnectBuilder password(byte[] password) { public ConnectBuilder password(byte[] password) {
this.hasPassword = true; this.hasPassword = password != null;
this.password = password; this.password = password;
return this; return this;
} }
public ConnectBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttConnectMessage build() { public MqttConnectMessage build() {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
@@ -164,9 +205,10 @@ public final class MqttMessageBuilders {
willQos.value(), willQos.value(),
willFlag, willFlag,
cleanSession, cleanSession,
keepAliveSecs); keepAliveSecs,
properties);
MqttConnectPayload mqttConnectPayload = MqttConnectPayload mqttConnectPayload =
new MqttConnectPayload(clientId, willTopic, willMessage, username, password); new MqttConnectPayload(clientId, willProperties, willTopic, willMessage, username, password);
return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload); return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload);
} }
} }
@@ -175,36 +217,54 @@ public final class MqttMessageBuilders {
private List<MqttTopicSubscription> subscriptions; private List<MqttTopicSubscription> subscriptions;
private int messageId; private int messageId;
private MqttProperties properties;
SubscribeBuilder() { SubscribeBuilder() {
} }
public SubscribeBuilder addSubscription(MqttQoS qos, String topic) { public SubscribeBuilder addSubscription(MqttQoS qos, String topic) {
if (subscriptions == null) { ensureSubscriptionsExist();
subscriptions = new ArrayList<MqttTopicSubscription>(5);
}
subscriptions.add(new MqttTopicSubscription(topic, qos)); subscriptions.add(new MqttTopicSubscription(topic, qos));
return this; return this;
} }
public SubscribeBuilder addSubscription(String topic, MqttSubscriptionOption option) {
ensureSubscriptionsExist();
subscriptions.add(new MqttTopicSubscription(topic, option));
return this;
}
public SubscribeBuilder messageId(int messageId) { public SubscribeBuilder messageId(int messageId) {
this.messageId = messageId; this.messageId = messageId;
return this; return this;
} }
public SubscribeBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttSubscribeMessage build() { public MqttSubscribeMessage build() {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(messageId); MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(messageId, properties);
MqttSubscribePayload mqttSubscribePayload = new MqttSubscribePayload(subscriptions); MqttSubscribePayload mqttSubscribePayload = new MqttSubscribePayload(subscriptions);
return new MqttSubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload); return new MqttSubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
} }
private void ensureSubscriptionsExist() {
if (subscriptions == null) {
subscriptions = new ArrayList<MqttTopicSubscription>(5);
}
}
} }
public static final class UnsubscribeBuilder { public static final class UnsubscribeBuilder {
private List<String> topicFilters; private List<String> topicFilters;
private int messageId; private int messageId;
private MqttProperties properties;
UnsubscribeBuilder() { UnsubscribeBuilder() {
} }
@@ -222,21 +282,33 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public UnsubscribeBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttUnsubscribeMessage build() { public MqttUnsubscribeMessage build() {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttMessageIdVariableHeader mqttVariableHeader = MqttMessageIdVariableHeader.from(messageId); MqttMessageIdAndPropertiesVariableHeader mqttVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(messageId, properties);
MqttUnsubscribePayload mqttSubscribePayload = new MqttUnsubscribePayload(topicFilters); MqttUnsubscribePayload mqttSubscribePayload = new MqttUnsubscribePayload(topicFilters);
return new MqttUnsubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload); return new MqttUnsubscribeMessage(mqttFixedHeader, mqttVariableHeader, mqttSubscribePayload);
} }
} }
public interface PropertiesInitializer<T> {
void apply(T builder);
}
public static final class ConnAckBuilder { public static final class ConnAckBuilder {
private MqttConnectReturnCode returnCode; private MqttConnectReturnCode returnCode;
private boolean sessionPresent; private boolean sessionPresent;
private MqttProperties properties = MqttProperties.NO_PROPERTIES;
private ConnAckPropertiesBuilder propsBuilder;
ConnAckBuilder() { private ConnAckBuilder() {
} }
public ConnAckBuilder returnCode(MqttConnectReturnCode returnCode) { public ConnAckBuilder returnCode(MqttConnectReturnCode returnCode) {
@@ -249,15 +321,402 @@ public final class MqttMessageBuilders {
return this; return this;
} }
public ConnAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public ConnAckBuilder properties(PropertiesInitializer<ConnAckPropertiesBuilder> consumer) {
if (propsBuilder == null) {
propsBuilder = new ConnAckPropertiesBuilder();
}
consumer.apply(propsBuilder);
return this;
}
public MqttConnAckMessage build() { public MqttConnAckMessage build() {
if (propsBuilder != null) {
properties = propsBuilder.build();
}
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0); new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttConnAckVariableHeader mqttConnAckVariableHeader = MqttConnAckVariableHeader mqttConnAckVariableHeader =
new MqttConnAckVariableHeader(returnCode, sessionPresent); new MqttConnAckVariableHeader(returnCode, sessionPresent, properties);
return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader);
} }
} }
public static final class ConnAckPropertiesBuilder {
private String clientId;
private Long sessionExpiryInterval;
private int receiveMaximum;
private Byte maximumQos;
private boolean retain;
private Long maximumPacketSize;
private int topicAliasMaximum;
private String reasonString;
private final MqttProperties.UserProperties userProperties = new MqttProperties.UserProperties();
private Boolean wildcardSubscriptionAvailable;
private Boolean subscriptionIdentifiersAvailable;
private Boolean sharedSubscriptionAvailable;
private Integer serverKeepAlive;
private String responseInformation;
private String serverReference;
private String authenticationMethod;
private byte[] authenticationData;
public MqttProperties build() {
final MqttProperties props = new MqttProperties();
if (clientId != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.ASSIGNED_CLIENT_IDENTIFIER.value(),
clientId));
}
if (sessionExpiryInterval != null) {
props.add(new MqttProperties.IntegerProperty(
MqttProperties.MqttPropertyType.SESSION_EXPIRY_INTERVAL.value(), sessionExpiryInterval.intValue()));
}
if (receiveMaximum > 0) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.RECEIVE_MAXIMUM.value(), receiveMaximum));
}
if (maximumQos != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.MAXIMUM_QOS.value(), receiveMaximum));
}
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.RETAIN_AVAILABLE.value(), retain ? 1 : 0));
if (maximumPacketSize != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.MAXIMUM_PACKET_SIZE.value(),
maximumPacketSize.intValue()));
}
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.TOPIC_ALIAS_MAXIMUM.value(),
topicAliasMaximum));
if (reasonString != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.REASON_STRING.value(), reasonString));
}
props.add(userProperties);
if (wildcardSubscriptionAvailable != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.WILDCARD_SUBSCRIPTION_AVAILABLE.value(),
wildcardSubscriptionAvailable ? 1 : 0));
}
if (subscriptionIdentifiersAvailable != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SUBSCRIPTION_IDENTIFIER_AVAILABLE.value(),
subscriptionIdentifiersAvailable ? 1 : 0));
}
if (sharedSubscriptionAvailable != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SHARED_SUBSCRIPTION_AVAILABLE.value(),
sharedSubscriptionAvailable ? 1 : 0));
}
if (serverKeepAlive != null) {
props.add(new MqttProperties.IntegerProperty(MqttProperties.MqttPropertyType.SERVER_KEEP_ALIVE.value(),
serverKeepAlive));
}
if (responseInformation != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.RESPONSE_INFORMATION.value(),
responseInformation));
}
if (serverReference != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.SERVER_REFERENCE.value(),
serverReference));
}
if (authenticationMethod != null) {
props.add(new MqttProperties.StringProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_METHOD.value(),
authenticationMethod));
}
if (authenticationData != null) {
props.add(new MqttProperties.BinaryProperty(MqttProperties.MqttPropertyType.AUTHENTICATION_DATA.value(),
authenticationData));
}
return props;
}
public ConnAckPropertiesBuilder sessionExpiryInterval(long seconds) {
this.sessionExpiryInterval = seconds;
return this;
}
public ConnAckPropertiesBuilder receiveMaximum(int value) {
this.receiveMaximum = checkPositive(value, "value");
return this;
}
public ConnAckPropertiesBuilder maximumQos(byte value) {
if (value != 0 && value != 1) {
throw new IllegalArgumentException("maximum QoS property could be 0 or 1");
}
this.maximumQos = value;
return this;
}
public ConnAckPropertiesBuilder retainAvailable(boolean retain) {
this.retain = retain;
return this;
}
public ConnAckPropertiesBuilder maximumPacketSize(long size) {
this.maximumPacketSize = checkPositive(size, "size");
return this;
}
public ConnAckPropertiesBuilder assignedClientId(String clientId) {
this.clientId = clientId;
return this;
}
public ConnAckPropertiesBuilder topicAliasMaximum(int value) {
this.topicAliasMaximum = value;
return this;
}
public ConnAckPropertiesBuilder reasonString(String reason) {
this.reasonString = reason;
return this;
}
public ConnAckPropertiesBuilder userProperty(String name, String value) {
userProperties.add(name, value);
return this;
}
public ConnAckPropertiesBuilder wildcardSubscriptionAvailable(boolean value) {
this.wildcardSubscriptionAvailable = value;
return this;
}
public ConnAckPropertiesBuilder subscriptionIdentifiersAvailable(boolean value) {
this.subscriptionIdentifiersAvailable = value;
return this;
}
public ConnAckPropertiesBuilder sharedSubscriptionAvailable(boolean value) {
this.sharedSubscriptionAvailable = value;
return this;
}
public ConnAckPropertiesBuilder serverKeepAlive(int seconds) {
this.serverKeepAlive = seconds;
return this;
}
public ConnAckPropertiesBuilder responseInformation(String value) {
this.responseInformation = value;
return this;
}
public ConnAckPropertiesBuilder serverReference(String host) {
this.serverReference = host;
return this;
}
public ConnAckPropertiesBuilder authenticationMethod(String methodName) {
this.authenticationMethod = methodName;
return this;
}
public ConnAckPropertiesBuilder authenticationData(byte[] rawData) {
this.authenticationData = rawData.clone();
return this;
}
}
public static final class PubAckBuilder {
private int packetId;
private byte reasonCode;
private MqttProperties properties;
PubAckBuilder() {
}
public PubAckBuilder reasonCode(byte reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public PubAckBuilder packetId(int packetId) {
this.packetId = packetId;
return this;
}
/**
* @deprecated use {@link PubAckBuilder#packetId(int)} instead
*/
@Deprecated
public PubAckBuilder packetId(short packetId) {
return packetId(packetId & 0xFFFF);
}
public PubAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public MqttMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttPubReplyMessageVariableHeader mqttPubAckVariableHeader =
new MqttPubReplyMessageVariableHeader(packetId, reasonCode, properties);
return new MqttMessage(mqttFixedHeader, mqttPubAckVariableHeader);
}
}
public static final class SubAckBuilder {
private int packetId;
private MqttProperties properties;
private final List<MqttQoS> grantedQoses = new ArrayList<MqttQoS>();
SubAckBuilder() {
}
public SubAckBuilder packetId(int packetId) {
this.packetId = packetId;
return this;
}
/**
* @deprecated use {@link SubAckBuilder#packetId(int)} instead
*/
@Deprecated
public SubAckBuilder packetId(short packetId) {
return packetId(packetId & 0xFFFF);
}
public SubAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public SubAckBuilder addGrantedQos(MqttQoS qos) {
this.grantedQoses.add(qos);
return this;
}
public SubAckBuilder addGrantedQoses(MqttQoS... qoses) {
this.grantedQoses.addAll(Arrays.asList(qoses));
return this;
}
public MqttSubAckMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttMessageIdAndPropertiesVariableHeader mqttSubAckVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(packetId, properties);
//transform to primitive types
int[] grantedQoses = new int[this.grantedQoses.size()];
int i = 0;
for (MqttQoS grantedQos : this.grantedQoses) {
grantedQoses[i++] = grantedQos.value();
}
MqttSubAckPayload subAckPayload = new MqttSubAckPayload(grantedQoses);
return new MqttSubAckMessage(mqttFixedHeader, mqttSubAckVariableHeader, subAckPayload);
}
}
public static final class UnsubAckBuilder {
private int packetId;
private MqttProperties properties;
private final List<Short> reasonCodes = new ArrayList<Short>();
UnsubAckBuilder() {
}
public UnsubAckBuilder packetId(int packetId) {
this.packetId = packetId;
return this;
}
/**
* @deprecated use {@link UnsubAckBuilder#packetId(int)} instead
*/
@Deprecated
public UnsubAckBuilder packetId(short packetId) {
return packetId(packetId & 0xFFFF);
}
public UnsubAckBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public UnsubAckBuilder addReasonCode(short reasonCode) {
this.reasonCodes.add(reasonCode);
return this;
}
public UnsubAckBuilder addReasonCodes(Short... reasonCodes) {
this.reasonCodes.addAll(Arrays.asList(reasonCodes));
return this;
}
public MqttUnsubAckMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.UNSUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttMessageIdAndPropertiesVariableHeader mqttSubAckVariableHeader =
new MqttMessageIdAndPropertiesVariableHeader(packetId, properties);
MqttUnsubAckPayload subAckPayload = new MqttUnsubAckPayload(reasonCodes);
return new MqttUnsubAckMessage(mqttFixedHeader, mqttSubAckVariableHeader, subAckPayload);
}
}
public static final class DisconnectBuilder {
private MqttProperties properties;
private byte reasonCode;
DisconnectBuilder() {
}
public DisconnectBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public DisconnectBuilder reasonCode(byte reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public MqttMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttReasonCodeAndPropertiesVariableHeader mqttDisconnectVariableHeader =
new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, properties);
return new MqttMessage(mqttFixedHeader, mqttDisconnectVariableHeader);
}
}
public static final class AuthBuilder {
private MqttProperties properties;
private byte reasonCode;
AuthBuilder() {
}
public AuthBuilder properties(MqttProperties properties) {
this.properties = properties;
return this;
}
public AuthBuilder reasonCode(byte reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public MqttMessage build() {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.AUTH, false, MqttQoS.AT_MOST_ONCE, false, 0);
MqttReasonCodeAndPropertiesVariableHeader mqttAuthVariableHeader =
new MqttReasonCodeAndPropertiesVariableHeader(reasonCode, properties);
return new MqttMessage(mqttFixedHeader, mqttAuthVariableHeader);
}
}
public static ConnectBuilder connect() { public static ConnectBuilder connect() {
return new ConnectBuilder(); return new ConnectBuilder();
} }
@@ -278,6 +737,26 @@ public final class MqttMessageBuilders {
return new UnsubscribeBuilder(); return new UnsubscribeBuilder();
} }
public static PubAckBuilder pubAck() {
return new PubAckBuilder();
}
public static SubAckBuilder subAck() {
return new SubAckBuilder();
}
public static UnsubAckBuilder unsubAck() {
return new UnsubAckBuilder();
}
public static DisconnectBuilder disconnect() {
return new DisconnectBuilder();
}
public static AuthBuilder auth() {
return new AuthBuilder();
}
private MqttMessageBuilders() { private MqttMessageBuilders() {
} }
} }
+16 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -50,7 +50,8 @@ public final class MqttMessageFactory {
case UNSUBACK: case UNSUBACK:
return new MqttUnsubAckMessage( return new MqttUnsubAckMessage(
mqttFixedHeader, mqttFixedHeader,
(MqttMessageIdVariableHeader) variableHeader); (MqttMessageIdVariableHeader) variableHeader,
(MqttUnsubAckPayload) payload);
case UNSUBSCRIBE: case UNSUBSCRIBE:
return new MqttUnsubscribeMessage( return new MqttUnsubscribeMessage(
@@ -65,17 +66,24 @@ public final class MqttMessageFactory {
(ByteBuf) payload); (ByteBuf) payload);
case PUBACK: case PUBACK:
//Having MqttPubReplyMessageVariableHeader or MqttMessageIdVariableHeader
return new MqttPubAckMessage(mqttFixedHeader, (MqttMessageIdVariableHeader) variableHeader); return new MqttPubAckMessage(mqttFixedHeader, (MqttMessageIdVariableHeader) variableHeader);
case PUBREC: case PUBREC:
case PUBREL: case PUBREL:
case PUBCOMP: case PUBCOMP:
//Having MqttPubReplyMessageVariableHeader or MqttMessageIdVariableHeader
return new MqttMessage(mqttFixedHeader, variableHeader); return new MqttMessage(mqttFixedHeader, variableHeader);
case PINGREQ: case PINGREQ:
case PINGRESP: case PINGRESP:
case DISCONNECT:
return new MqttMessage(mqttFixedHeader); return new MqttMessage(mqttFixedHeader);
case DISCONNECT:
case AUTH:
//Having MqttReasonCodeAndPropertiesVariableHeader
return new MqttMessage(mqttFixedHeader,
variableHeader);
default: default:
throw new IllegalArgumentException("unknown message type: " + mqttFixedHeader.messageType()); throw new IllegalArgumentException("unknown message type: " + mqttFixedHeader.messageType());
} }
@@ -85,5 +93,10 @@ public final class MqttMessageFactory {
return new MqttMessage(null, null, null, DecoderResult.failure(cause)); return new MqttMessage(null, null, null, DecoderResult.failure(cause));
} }
public static MqttMessage newInvalidMessage(MqttFixedHeader mqttFixedHeader, Object variableHeader,
Throwable cause) {
return new MqttMessage(mqttFixedHeader, variableHeader, null, DecoderResult.failure(cause));
}
private MqttMessageFactory() { } private MqttMessageFactory() { }
} }
@@ -0,0 +1,52 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.StringUtil;
/**
* Variable Header containing, Packet Id and Properties as in MQTT v5 spec.
*/
public final class MqttMessageIdAndPropertiesVariableHeader extends MqttMessageIdVariableHeader {
private final MqttProperties properties;
public MqttMessageIdAndPropertiesVariableHeader(int messageId, MqttProperties properties) {
super(messageId);
if (messageId < 1 || messageId > 0xffff) {
throw new IllegalArgumentException("messageId: " + messageId + " (expected: 1 ~ 65535)");
}
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
return StringUtil.simpleClassName(this) + "[" +
"messageId=" + messageId() +
", properties=" + properties +
']';
}
@Override
MqttMessageIdAndPropertiesVariableHeader withDefaultEmptyProperties() {
return this;
}
}
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -20,9 +20,9 @@ import io.netty.util.internal.StringUtil;
/** /**
* Variable Header containing only Message Id * Variable Header containing only Message Id
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#msg-id">MQTTV3.1/msg-id</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#msg-id">MQTTV3.1/msg-id</a>
*/ */
public final class MqttMessageIdVariableHeader { public class MqttMessageIdVariableHeader {
private final int messageId; private final int messageId;
@@ -33,7 +33,7 @@ public final class MqttMessageIdVariableHeader {
return new MqttMessageIdVariableHeader(messageId); return new MqttMessageIdVariableHeader(messageId);
} }
private MqttMessageIdVariableHeader(int messageId) { protected MqttMessageIdVariableHeader(int messageId) {
this.messageId = messageId; this.messageId = messageId;
} }
@@ -49,4 +49,12 @@ public final class MqttMessageIdVariableHeader {
.append(']') .append(']')
.toString(); .toString();
} }
public MqttMessageIdAndPropertiesVariableHeader withEmptyProperties() {
return new MqttMessageIdAndPropertiesVariableHeader(messageId, MqttProperties.NO_PROPERTIES);
}
MqttMessageIdAndPropertiesVariableHeader withDefaultEmptyProperties() {
return withEmptyProperties();
}
} }
+22 -7
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -33,7 +33,24 @@ public enum MqttMessageType {
UNSUBACK(11), UNSUBACK(11),
PINGREQ(12), PINGREQ(12),
PINGRESP(13), PINGRESP(13),
DISCONNECT(14); DISCONNECT(14),
AUTH(15);
private static final MqttMessageType[] VALUES;
static {
// this prevent values to be assigned with the wrong order
// and ensure valueOf to work fine
final MqttMessageType[] values = values();
VALUES = new MqttMessageType[values.length + 1];
for (MqttMessageType mqttMessageType : values) {
final int value = mqttMessageType.value;
if (VALUES[value] != null) {
throw new AssertionError("value already in use: " + value);
}
VALUES[value] = mqttMessageType;
}
}
private final int value; private final int value;
@@ -46,12 +63,10 @@ public enum MqttMessageType {
} }
public static MqttMessageType valueOf(int type) { public static MqttMessageType valueOf(int type) {
for (MqttMessageType t : values()) { if (type <= 0 || type >= VALUES.length) {
if (t.value == type) { throw new IllegalArgumentException("unknown message type: " + type);
return t;
}
} }
throw new IllegalArgumentException("unknown message type: " + type); return VALUES[type];
} }
} }
@@ -0,0 +1,414 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.collection.IntObjectHashMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
/**
* MQTT Properties container
* */
public final class MqttProperties {
public enum MqttPropertyType {
// single byte properties
PAYLOAD_FORMAT_INDICATOR(0x01),
REQUEST_PROBLEM_INFORMATION(0x17),
REQUEST_RESPONSE_INFORMATION(0x19),
MAXIMUM_QOS(0x24),
RETAIN_AVAILABLE(0x25),
WILDCARD_SUBSCRIPTION_AVAILABLE(0x28),
SUBSCRIPTION_IDENTIFIER_AVAILABLE(0x29),
SHARED_SUBSCRIPTION_AVAILABLE(0x2A),
// two bytes properties
SERVER_KEEP_ALIVE(0x13),
RECEIVE_MAXIMUM(0x21),
TOPIC_ALIAS_MAXIMUM(0x22),
TOPIC_ALIAS(0x23),
// four bytes properties
PUBLICATION_EXPIRY_INTERVAL(0x02),
SESSION_EXPIRY_INTERVAL(0x11),
WILL_DELAY_INTERVAL(0x18),
MAXIMUM_PACKET_SIZE(0x27),
// Variable Byte Integer
SUBSCRIPTION_IDENTIFIER(0x0B),
// UTF-8 Encoded String properties
CONTENT_TYPE(0x03),
RESPONSE_TOPIC(0x08),
ASSIGNED_CLIENT_IDENTIFIER(0x12),
AUTHENTICATION_METHOD(0x15),
RESPONSE_INFORMATION(0x1A),
SERVER_REFERENCE(0x1C),
REASON_STRING(0x1F),
USER_PROPERTY(0x26),
// Binary Data
CORRELATION_DATA(0x09),
AUTHENTICATION_DATA(0x16);
private static final MqttPropertyType[] VALUES;
static {
VALUES = new MqttPropertyType[43];
for (MqttPropertyType v : values()) {
VALUES[v.value] = v;
}
}
private final int value;
MqttPropertyType(int value) {
this.value = value;
}
public int value() {
return value;
}
public static MqttPropertyType valueOf(int type) {
MqttPropertyType t = null;
try {
t = VALUES[type];
} catch (ArrayIndexOutOfBoundsException ignored) {
// nop
}
if (t == null) {
throw new IllegalArgumentException("unknown property type: " + type);
}
return t;
}
}
public static final MqttProperties NO_PROPERTIES = new MqttProperties(false);
static MqttProperties withEmptyDefaults(MqttProperties properties) {
if (properties == null) {
return MqttProperties.NO_PROPERTIES;
}
return properties;
}
/**
* MQTT property base class
*
* @param <T> property type
*/
public abstract static class MqttProperty<T> {
final T value;
final int propertyId;
protected MqttProperty(int propertyId, T value) {
this.propertyId = propertyId;
this.value = value;
}
/**
* Get MQTT property value
*
* @return property value
*/
public T value() {
return value;
}
/**
* Get MQTT property ID
* @return property ID
*/
public int propertyId() {
return propertyId;
}
@Override
public int hashCode() {
return propertyId + 31 * value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MqttProperty that = (MqttProperty) obj;
return this.propertyId == that.propertyId && this.value.equals(that.value);
}
}
public static final class IntegerProperty extends MqttProperty<Integer> {
public IntegerProperty(int propertyId, Integer value) {
super(propertyId, value);
}
@Override
public String toString() {
return "IntegerProperty(" + propertyId + ", " + value + ")";
}
}
public static final class StringProperty extends MqttProperty<String> {
public StringProperty(int propertyId, String value) {
super(propertyId, value);
}
@Override
public String toString() {
return "StringProperty(" + propertyId + ", " + value + ")";
}
}
public static final class StringPair {
public final String key;
public final String value;
public StringPair(String key, String value) {
this.key = key;
this.value = value;
}
@Override
public int hashCode() {
return key.hashCode() + 31 * value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
StringPair that = (StringPair) obj;
return that.key.equals(this.key) && that.value.equals(this.value);
}
}
//User properties are the only properties that may be included multiple times and
//are the only properties where ordering is required. Therefore, they need a special handling
public static final class UserProperties extends MqttProperty<List<StringPair>> {
public UserProperties() {
super(MqttPropertyType.USER_PROPERTY.value, new ArrayList<StringPair>());
}
/**
* Create user properties from the collection of the String pair values
*
* @param values string pairs. Collection entries are copied, collection itself isn't shared
*/
public UserProperties(Collection<StringPair> values) {
this();
this.value.addAll(values);
}
private static UserProperties fromUserPropertyCollection(Collection<UserProperty> properties) {
UserProperties userProperties = new UserProperties();
for (UserProperty property: properties) {
userProperties.add(new StringPair(property.value.key, property.value.value));
}
return userProperties;
}
public void add(StringPair pair) {
this.value.add(pair);
}
public void add(String key, String value) {
this.value.add(new StringPair(key, value));
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("UserProperties(");
boolean first = true;
for (StringPair pair: value) {
if (!first) {
builder.append(", ");
}
builder.append(pair.key + "->" + pair.value);
first = false;
}
builder.append(")");
return builder.toString();
}
}
public static final class UserProperty extends MqttProperty<StringPair> {
public UserProperty(String key, String value) {
super(MqttPropertyType.USER_PROPERTY.value, new StringPair(key, value));
}
@Override
public String toString() {
return "UserProperty(" + value.key + ", " + value.value + ")";
}
}
public static final class BinaryProperty extends MqttProperty<byte[]> {
public BinaryProperty(int propertyId, byte[] value) {
super(propertyId, value);
}
@Override
public String toString() {
return "BinaryProperty(" + propertyId + ", " + value.length + " bytes)";
}
}
public MqttProperties() {
this(true);
}
private MqttProperties(boolean canModify) {
this.canModify = canModify;
}
private IntObjectHashMap<MqttProperty> props;
private List<UserProperty> userProperties;
private List<IntegerProperty> subscriptionIds;
private final boolean canModify;
public void add(MqttProperty property) {
if (!canModify) {
throw new UnsupportedOperationException("adding property isn't allowed");
}
IntObjectHashMap<MqttProperty> props = this.props;
if (property.propertyId == MqttPropertyType.USER_PROPERTY.value) {
List<UserProperty> userProperties = this.userProperties;
if (userProperties == null) {
userProperties = new ArrayList<UserProperty>(1);
this.userProperties = userProperties;
}
if (property instanceof UserProperty) {
userProperties.add((UserProperty) property);
} else if (property instanceof UserProperties) {
for (StringPair pair: ((UserProperties) property).value) {
userProperties.add(new UserProperty(pair.key, pair.value));
}
} else {
throw new IllegalArgumentException("User property must be of UserProperty or UserProperties type");
}
} else if (property.propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
List<IntegerProperty> subscriptionIds = this.subscriptionIds;
if (subscriptionIds == null) {
subscriptionIds = new ArrayList<IntegerProperty>(1);
this.subscriptionIds = subscriptionIds;
}
if (property instanceof IntegerProperty) {
subscriptionIds.add((IntegerProperty) property);
} else {
throw new IllegalArgumentException("Subscription ID must be an integer property");
}
} else {
if (props == null) {
props = new IntObjectHashMap<MqttProperty>();
this.props = props;
}
props.put(property.propertyId, property);
}
}
public Collection<? extends MqttProperty> listAll() {
IntObjectHashMap<MqttProperty> props = this.props;
if (props == null && subscriptionIds == null && userProperties == null) {
return Collections.<MqttProperty>emptyList();
}
if (subscriptionIds == null && userProperties == null) {
return props.values();
}
if (props == null && userProperties == null) {
return subscriptionIds;
}
List<MqttProperty> propValues = new ArrayList<MqttProperty>(props != null ? props.size() : 1);
if (props != null) {
propValues.addAll(props.values());
}
if (subscriptionIds != null) {
propValues.addAll(subscriptionIds);
}
if (userProperties != null) {
propValues.add(UserProperties.fromUserPropertyCollection(userProperties));
}
return propValues;
}
public boolean isEmpty() {
IntObjectHashMap<MqttProperty> props = this.props;
return props == null || props.isEmpty();
}
/**
* Get property by ID. If there are multiple properties of this type (can be with Subscription ID)
* then return the first one.
*
* @param propertyId ID of the property
* @return a property if it is set, null otherwise
*/
public MqttProperty getProperty(int propertyId) {
if (propertyId == MqttPropertyType.USER_PROPERTY.value) {
//special handling to keep compatibility with earlier versions
List<UserProperty> userProperties = this.userProperties;
if (userProperties == null) {
return null;
}
return UserProperties.fromUserPropertyCollection(userProperties);
}
if (propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
List<IntegerProperty> subscriptionIds = this.subscriptionIds;
if (subscriptionIds == null || subscriptionIds.isEmpty()) {
return null;
}
return subscriptionIds.get(0);
}
IntObjectHashMap<MqttProperty> props = this.props;
return props == null ? null : props.get(propertyId);
}
/**
* Get properties by ID.
* Some properties (Subscription ID and User Properties) may occur multiple times,
* this method returns all their values in order.
*
* @param propertyId ID of the property
* @return all properties having specified ID
*/
public List<? extends MqttProperty> getProperties(int propertyId) {
if (propertyId == MqttPropertyType.USER_PROPERTY.value) {
return userProperties == null ? Collections.<MqttProperty>emptyList() : userProperties;
}
if (propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
return subscriptionIds == null ? Collections.<MqttProperty>emptyList() : subscriptionIds;
}
IntObjectHashMap<MqttProperty> props = this.props;
return (props == null || !props.containsKey(propertyId)) ?
Collections.<MqttProperty>emptyList() :
Collections.singletonList(props.get(propertyId));
}
}
+2 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,7 +17,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#puback">MQTTV3.1/puback</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#puback">MQTTV3.1/puback</a>
*/ */
public final class MqttPubAckMessage extends MqttMessage { public final class MqttPubAckMessage extends MqttMessage {
@@ -0,0 +1,56 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.StringUtil;
/**
* Variable Header containing Packet Id, reason code and Properties as in MQTT v5 spec.
*/
public final class MqttPubReplyMessageVariableHeader extends MqttMessageIdVariableHeader {
private final byte reasonCode;
private final MqttProperties properties;
public static final byte REASON_CODE_OK = 0;
public MqttPubReplyMessageVariableHeader(int messageId, byte reasonCode, MqttProperties properties) {
super(messageId);
if (messageId < 1 || messageId > 0xffff) {
throw new IllegalArgumentException("messageId: " + messageId + " (expected: 1 ~ 65535)");
}
this.reasonCode = reasonCode;
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public byte reasonCode() {
return reasonCode;
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
return StringUtil.simpleClassName(this) + "[" +
"messageId=" + messageId() +
", reasonCode=" + reasonCode +
", properties=" + properties +
']';
}
}
+4 -8
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -18,10 +18,10 @@ package io.netty.handler.codec.mqtt;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder; import io.netty.buffer.ByteBufHolder;
import io.netty.util.IllegalReferenceCountException; import io.netty.buffer.ByteBufUtil;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#publish">MQTTV3.1/publish</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#publish">MQTTV3.1/publish</a>
*/ */
public class MqttPublishMessage extends MqttMessage implements ByteBufHolder { public class MqttPublishMessage extends MqttMessage implements ByteBufHolder {
@@ -44,11 +44,7 @@ public class MqttPublishMessage extends MqttMessage implements ByteBufHolder {
@Override @Override
public ByteBuf content() { public ByteBuf content() {
final ByteBuf data = (ByteBuf) super.payload(); return ByteBufUtil.ensureAccessible((ByteBuf) super.payload());
if (data.refCnt() <= 0) {
throw new IllegalReferenceCountException(data.refCnt());
}
return data;
} }
@Override @Override
+16 -2
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -24,11 +24,17 @@ import io.netty.util.internal.StringUtil;
public final class MqttPublishVariableHeader { public final class MqttPublishVariableHeader {
private final String topicName; private final String topicName;
private final int packetId; private int packetId;
private final MqttProperties properties;
public MqttPublishVariableHeader(String topicName, int packetId) { public MqttPublishVariableHeader(String topicName, int packetId) {
this(topicName, packetId, MqttProperties.NO_PROPERTIES);
}
public MqttPublishVariableHeader(String topicName, int packetId, MqttProperties properties) {
this.topicName = topicName; this.topicName = topicName;
this.packetId = packetId; this.packetId = packetId;
this.properties = MqttProperties.withEmptyDefaults(properties);
} }
public String topicName() { public String topicName() {
@@ -47,6 +53,14 @@ public final class MqttPublishVariableHeader {
return packetId; return packetId;
} }
public MqttProperties properties() {
return properties;
}
public void setPacketId(int packetId) {
this.packetId = packetId;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
+12 -6
View File
@@ -5,7 +5,7 @@
* "License"); you may not use this file except in compliance with the License. You may obtain a * "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at: * copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software distributed under the License * Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
@@ -32,11 +32,17 @@ public enum MqttQoS {
} }
public static MqttQoS valueOf(int value) { public static MqttQoS valueOf(int value) {
for (MqttQoS q: values()) { switch (value) {
if (q.value == value) { case 0:
return q; return AT_MOST_ONCE;
} case 1:
return AT_LEAST_ONCE;
case 2:
return EXACTLY_ONCE;
case 0x80:
return FAILURE;
default:
throw new IllegalArgumentException("invalid QoS: " + value);
} }
throw new IllegalArgumentException("invalid QoS: " + value);
} }
} }
@@ -0,0 +1,54 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.StringUtil;
/**
* Variable Header for AUTH and DISCONNECT messages represented by {@link MqttMessage}
*/
public final class MqttReasonCodeAndPropertiesVariableHeader {
private final byte reasonCode;
private final MqttProperties properties;
public static final byte REASON_CODE_OK = 0;
public MqttReasonCodeAndPropertiesVariableHeader(byte reasonCode,
MqttProperties properties) {
this.reasonCode = reasonCode;
this.properties = MqttProperties.withEmptyDefaults(properties);
}
public byte reasonCode() {
return reasonCode;
}
public MqttProperties properties() {
return properties;
}
@Override
public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this))
.append('[')
.append("reasonCode=").append(reasonCode)
.append(", properties=").append(properties)
.append(']')
.toString();
}
}
+14 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,22 +17,33 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#suback">MQTTV3.1/suback</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#suback">MQTTV3.1/suback</a>
*/ */
public final class MqttSubAckMessage extends MqttMessage { public final class MqttSubAckMessage extends MqttMessage {
public MqttSubAckMessage( public MqttSubAckMessage(
MqttFixedHeader mqttFixedHeader, MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader, MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttSubAckPayload payload) { MqttSubAckPayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
public MqttSubAckMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttSubAckPayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override @Override
public MqttSubAckPayload payload() { public MqttSubAckPayload payload() {
return (MqttSubAckPayload) super.payload(); return (MqttSubAckPayload) super.payload();
+26 -17
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -16,6 +16,7 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import java.util.ArrayList; import java.util.ArrayList;
@@ -27,43 +28,51 @@ import java.util.List;
*/ */
public class MqttSubAckPayload { public class MqttSubAckPayload {
private final List<Integer> grantedQoSLevels; private final List<Integer> reasonCodes;
public MqttSubAckPayload(int... grantedQoSLevels) { public MqttSubAckPayload(int... reasonCodes) {
if (grantedQoSLevels == null) { ObjectUtil.checkNotNull(reasonCodes, "reasonCodes");
throw new NullPointerException("grantedQoSLevels");
}
List<Integer> list = new ArrayList<Integer>(grantedQoSLevels.length); List<Integer> list = new ArrayList<Integer>(reasonCodes.length);
for (int v: grantedQoSLevels) { for (int v: reasonCodes) {
list.add(v); list.add(v);
} }
this.grantedQoSLevels = Collections.unmodifiableList(list); this.reasonCodes = Collections.unmodifiableList(list);
} }
public MqttSubAckPayload(Iterable<Integer> grantedQoSLevels) { public MqttSubAckPayload(Iterable<Integer> reasonCodes) {
if (grantedQoSLevels == null) { ObjectUtil.checkNotNull(reasonCodes, "reasonCodes");
throw new NullPointerException("grantedQoSLevels");
}
List<Integer> list = new ArrayList<Integer>(); List<Integer> list = new ArrayList<Integer>();
for (Integer v: grantedQoSLevels) { for (Integer v: reasonCodes) {
if (v == null) { if (v == null) {
break; break;
} }
list.add(v); list.add(v);
} }
this.grantedQoSLevels = Collections.unmodifiableList(list); this.reasonCodes = Collections.unmodifiableList(list);
} }
public List<Integer> grantedQoSLevels() { public List<Integer> grantedQoSLevels() {
return grantedQoSLevels; List<Integer> qosLevels = new ArrayList<Integer>(reasonCodes.size());
for (int code: reasonCodes) {
if (code > MqttQoS.EXACTLY_ONCE.value()) {
qosLevels.add(MqttQoS.FAILURE.value());
} else {
qosLevels.add(code);
}
}
return qosLevels;
}
public List<Integer> reasonCodes() {
return reasonCodes;
} }
@Override @Override
public String toString() { public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
.append('[') .append('[')
.append("grantedQoSLevels=").append(grantedQoSLevels) .append("reasonCodes=").append(reasonCodes)
.append(']') .append(']')
.toString(); .toString();
} }
+14 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,23 +17,34 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#subscribe"> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#subscribe">
* MQTTV3.1/subscribe</a> * MQTTV3.1/subscribe</a>
*/ */
public final class MqttSubscribeMessage extends MqttMessage { public final class MqttSubscribeMessage extends MqttMessage {
public MqttSubscribeMessage( public MqttSubscribeMessage(
MqttFixedHeader mqttFixedHeader, MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader, MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttSubscribePayload payload) { MqttSubscribePayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
public MqttSubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttSubscribePayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override @Override
public MqttSubscribePayload payload() { public MqttSubscribePayload payload() {
return (MqttSubscribePayload) super.payload(); return (MqttSubscribePayload) super.payload();
+6 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -39,11 +39,12 @@ public final class MqttSubscribePayload {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('['); StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
for (int i = 0; i < topicSubscriptions.size() - 1; i++) { for (int i = 0; i < topicSubscriptions.size(); i++) {
builder.append(topicSubscriptions.get(i)).append(", "); builder.append(topicSubscriptions.get(i)).append(", ");
} }
builder.append(topicSubscriptions.get(topicSubscriptions.size() - 1)); if (!topicSubscriptions.isEmpty()) {
builder.append(']'); builder.setLength(builder.length() - 2);
return builder.toString(); }
return builder.append(']').toString();
} }
} }
@@ -0,0 +1,128 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
/**
* Model the SubscriptionOption used in Subscribe MQTT v5 packet
*/
public final class MqttSubscriptionOption {
public enum RetainedHandlingPolicy {
SEND_AT_SUBSCRIBE(0),
SEND_AT_SUBSCRIBE_IF_NOT_YET_EXISTS(1),
DONT_SEND_AT_SUBSCRIBE(2);
private final int value;
RetainedHandlingPolicy(int value) {
this.value = value;
}
public int value() {
return value;
}
public static RetainedHandlingPolicy valueOf(int value) {
switch (value) {
case 0:
return SEND_AT_SUBSCRIBE;
case 1:
return SEND_AT_SUBSCRIBE_IF_NOT_YET_EXISTS;
case 2:
return DONT_SEND_AT_SUBSCRIBE;
default:
throw new IllegalArgumentException("invalid RetainedHandlingPolicy: " + value);
}
}
}
private final MqttQoS qos;
private final boolean noLocal;
private final boolean retainAsPublished;
private final RetainedHandlingPolicy retainHandling;
public static MqttSubscriptionOption onlyFromQos(MqttQoS qos) {
return new MqttSubscriptionOption(qos, false, false, RetainedHandlingPolicy.SEND_AT_SUBSCRIBE);
}
public MqttSubscriptionOption(MqttQoS qos,
boolean noLocal,
boolean retainAsPublished,
RetainedHandlingPolicy retainHandling) {
this.qos = qos;
this.noLocal = noLocal;
this.retainAsPublished = retainAsPublished;
this.retainHandling = retainHandling;
}
public MqttQoS qos() {
return qos;
}
public boolean isNoLocal() {
return noLocal;
}
public boolean isRetainAsPublished() {
return retainAsPublished;
}
public RetainedHandlingPolicy retainHandling() {
return retainHandling;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MqttSubscriptionOption that = (MqttSubscriptionOption) o;
if (noLocal != that.noLocal) {
return false;
}
if (retainAsPublished != that.retainAsPublished) {
return false;
}
if (qos != that.qos) {
return false;
}
return retainHandling == that.retainHandling;
}
@Override
public int hashCode() {
int result = qos.hashCode();
result = 31 * result + (noLocal ? 1 : 0);
result = 31 * result + (retainAsPublished ? 1 : 0);
result = 31 * result + retainHandling.hashCode();
return result;
}
@Override
public String toString() {
return "SubscriptionOption[" +
"qos=" + qos +
", noLocal=" + noLocal +
", retainAsPublished=" + retainAsPublished +
", retainHandling=" + retainHandling +
']';
}
}
+14 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -25,11 +25,16 @@ import io.netty.util.internal.StringUtil;
public final class MqttTopicSubscription { public final class MqttTopicSubscription {
private final String topicFilter; private final String topicFilter;
private final MqttQoS qualityOfService; private final MqttSubscriptionOption option;
public MqttTopicSubscription(String topicFilter, MqttQoS qualityOfService) { public MqttTopicSubscription(String topicFilter, MqttQoS qualityOfService) {
this.topicFilter = topicFilter; this.topicFilter = topicFilter;
this.qualityOfService = qualityOfService; this.option = MqttSubscriptionOption.onlyFromQos(qualityOfService);
}
public MqttTopicSubscription(String topicFilter, MqttSubscriptionOption option) {
this.topicFilter = topicFilter;
this.option = option;
} }
public String topicName() { public String topicName() {
@@ -37,7 +42,11 @@ public final class MqttTopicSubscription {
} }
public MqttQoS qualityOfService() { public MqttQoS qualityOfService() {
return qualityOfService; return option.qos();
}
public MqttSubscriptionOption option() {
return option;
} }
@Override @Override
@@ -45,7 +54,7 @@ public final class MqttTopicSubscription {
return new StringBuilder(StringUtil.simpleClassName(this)) return new StringBuilder(StringUtil.simpleClassName(this))
.append('[') .append('[')
.append("topicFilter=").append(topicFilter) .append("topicFilter=").append(topicFilter)
.append(", qualityOfService=").append(qualityOfService) .append(", option=").append(this.option)
.append(']') .append(']')
.toString(); .toString();
} }
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+34 -4
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,16 +17,46 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsuback">MQTTV3.1/unsuback</a> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsuback">
* MQTTV3.1/unsuback</a>
*/ */
public final class MqttUnsubAckMessage extends MqttMessage { public final class MqttUnsubAckMessage extends MqttMessage {
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader, MqttMessageIdVariableHeader variableHeader) { public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
super(mqttFixedHeader, variableHeader, null); MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttUnsubAckPayload payload) {
super(mqttFixedHeader, variableHeader, MqttUnsubAckPayload.withEmptyDefaults(payload));
}
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttUnsubAckPayload payload) {
this(mqttFixedHeader, fallbackVariableHeader(variableHeader), payload);
}
public MqttUnsubAckMessage(MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader) {
this(mqttFixedHeader, variableHeader, null);
}
private static MqttMessageIdAndPropertiesVariableHeader fallbackVariableHeader(
MqttMessageIdVariableHeader variableHeader) {
if (variableHeader instanceof MqttMessageIdAndPropertiesVariableHeader) {
return (MqttMessageIdAndPropertiesVariableHeader) variableHeader;
}
return new MqttMessageIdAndPropertiesVariableHeader(variableHeader.messageId(), MqttProperties.NO_PROPERTIES);
} }
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override
public MqttUnsubAckPayload payload() {
return (MqttUnsubAckPayload) super.payload();
}
} }
@@ -0,0 +1,75 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.mqtt;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Payload for MQTT unsuback message as in V5.
*/
public final class MqttUnsubAckPayload {
private final List<Short> unsubscribeReasonCodes;
private static final MqttUnsubAckPayload EMPTY = new MqttUnsubAckPayload();
public static MqttUnsubAckPayload withEmptyDefaults(MqttUnsubAckPayload payload) {
if (payload == null) {
return EMPTY;
} else {
return payload;
}
}
public MqttUnsubAckPayload(short... unsubscribeReasonCodes) {
ObjectUtil.checkNotNull(unsubscribeReasonCodes, "unsubscribeReasonCodes");
List<Short> list = new ArrayList<Short>(unsubscribeReasonCodes.length);
for (Short v: unsubscribeReasonCodes) {
list.add(v);
}
this.unsubscribeReasonCodes = Collections.unmodifiableList(list);
}
public MqttUnsubAckPayload(Iterable<Short> unsubscribeReasonCodes) {
ObjectUtil.checkNotNull(unsubscribeReasonCodes, "unsubscribeReasonCodes");
List<Short> list = new ArrayList<Short>();
for (Short v: unsubscribeReasonCodes) {
ObjectUtil.checkNotNull(v, "unsubscribeReasonCode");
list.add(v);
}
this.unsubscribeReasonCodes = Collections.unmodifiableList(list);
}
public List<Short> unsubscribeReasonCodes() {
return unsubscribeReasonCodes;
}
@Override
public String toString() {
return new StringBuilder(StringUtil.simpleClassName(this))
.append('[')
.append("unsubscribeReasonCodes=").append(unsubscribeReasonCodes)
.append(']')
.toString();
}
}
+14 -3
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -17,23 +17,34 @@
package io.netty.handler.codec.mqtt; package io.netty.handler.codec.mqtt;
/** /**
* See <a href="http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsubscribe"> * See <a href="https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#unsubscribe">
* MQTTV3.1/unsubscribe</a> * MQTTV3.1/unsubscribe</a>
*/ */
public final class MqttUnsubscribeMessage extends MqttMessage { public final class MqttUnsubscribeMessage extends MqttMessage {
public MqttUnsubscribeMessage( public MqttUnsubscribeMessage(
MqttFixedHeader mqttFixedHeader, MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader, MqttMessageIdAndPropertiesVariableHeader variableHeader,
MqttUnsubscribePayload payload) { MqttUnsubscribePayload payload) {
super(mqttFixedHeader, variableHeader, payload); super(mqttFixedHeader, variableHeader, payload);
} }
public MqttUnsubscribeMessage(
MqttFixedHeader mqttFixedHeader,
MqttMessageIdVariableHeader variableHeader,
MqttUnsubscribePayload payload) {
this(mqttFixedHeader, variableHeader.withDefaultEmptyProperties(), payload);
}
@Override @Override
public MqttMessageIdVariableHeader variableHeader() { public MqttMessageIdVariableHeader variableHeader() {
return (MqttMessageIdVariableHeader) super.variableHeader(); return (MqttMessageIdVariableHeader) super.variableHeader();
} }
public MqttMessageIdAndPropertiesVariableHeader idAndPropertiesVariableHeader() {
return (MqttMessageIdAndPropertiesVariableHeader) super.variableHeader();
}
@Override @Override
public MqttUnsubscribePayload payload() { public MqttUnsubscribePayload payload() {
return (MqttUnsubscribePayload) super.payload(); return (MqttUnsubscribePayload) super.payload();
+6 -5
View File
@@ -5,7 +5,7 @@
* version 2.0 (the "License"); you may not use this file except in compliance * version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at: * with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
@@ -39,11 +39,12 @@ public final class MqttUnsubscribePayload {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('['); StringBuilder builder = new StringBuilder(StringUtil.simpleClassName(this)).append('[');
for (int i = 0; i < topics.size() - 1; i++) { for (int i = 0; i < topics.size(); i++) {
builder.append("topicName = ").append(topics.get(i)).append(", "); builder.append("topicName = ").append(topics.get(i)).append(", ");
} }
builder.append("topicName = ").append(topics.get(topics.size() - 1)) if (!topics.isEmpty()) {
.append(']'); builder.setLength(builder.length() - 2);
return builder.toString(); }
return builder.append("]").toString();
} }
} }
+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

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