@onekeyfe/react-native-jpush 0.0.0-stage → 6.7.0-22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -2
  3. package/ReactNativeJpush.podspec +66 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +144 -0
  6. package/android/fcm/build.gradle +36 -0
  7. package/android/fcm/consumer-rules.pro +2 -0
  8. package/android/fcm/src/main/AndroidManifest.xml +25 -0
  9. package/android/fcm/src/main/java/com/onekey/jpush/fcm/FcmInitProvider.java +25 -0
  10. package/android/fcm/src/main/java/com/onekey/jpush/fcm/FcmMessagingService.java +84 -0
  11. package/android/fcm/src/main/java/com/onekey/jpush/fcm/FcmPushManager.java +69 -0
  12. package/android/fcm/src/test/java/com/onekey/jpush/fcm/FcmAdapterTest.java +684 -0
  13. package/android/fcm/src/test/java/com/onekey/jpush/fcm/FcmManifestTest.java +54 -0
  14. package/android/gradle.properties +4 -0
  15. package/android/src/main/AndroidManifest.xml +143 -0
  16. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  17. package/android/src/main/java/cn/android/service/JTransitActivity.java +19 -0
  18. package/android/src/main/java/cn/jiguang/android/BuildConfig.java +10 -0
  19. package/android/src/main/java/cn/jiguang/android/IDataShare.java +190 -0
  20. package/android/src/main/java/cn/jiguang/api/BaseLogger.java +82 -0
  21. package/android/src/main/java/cn/jiguang/api/JAction.java +22 -0
  22. package/android/src/main/java/cn/jiguang/api/JActionExtra.java +21 -0
  23. package/android/src/main/java/cn/jiguang/api/JAnalyticsAction.java +11 -0
  24. package/android/src/main/java/cn/jiguang/api/JCoreInterface.java +228 -0
  25. package/android/src/main/java/cn/jiguang/api/JCoreManager.java +156 -0
  26. package/android/src/main/java/cn/jiguang/api/JDispatchAction.java +66 -0
  27. package/android/src/main/java/cn/jiguang/api/MultiSpHelper.java +114 -0
  28. package/android/src/main/java/cn/jiguang/api/ReportCallBack.java +9 -0
  29. package/android/src/main/java/cn/jiguang/api/SdkType.java +13 -0
  30. package/android/src/main/java/cn/jiguang/api/utils/JCollectionAuth.java +28 -0
  31. package/android/src/main/java/cn/jiguang/core/JCoreCrypto.java +545 -0
  32. package/android/src/main/java/cn/jiguang/core/JCoreStorage.java +737 -0
  33. package/android/src/main/java/cn/jiguang/core/JCoreTcpEngine.java +1395 -0
  34. package/android/src/main/java/cn/jiguang/core/JCoreWireCodec.java +1716 -0
  35. package/android/src/main/java/cn/jiguang/internal/ActionManager.java +63 -0
  36. package/android/src/main/java/cn/jiguang/internal/JConstants.java +61 -0
  37. package/android/src/main/java/cn/jiguang/internal/JCoreHelperAction.java +12 -0
  38. package/android/src/main/java/cn/jiguang/internal/JCoreInternalHelper.java +102 -0
  39. package/android/src/main/java/cn/jiguang/net/DefaultHostVerifier.java +40 -0
  40. package/android/src/main/java/cn/jiguang/net/HttpRequest.java +78 -0
  41. package/android/src/main/java/cn/jiguang/net/HttpResponse.java +48 -0
  42. package/android/src/main/java/cn/jiguang/net/HttpUtils.java +106 -0
  43. package/android/src/main/java/cn/jiguang/plugins/core/JCoreModule.java +91 -0
  44. package/android/src/main/java/cn/jiguang/plugins/core/JCorePackage.java +27 -0
  45. package/android/src/main/java/cn/jiguang/plugins/push/JPushModule.java +773 -0
  46. package/android/src/main/java/cn/jiguang/plugins/push/JPushPackage.java +25 -0
  47. package/android/src/main/java/cn/jiguang/plugins/push/common/JConstants.java +88 -0
  48. package/android/src/main/java/cn/jiguang/plugins/push/common/JLogger.java +122 -0
  49. package/android/src/main/java/cn/jiguang/plugins/push/helper/JPushHelper.java +710 -0
  50. package/android/src/main/java/cn/jiguang/plugins/push/receiver/JPushBroadcastReceiver.java +37 -0
  51. package/android/src/main/java/cn/jiguang/plugins/push/receiver/JPushModuleReceiver.java +264 -0
  52. package/android/src/main/java/cn/jiguang/plugins/service/JCoreModuleService.java +7 -0
  53. package/android/src/main/java/cn/jpush/android/api/BasicPushNotificationBuilder.java +83 -0
  54. package/android/src/main/java/cn/jpush/android/api/CallBackParams.java +50 -0
  55. package/android/src/main/java/cn/jpush/android/api/CmdMessage.java +33 -0
  56. package/android/src/main/java/cn/jpush/android/api/CustomMessage.java +28 -0
  57. package/android/src/main/java/cn/jpush/android/api/CustomPushNotificationBuilder.java +81 -0
  58. package/android/src/main/java/cn/jpush/android/api/DefaultPushNotificationBuilder.java +90 -0
  59. package/android/src/main/java/cn/jpush/android/api/InAppSlotParams.java +65 -0
  60. package/android/src/main/java/cn/jpush/android/api/InstrumentedActivity.java +17 -0
  61. package/android/src/main/java/cn/jpush/android/api/InstrumentedListActivity.java +17 -0
  62. package/android/src/main/java/cn/jpush/android/api/JPushInterface.java +490 -0
  63. package/android/src/main/java/cn/jpush/android/api/JPushMessage.java +121 -0
  64. package/android/src/main/java/cn/jpush/android/api/JThirdPlatFormInterface.java +247 -0
  65. package/android/src/main/java/cn/jpush/android/api/MultiActionsNotificationBuilder.java +72 -0
  66. package/android/src/main/java/cn/jpush/android/api/NotificationCustomButton.java +50 -0
  67. package/android/src/main/java/cn/jpush/android/api/NotificationMessage.java +67 -0
  68. package/android/src/main/java/cn/jpush/android/api/PushNotificationBuilder.java +13 -0
  69. package/android/src/main/java/cn/jpush/android/api/TagAliasCallback.java +8 -0
  70. package/android/src/main/java/cn/jpush/android/data/JPushCollectControl.java +96 -0
  71. package/android/src/main/java/cn/jpush/android/data/JPushConfig.java +79 -0
  72. package/android/src/main/java/cn/jpush/android/data/JPushConfigEx.java +41 -0
  73. package/android/src/main/java/cn/jpush/android/data/JPushLocalNotification.java +155 -0
  74. package/android/src/main/java/cn/jpush/android/helper/JCoreHelper.java +70 -0
  75. package/android/src/main/java/cn/jpush/android/helper/Logger.java +82 -0
  76. package/android/src/main/java/cn/jpush/android/local/ActionHelper.java +1293 -0
  77. package/android/src/main/java/cn/jpush/android/local/ActivityLifeCallBack.java +47 -0
  78. package/android/src/main/java/cn/jpush/android/local/JPushAction.java +67 -0
  79. package/android/src/main/java/cn/jpush/android/local/JPushConstants.java +34 -0
  80. package/android/src/main/java/cn/jpush/android/local/JPushResponse.java +26 -0
  81. package/android/src/main/java/cn/jpush/android/local/ProxyActivityAction.java +34 -0
  82. package/android/src/main/java/cn/jpush/android/service/AlarmReceiver.java +15 -0
  83. package/android/src/main/java/cn/jpush/android/service/BActivity.java +19 -0
  84. package/android/src/main/java/cn/jpush/android/service/DActivity.java +19 -0
  85. package/android/src/main/java/cn/jpush/android/service/DaemonService.java +17 -0
  86. package/android/src/main/java/cn/jpush/android/service/DataProvider.java +136 -0
  87. package/android/src/main/java/cn/jpush/android/service/DataShare.java +129 -0
  88. package/android/src/main/java/cn/jpush/android/service/DownloadProvider.java +139 -0
  89. package/android/src/main/java/cn/jpush/android/service/InitProvider.java +48 -0
  90. package/android/src/main/java/cn/jpush/android/service/JCommonService.java +79 -0
  91. package/android/src/main/java/cn/jpush/android/service/JNotifyActivity.java +28 -0
  92. package/android/src/main/java/cn/jpush/android/service/JPushMessageReceiver.java +94 -0
  93. package/android/src/main/java/cn/jpush/android/service/PushReceiver.java +26 -0
  94. package/android/src/main/java/cn/jpush/android/service/PushService.java +28 -0
  95. package/android/src/main/java/cn/jpush/android/service/SchedulerReceiver.java +17 -0
  96. package/android/src/main/java/cn/jpush/android/service/TagAliasReceiver.java +11 -0
  97. package/android/src/main/java/cn/jpush/android/service/WakedResultReceiver.java +24 -0
  98. package/android/src/main/java/cn/jpush/android/ui/FullScreenView.java +11 -0
  99. package/android/src/main/java/cn/jpush/android/ui/PopWinActivity.java +16 -0
  100. package/android/src/main/java/cn/jpush/android/ui/PushActivity.java +27 -0
  101. package/android/src/main/java/cn/jpush/android/ui/RoundedImageView.java +63 -0
  102. package/android/src/main/java/cn/jpush/android/ui/ShadowViewCard.java +56 -0
  103. package/android/src/main/java/cn/jpush/android/ups/ICallbackResult.java +5 -0
  104. package/android/src/main/java/cn/jpush/android/ups/JPushUPSManager.java +37 -0
  105. package/android/src/main/java/cn/jpush/android/ups/TokenResult.java +47 -0
  106. package/android/src/main/java/cn/jpush/android/ups/UPSRegisterCallBack.java +4 -0
  107. package/android/src/main/java/cn/jpush/android/ups/UPSTurnCallBack.java +4 -0
  108. package/android/src/main/java/cn/jpush/android/ups/UPSUnRegisterCallBack.java +4 -0
  109. package/android/src/main/java/com/margelo/nitro/reactnativejpush/ReactNativeJpush.kt +455 -0
  110. package/android/src/main/java/com/margelo/nitro/reactnativejpush/ReactNativeJpushPackage.kt +50 -0
  111. package/android/src/main/res/values/jpush_string.xml +7 -0
  112. package/android/src/main/res/values/jpush_style.xml +22 -0
  113. package/android/src/test/java/com/margelo/nitro/reactnativejpush/JPushCloudHandshakeTest.java +110 -0
  114. package/android/src/test/java/com/margelo/nitro/reactnativejpush/JPushHandshakeFailureTest.java +73 -0
  115. package/android/src/test/java/com/margelo/nitro/reactnativejpush/ReactNativeJpushProtocolTest.java +908 -0
  116. package/ios/ReactNativeJpush.swift +522 -0
  117. package/ios/bridge/RCTJCoreModule.h +5 -0
  118. package/ios/bridge/RCTJCoreModule.m +31 -0
  119. package/ios/bridge/RCTJPushEventQueue.h +14 -0
  120. package/ios/bridge/RCTJPushEventQueue.m +23 -0
  121. package/ios/bridge/RCTJPushModule.h +37 -0
  122. package/ios/bridge/RCTJPushModule.m +1165 -0
  123. package/ios/include/JCOREService.h +3 -0
  124. package/ios/include/JGCOREAPI.h +14 -0
  125. package/ios/include/JGInforCollectionAuth.h +17 -0
  126. package/ios/include/JGInforCollectionAuthItems.h +2 -0
  127. package/ios/include/JGPushNotificationTrigger.h +2 -0
  128. package/ios/include/JPUSHService.h +359 -0
  129. package/ios/jcore/JCOREConfiguration.h +141 -0
  130. package/ios/jcore/JCOREConfiguration.m +703 -0
  131. package/ios/jcore/JCORECrypto.h +73 -0
  132. package/ios/jcore/JCORECrypto.m +649 -0
  133. package/ios/jcore/JCORESisResolver.h +56 -0
  134. package/ios/jcore/JCORESisResolver.m +525 -0
  135. package/ios/jcore/JCORETcpSession.h +42 -0
  136. package/ios/jcore/JCORETcpSession.m +956 -0
  137. package/ios/jcore/JCOREWireProtocol.h +274 -0
  138. package/ios/jcore/JCOREWireProtocol.m +1453 -0
  139. package/ios/jpush/JPUSHModels.m +214 -0
  140. package/ios/jpush/JPUSHNotificationManager.h +34 -0
  141. package/ios/jpush/JPUSHNotificationManager.m +360 -0
  142. package/ios/jpush/JPUSHService.m +794 -0
  143. package/ios/tests/ReactNativeJpushTests.m +148 -0
  144. package/lib/module/ReactNativeJpush.nitro.js +4 -0
  145. package/lib/module/ReactNativeJpush.nitro.js.map +1 -0
  146. package/lib/module/index.js +801 -0
  147. package/lib/module/index.js.map +1 -0
  148. package/lib/module/package.json +1 -0
  149. package/lib/typescript/package.json +1 -0
  150. package/lib/typescript/src/ReactNativeJpush.nitro.d.ts +60 -0
  151. package/lib/typescript/src/ReactNativeJpush.nitro.d.ts.map +1 -0
  152. package/lib/typescript/src/index.d.ts +235 -0
  153. package/lib/typescript/src/index.d.ts.map +1 -0
  154. package/nitro.json +23 -0
  155. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  156. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__string.hpp +76 -0
  157. package/nitrogen/generated/android/c++/JHybridReactNativeJpushSpec.cpp +310 -0
  158. package/nitrogen/generated/android/c++/JHybridReactNativeJpushSpec.hpp +115 -0
  159. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/Func_void_bool.kt +78 -0
  160. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/Func_void_std__string_std__string.kt +78 -0
  161. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/HybridReactNativeJpushSpec.kt +274 -0
  162. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/reactnativejpushOnLoad.kt +35 -0
  163. package/nitrogen/generated/android/reactnativejpush+autolinking.cmake +81 -0
  164. package/nitrogen/generated/android/reactnativejpush+autolinking.gradle +27 -0
  165. package/nitrogen/generated/android/reactnativejpushOnLoad.cpp +58 -0
  166. package/nitrogen/generated/android/reactnativejpushOnLoad.hpp +34 -0
  167. package/nitrogen/generated/ios/ReactNativeJpush+autolinking.rb +62 -0
  168. package/nitrogen/generated/ios/ReactNativeJpush-Swift-Cxx-Bridge.cpp +49 -0
  169. package/nitrogen/generated/ios/ReactNativeJpush-Swift-Cxx-Bridge.hpp +147 -0
  170. package/nitrogen/generated/ios/ReactNativeJpush-Swift-Cxx-Umbrella.hpp +46 -0
  171. package/nitrogen/generated/ios/ReactNativeJpushAutolinking.mm +33 -0
  172. package/nitrogen/generated/ios/ReactNativeJpushAutolinking.swift +26 -0
  173. package/nitrogen/generated/ios/c++/HybridReactNativeJpushSpecSwift.cpp +11 -0
  174. package/nitrogen/generated/ios/c++/HybridReactNativeJpushSpecSwift.hpp +402 -0
  175. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  176. package/nitrogen/generated/ios/swift/Func_void_std__string_std__string.swift +46 -0
  177. package/nitrogen/generated/ios/swift/HybridReactNativeJpushSpec.swift +107 -0
  178. package/nitrogen/generated/ios/swift/HybridReactNativeJpushSpec_cxx.swift +723 -0
  179. package/nitrogen/generated/shared/c++/HybridReactNativeJpushSpec.cpp +73 -0
  180. package/nitrogen/generated/shared/c++/HybridReactNativeJpushSpec.hpp +116 -0
  181. package/package.json +174 -4
  182. package/src/ReactNativeJpush.nitro.ts +93 -0
  183. package/src/index.tsx +1090 -0
@@ -0,0 +1,1716 @@
1
+ package cn.jiguang.core;
2
+
3
+ import android.os.Build;
4
+ import java.io.ByteArrayOutputStream;
5
+ import java.io.EOFException;
6
+ import java.io.IOException;
7
+ import java.io.InputStream;
8
+ import java.nio.ByteBuffer;
9
+ import java.nio.charset.StandardCharsets;
10
+ import java.util.ArrayList;
11
+ import java.util.Collection;
12
+ import java.util.Collections;
13
+ import java.util.LinkedHashMap;
14
+ import java.util.LinkedHashSet;
15
+ import java.util.List;
16
+ import java.util.Map;
17
+ import java.util.Set;
18
+
19
+ public final class JCoreWireCodec {
20
+ public static final int CMD_REGISTER = 0;
21
+ public static final int CMD_LOGIN = 1;
22
+ public static final int CMD_HEARTBEAT = 2;
23
+ public static final int CMD_PUSH_MESSAGE = 3;
24
+ public static final int CMD_MESSAGE_ACK = 4;
25
+ public static final int CMD_DEVICE_TOKEN = 13;
26
+ public static final int CMD_CTRL = 25;
27
+ public static final int CMD_MOBILE_NUMBER = 26;
28
+ public static final int CMD_THIRD_TOKEN = 27;
29
+ public static final int CMD_TAG_OP = 28;
30
+ public static final int CMD_ALIAS_OP = 29;
31
+ public static final int CMD_PROPERTY_OP = 38;
32
+ public static final int CMD_PUSH_CTRL = 40;
33
+
34
+ public static final int VER_REGISTER = 29;
35
+ public static final int VER_LOGIN = 27;
36
+ public static final int VER_HEARTBEAT = 8;
37
+ public static final int VER_PUSH_MESSAGE = 6;
38
+ public static final int VER_MESSAGE_ACK = 3;
39
+ public static final int VER_DEVICE_TOKEN = 3;
40
+ public static final int VER_CTRL = 1;
41
+ public static final int VER_MOBILE_NUMBER = 1;
42
+ public static final int VER_THIRD_TOKEN = 2;
43
+ public static final int VER_TAG_OP = 1;
44
+ public static final int VER_ALIAS_OP = 1;
45
+ public static final int VER_PROPERTY_OP = 1;
46
+ public static final int VER_PUSH_CTRL = 1;
47
+
48
+ public static final int ALG_AES_CBC = 1;
49
+ public static final int ALG_SM4_CBC = 2;
50
+
51
+ public static final byte SIS_MAGIC_U = (byte) 'U';
52
+ public static final byte SIS_MAGIC_M = (byte) 'M';
53
+ public static final byte SIS_MAGIC_G = (byte) 'G';
54
+
55
+ public static final int CLIENT_HEADER_BYTES = 24;
56
+ public static final int SERVER_HEADER_BYTES = 20;
57
+
58
+ private static volatile long lastRegisterSeed = 1L;
59
+ private static volatile int defaultAlgorithm = ALG_AES_CBC;
60
+
61
+ private JCoreWireCodec() {
62
+ }
63
+
64
+ public static long getLastRegisterSeed() {
65
+ return lastRegisterSeed;
66
+ }
67
+
68
+ public static void setLastRegisterSeed(long seed) {
69
+ if (seed > 0L) {
70
+ lastRegisterSeed = seed;
71
+ }
72
+ }
73
+
74
+ public static int getDefaultAlgorithm() {
75
+ return defaultAlgorithm;
76
+ }
77
+
78
+ public static void setDefaultAlgorithm(int algorithm) {
79
+ defaultAlgorithm = (algorithm == ALG_SM4_CBC) ? ALG_SM4_CBC : ALG_AES_CBC;
80
+ }
81
+
82
+ public static final class DecodedFrame {
83
+ public final int version;
84
+ public final int command;
85
+ public final int algorithm;
86
+ public final int flag;
87
+ public final long requestId;
88
+ public final int sessionId;
89
+ public final long uid;
90
+ public final int statusCode;
91
+ public final byte[] body;
92
+
93
+ public DecodedFrame(int version, int command, long requestId, long uid, int statusCode, byte[] body) {
94
+ this(version, command, ALG_AES_CBC, 0, requestId, 0, uid, statusCode, body);
95
+ }
96
+
97
+ public DecodedFrame(
98
+ int version,
99
+ int command,
100
+ long requestId,
101
+ int sessionId,
102
+ long uid,
103
+ int statusCode,
104
+ byte[] body
105
+ ) {
106
+ this(version, command, ALG_AES_CBC, 0, requestId, sessionId, uid, statusCode, body);
107
+ }
108
+
109
+ public DecodedFrame(
110
+ int version,
111
+ int command,
112
+ int algorithm,
113
+ int flag,
114
+ long requestId,
115
+ int sessionId,
116
+ long uid,
117
+ int statusCode,
118
+ byte[] body
119
+ ) {
120
+ this.version = version;
121
+ this.command = command;
122
+ this.algorithm = algorithm;
123
+ this.flag = flag;
124
+ this.requestId = requestId;
125
+ this.sessionId = sessionId;
126
+ this.uid = uid;
127
+ this.statusCode = statusCode;
128
+ this.body = body != null ? body : new byte[0];
129
+ }
130
+ }
131
+
132
+ public static final class RegisterRequest {
133
+ public final String key;
134
+ public final String apkVersion;
135
+ public final String clientInfo;
136
+ public final int platform;
137
+ public final String extKey;
138
+ public final int regBusiness;
139
+ public final String accountId;
140
+ public final String packageName;
141
+ public final String appKey;
142
+ public final String channel;
143
+ public final String deviceId;
144
+
145
+ public RegisterRequest(
146
+ String key,
147
+ String apkVersion,
148
+ String clientInfo,
149
+ int platform,
150
+ String extKey,
151
+ int regBusiness,
152
+ String accountId,
153
+ String packageName,
154
+ String appKey,
155
+ String channel,
156
+ String deviceId
157
+ ) {
158
+ this.key = key != null ? key : "";
159
+ this.apkVersion = apkVersion != null ? apkVersion : "";
160
+ this.clientInfo = clientInfo != null ? clientInfo : "";
161
+ this.platform = platform;
162
+ this.extKey = extKey != null ? extKey : "";
163
+ this.regBusiness = regBusiness;
164
+ this.accountId = accountId != null ? accountId : "";
165
+ this.packageName = packageName != null ? packageName : "";
166
+ this.appKey = appKey != null ? appKey : "";
167
+ this.channel = channel != null ? channel : "";
168
+ this.deviceId = deviceId != null ? deviceId : "";
169
+ }
170
+ }
171
+
172
+ public static final class RegisterResponse {
173
+ public final int statusCode;
174
+ public final long uid;
175
+ public final String password;
176
+ public final String registrationId;
177
+ public final String deviceId;
178
+
179
+ public RegisterResponse(int statusCode, long uid, String password, String registrationId) {
180
+ this(statusCode, uid, password, registrationId, "");
181
+ }
182
+
183
+ public RegisterResponse(int statusCode, long uid, String password, String registrationId, String deviceId) {
184
+ this.statusCode = statusCode;
185
+ this.uid = uid;
186
+ this.password = password != null ? password : "";
187
+ this.registrationId = registrationId != null ? registrationId : "";
188
+ this.deviceId = deviceId != null ? deviceId : "";
189
+ }
190
+ }
191
+
192
+ public static final class LoginRequest {
193
+ public final String passwordMd5;
194
+ public final String sdkVerInfo;
195
+ public final String appKey;
196
+ public final int platform;
197
+ public final int loginFlag;
198
+ public final int netType;
199
+ public final String deviceId;
200
+ public final String countryCode;
201
+ public final int initPushSdk;
202
+ public final String gSdkTypeInfo;
203
+
204
+ public LoginRequest(
205
+ String passwordMd5,
206
+ String sdkVerInfo,
207
+ String appKey,
208
+ int platform,
209
+ int loginFlag,
210
+ int netType,
211
+ String deviceId,
212
+ String countryCode,
213
+ int initPushSdk,
214
+ String gSdkTypeInfo
215
+ ) {
216
+ this.passwordMd5 = passwordMd5 != null ? passwordMd5 : "";
217
+ this.sdkVerInfo = sdkVerInfo != null ? sdkVerInfo : "";
218
+ this.appKey = appKey != null ? appKey : "";
219
+ this.platform = platform;
220
+ this.loginFlag = loginFlag;
221
+ this.netType = netType;
222
+ this.deviceId = deviceId != null ? deviceId : "";
223
+ this.countryCode = countryCode != null ? countryCode : "";
224
+ this.initPushSdk = initPushSdk;
225
+ this.gSdkTypeInfo = gSdkTypeInfo != null ? gSdkTypeInfo : "";
226
+ }
227
+ }
228
+
229
+ public static final class LoginResponse {
230
+ public final int statusCode;
231
+ public final int sessionId;
232
+ public final int serverVersion;
233
+ public final String errorDesc;
234
+ public final int serverTimestamp;
235
+
236
+ public LoginResponse(int statusCode, int sessionId, int serverVersion, int serverTimestamp) {
237
+ this(statusCode, sessionId, serverVersion, "", serverTimestamp);
238
+ }
239
+
240
+ public LoginResponse(
241
+ int statusCode,
242
+ int sessionId,
243
+ int serverVersion,
244
+ String errorDesc,
245
+ int serverTimestamp
246
+ ) {
247
+ this.statusCode = statusCode;
248
+ this.sessionId = sessionId;
249
+ this.serverVersion = serverVersion;
250
+ this.errorDesc = errorDesc != null ? errorDesc : "";
251
+ this.serverTimestamp = serverTimestamp;
252
+ }
253
+ }
254
+
255
+ public static final class HeartbeatRequest {
256
+ public final int loginFlag;
257
+ public final int appState;
258
+ public final int intervalSeconds;
259
+ public final int sdkType;
260
+
261
+ public HeartbeatRequest(int loginFlag, int appState, int intervalSeconds, int sdkType) {
262
+ this.loginFlag = loginFlag;
263
+ this.appState = appState;
264
+ this.intervalSeconds = intervalSeconds;
265
+ this.sdkType = sdkType;
266
+ }
267
+ }
268
+
269
+ public static final class PushEnvelope {
270
+ public final byte messageType;
271
+ public final long messageId;
272
+ public final String content;
273
+
274
+ public PushEnvelope(byte messageType, long messageId, String content) {
275
+ this.messageType = messageType;
276
+ this.messageId = messageId;
277
+ this.content = content != null ? content : "";
278
+ }
279
+ }
280
+
281
+ public static final class MessageAck {
282
+ public final int statusCode;
283
+ public final byte messageType;
284
+ public final long messageId;
285
+ public final String extData;
286
+
287
+ public MessageAck(byte messageType, long messageId) {
288
+ this(0, messageType, messageId, "");
289
+ }
290
+
291
+ public MessageAck(int statusCode, byte messageType, long messageId, String extData) {
292
+ this.statusCode = statusCode;
293
+ this.messageType = messageType;
294
+ this.messageId = messageId;
295
+ this.extData = extData != null ? extData : "";
296
+ }
297
+ }
298
+
299
+ public static final class CtrlCommand {
300
+ public final long requestId;
301
+ public final String jsonContent;
302
+
303
+ public CtrlCommand(long requestId, String jsonContent) {
304
+ this.requestId = requestId;
305
+ this.jsonContent = jsonContent != null ? jsonContent : "";
306
+ }
307
+ }
308
+
309
+ public static final class CtrlAck {
310
+ public final int statusCode;
311
+ public final long requestId;
312
+
313
+ public CtrlAck(int statusCode, long requestId) {
314
+ this.statusCode = statusCode;
315
+ this.requestId = requestId;
316
+ }
317
+ }
318
+
319
+ public static final class DeviceTokenPayload {
320
+ public final String token;
321
+ public final byte platform;
322
+ public final boolean unbind;
323
+
324
+ public DeviceTokenPayload(String token, byte platform) {
325
+ this(token, platform, false);
326
+ }
327
+
328
+ public DeviceTokenPayload(String token, byte platform, boolean unbind) {
329
+ this.token = token != null ? token : "";
330
+ this.platform = platform;
331
+ this.unbind = unbind;
332
+ }
333
+ }
334
+
335
+ public static final class MobileNumberPayload {
336
+ public final int typeFlag;
337
+ public final int opFlag;
338
+ public final int sequence;
339
+ public final String mobileNumber;
340
+
341
+ public MobileNumberPayload(int sequence, String mobileNumber) {
342
+ this(7, 1, sequence, mobileNumber);
343
+ }
344
+
345
+ public MobileNumberPayload(int typeFlag, int opFlag, int sequence, String mobileNumber) {
346
+ this.typeFlag = typeFlag;
347
+ this.opFlag = opFlag;
348
+ this.sequence = sequence;
349
+ this.mobileNumber = mobileNumber != null ? mobileNumber : "";
350
+ }
351
+ }
352
+
353
+ public static final class OperationPayload {
354
+ public final int command;
355
+ public final int action;
356
+ public final String op;
357
+ public final int sequence;
358
+ public final String alias;
359
+ public final Set<String> tags;
360
+ public final Map<String, String> properties;
361
+ public final String rawJson;
362
+
363
+ public OperationPayload(
364
+ int command,
365
+ int action,
366
+ String op,
367
+ int sequence,
368
+ String alias,
369
+ Set<String> tags,
370
+ Map<String, String> properties,
371
+ String rawJson
372
+ ) {
373
+ this.command = command;
374
+ this.action = action;
375
+ this.op = op != null ? op : "";
376
+ this.sequence = sequence;
377
+ this.alias = alias != null ? alias : "";
378
+ this.tags = tags != null ? tags : Collections.<String>emptySet();
379
+ this.properties = properties != null ? properties : Collections.<String, String>emptyMap();
380
+ this.rawJson = rawJson != null ? rawJson : "";
381
+ }
382
+ }
383
+
384
+ private static boolean hasResponseStatusPrefix(int command) {
385
+ return command == CMD_REGISTER
386
+ || command == CMD_LOGIN
387
+ || command == CMD_HEARTBEAT
388
+ || command == 10
389
+ || command == 19
390
+ || command == CMD_MOBILE_NUMBER
391
+ || command == CMD_THIRD_TOKEN
392
+ || command == CMD_PROPERTY_OP;
393
+ }
394
+
395
+ private static byte[] encryptPayload(byte[] plainBytes, int algorithm, int flag, long seed) {
396
+ if (plainBytes == null) {
397
+ plainBytes = new byte[0];
398
+ }
399
+ byte[] source = ((flag & 1) != 0 && plainBytes.length > 0)
400
+ ? JCoreCrypto.gzipCompress(plainBytes)
401
+ : plainBytes;
402
+ if (algorithm == ALG_SM4_CBC) {
403
+ return JCoreCrypto.sm4CbcSeedEncrypt(source, seed);
404
+ }
405
+ return JCoreCrypto.aes256CbcSeedEncrypt(source, seed);
406
+ }
407
+
408
+ private static byte[] decryptPayload(byte[] cipherBytes, int algorithm, int flag, long seed) {
409
+ if (cipherBytes == null || cipherBytes.length == 0) {
410
+ return new byte[0];
411
+ }
412
+ byte[] decrypted = (algorithm == ALG_SM4_CBC)
413
+ ? JCoreCrypto.sm4CbcSeedDecrypt(cipherBytes, seed)
414
+ : JCoreCrypto.aes256CbcSeedDecrypt(cipherBytes, seed);
415
+ if (decrypted.length > 0 && (flag & 1) != 0) {
416
+ decrypted = JCoreCrypto.gzipDecompress(decrypted);
417
+ }
418
+ return decrypted;
419
+ }
420
+
421
+ /**
422
+ * Encodes an outbound Client -> Server 24-byte header TCP packet.
423
+ */
424
+ public static byte[] encodeFrame(int command, int version, long requestId, int sessionId, long uid, byte[] body) {
425
+ return encodeClientFrame(command, version, defaultAlgorithm, 0, requestId, sessionId, uid, body);
426
+ }
427
+
428
+ public static byte[] encodeClientFrame(
429
+ int command,
430
+ int version,
431
+ int algorithm,
432
+ int flag,
433
+ long requestId,
434
+ int seedOrSessionId,
435
+ long uid,
436
+ byte[] body
437
+ ) {
438
+ int effectiveAlg = (algorithm == ALG_SM4_CBC) ? ALG_SM4_CBC : ALG_AES_CBC;
439
+ int headerSeedOrSid = seedOrSessionId;
440
+ long effectiveUid = uid;
441
+ long cryptoSeed;
442
+ if (command == CMD_REGISTER) {
443
+ int regSeed = seedOrSessionId > 0 ? seedOrSessionId : JCoreCrypto.nextRegisterSeed();
444
+ lastRegisterSeed = regSeed & 0xFFFFFFFFL;
445
+ headerSeedOrSid = regSeed;
446
+ effectiveUid = 0L;
447
+ cryptoSeed = lastRegisterSeed;
448
+ } else {
449
+ cryptoSeed = (uid > 0L) ? uid : lastRegisterSeed;
450
+ }
451
+
452
+ byte[] payload = body != null ? body : new byte[0];
453
+ byte[] encryptedBody = encryptPayload(payload, effectiveAlg, flag, cryptoSeed);
454
+ int totalLen = CLIENT_HEADER_BYTES + encryptedBody.length;
455
+
456
+ ByteBuffer buffer = ByteBuffer.allocate(totalLen);
457
+ buffer.putShort((short) ((totalLen & 0x7FFF) | 0x8000));
458
+ buffer.put((byte) (version & 0xFF));
459
+ buffer.put((byte) (command & 0xFF));
460
+ buffer.put((byte) (effectiveAlg & 0xFF));
461
+ buffer.put((byte) (flag & 0xFF));
462
+ buffer.putInt(0); // reserved
463
+ buffer.putShort((short) (requestId & 0xFFFF));
464
+ buffer.putInt(headerSeedOrSid);
465
+ buffer.putLong(effectiveUid);
466
+ if (encryptedBody.length > 0) {
467
+ buffer.put(encryptedBody);
468
+ }
469
+ return buffer.array();
470
+ }
471
+
472
+ /**
473
+ * Decodes a 24-byte Client -> Server frame (used by loopback/mock servers and unit tests).
474
+ */
475
+ public static DecodedFrame decodeClientFrame(byte[] rawFrame, long defaultUid) {
476
+ if (rawFrame == null || rawFrame.length < CLIENT_HEADER_BYTES) {
477
+ return null;
478
+ }
479
+ ByteBuffer buf = ByteBuffer.wrap(rawFrame);
480
+ int rawLen = buf.getShort() & 0xFFFF;
481
+ int totalLen = rawLen & 0x7FFF;
482
+ boolean encrypted = (rawLen & 0x8000) != 0;
483
+ if (totalLen < CLIENT_HEADER_BYTES || totalLen > rawFrame.length) {
484
+ totalLen = rawFrame.length;
485
+ }
486
+ int version = buf.get() & 0xFF;
487
+ int command = buf.get() & 0xFF;
488
+ int algorithm = buf.get() & 0xFF;
489
+ int flag = buf.get() & 0xFF;
490
+ buf.getInt(); // reserved
491
+ long rid = buf.getShort() & 0xFFFFL;
492
+ int seedOrSid = buf.getInt();
493
+ long uid = buf.getLong();
494
+
495
+ byte[] body = new byte[0];
496
+ if (totalLen > CLIENT_HEADER_BYTES) {
497
+ body = new byte[totalLen - CLIENT_HEADER_BYTES];
498
+ System.arraycopy(rawFrame, CLIENT_HEADER_BYTES, body, 0, body.length);
499
+ }
500
+ if (encrypted && body.length > 0) {
501
+ long seed;
502
+ if (command == CMD_REGISTER) {
503
+ seed = seedOrSid & 0xFFFFFFFFL;
504
+ if (seed > 0L) {
505
+ lastRegisterSeed = seed;
506
+ }
507
+ } else if (uid > 0L) {
508
+ seed = uid;
509
+ } else if (defaultUid > 0L) {
510
+ seed = defaultUid;
511
+ } else {
512
+ seed = lastRegisterSeed;
513
+ }
514
+ body = decryptPayload(body, algorithm, flag, seed);
515
+ }
516
+ return new DecodedFrame(version, command, algorithm, flag, rid, seedOrSid, uid, 0, body);
517
+ }
518
+
519
+ public static DecodedFrame readClientFrameFromStream(InputStream in, long defaultUid) throws IOException {
520
+ byte[] fullFrame = readRawPacketBytes(in, CLIENT_HEADER_BYTES);
521
+ if (fullFrame == null) {
522
+ return null;
523
+ }
524
+ return decodeClientFrame(fullFrame, defaultUid);
525
+ }
526
+
527
+ /**
528
+ * Encodes an inbound Server -> Client 20-byte header TCP packet.
529
+ */
530
+ public static byte[] encodeServerFrame(
531
+ int command,
532
+ int version,
533
+ int algorithm,
534
+ int flag,
535
+ long requestId,
536
+ long uid,
537
+ long registerSeed,
538
+ byte[] body
539
+ ) {
540
+ int effectiveAlg = (algorithm == ALG_SM4_CBC) ? ALG_SM4_CBC : ALG_AES_CBC;
541
+ long seed;
542
+ if (command == CMD_REGISTER) {
543
+ seed = registerSeed > 0L ? registerSeed : lastRegisterSeed;
544
+ } else {
545
+ seed = uid > 0L ? uid : (registerSeed > 0L ? registerSeed : lastRegisterSeed);
546
+ }
547
+ byte[] payload = body != null ? body : new byte[0];
548
+ byte[] encryptedBody = encryptPayload(payload, effectiveAlg, flag, seed);
549
+ int totalLen = SERVER_HEADER_BYTES + encryptedBody.length;
550
+
551
+ ByteBuffer buffer = ByteBuffer.allocate(totalLen);
552
+ buffer.putShort((short) ((totalLen & 0x7FFF) | 0x8000));
553
+ buffer.put((byte) (version & 0xFF));
554
+ buffer.put((byte) (command & 0xFF));
555
+ buffer.put((byte) (effectiveAlg & 0xFF));
556
+ buffer.put((byte) (flag & 0xFF));
557
+ buffer.putInt(0); // reserved
558
+ buffer.putShort((short) (requestId & 0xFFFF));
559
+ buffer.putLong(uid);
560
+ if (encryptedBody.length > 0) {
561
+ buffer.put(encryptedBody);
562
+ }
563
+ return buffer.array();
564
+ }
565
+
566
+ public static byte[] encodeResponseFrame(
567
+ int command,
568
+ int version,
569
+ long requestId,
570
+ int sessionId,
571
+ long uid,
572
+ int statusCode,
573
+ byte[] body
574
+ ) {
575
+ return encodeResponseFrame(command, version, defaultAlgorithm, 0, requestId, sessionId, uid, statusCode, body);
576
+ }
577
+
578
+ public static byte[] encodeResponseFrame(
579
+ int command,
580
+ int version,
581
+ int algorithm,
582
+ int flag,
583
+ long requestId,
584
+ int sessionId,
585
+ long uid,
586
+ int statusCode,
587
+ byte[] body
588
+ ) {
589
+ byte[] raw = body != null ? body : new byte[0];
590
+ byte[] payload;
591
+ if (hasResponseStatusPrefix(command)) {
592
+ ByteBuffer buf = ByteBuffer.allocate(2 + raw.length);
593
+ buf.putShort((short) (statusCode & 0xFFFF));
594
+ if (raw.length > 0) {
595
+ buf.put(raw);
596
+ }
597
+ payload = buf.array();
598
+ } else {
599
+ payload = raw;
600
+ }
601
+ long regSeed = sessionId > 0 ? (sessionId & 0xFFFFFFFFL) : lastRegisterSeed;
602
+ return encodeServerFrame(command, version, algorithm, flag, requestId, uid, regSeed, payload);
603
+ }
604
+
605
+ /**
606
+ * Decodes an inbound 20-byte Server -> Client TCP packet (or falls back to 24-byte Client frame if needed).
607
+ */
608
+ public static DecodedFrame decodeFrame(byte[] rawFrame, long defaultUid) {
609
+ return decodeServerFrame(rawFrame, defaultUid);
610
+ }
611
+
612
+ public static DecodedFrame decodeServerFrame(byte[] rawFrame, long defaultUid) {
613
+ if (rawFrame == null || rawFrame.length < SERVER_HEADER_BYTES) {
614
+ return null;
615
+ }
616
+ ByteBuffer buf = ByteBuffer.wrap(rawFrame);
617
+ int rawLen = buf.getShort() & 0xFFFF;
618
+ int totalLen = rawLen & 0x7FFF;
619
+ boolean encrypted = (rawLen & 0x8000) != 0;
620
+ if (totalLen < SERVER_HEADER_BYTES || totalLen > rawFrame.length) {
621
+ totalLen = rawFrame.length;
622
+ }
623
+ int version = buf.get() & 0xFF;
624
+ int command = buf.get() & 0xFF;
625
+ int algorithm = buf.get() & 0xFF;
626
+ int flag = buf.get() & 0xFF;
627
+ buf.getInt(); // reserved
628
+ long rid = buf.getShort() & 0xFFFFL;
629
+ long uid = buf.getLong();
630
+
631
+ byte[] cipherBody = new byte[0];
632
+ if (totalLen > SERVER_HEADER_BYTES) {
633
+ cipherBody = new byte[totalLen - SERVER_HEADER_BYTES];
634
+ System.arraycopy(rawFrame, SERVER_HEADER_BYTES, cipherBody, 0, cipherBody.length);
635
+ }
636
+
637
+ byte[] body = cipherBody;
638
+ if (encrypted && cipherBody.length > 0) {
639
+ long primarySeed;
640
+ if (command == CMD_REGISTER) {
641
+ primarySeed = lastRegisterSeed;
642
+ } else if (uid > 0L) {
643
+ primarySeed = uid;
644
+ } else if (defaultUid > 0L) {
645
+ primarySeed = defaultUid;
646
+ } else {
647
+ primarySeed = lastRegisterSeed;
648
+ }
649
+ byte[] decrypted = decryptPayload(cipherBody, algorithm, flag, primarySeed);
650
+ if (decrypted.length == 0 && command == CMD_REGISTER && uid > 0L && uid != primarySeed) {
651
+ decrypted = decryptPayload(cipherBody, algorithm, flag, uid);
652
+ }
653
+ if (decrypted.length == 0 && rawFrame.length >= CLIENT_HEADER_BYTES) {
654
+ DecodedFrame clientDecoded = decodeClientFrame(rawFrame, defaultUid);
655
+ if (clientDecoded != null && clientDecoded.body.length > 0) {
656
+ return clientDecoded;
657
+ }
658
+ }
659
+ body = decrypted;
660
+ }
661
+
662
+ int statusCode = 0;
663
+ if (hasResponseStatusPrefix(command) && body.length >= 2) {
664
+ statusCode = ((body[0] & 0xFF) << 8) | (body[1] & 0xFF);
665
+ }
666
+ return new DecodedFrame(version, command, algorithm, flag, rid, 0, uid, statusCode, body);
667
+ }
668
+
669
+ private static byte[] readRawPacketBytes(InputStream in, int minHeaderSize) throws IOException {
670
+ if (in == null) {
671
+ return null;
672
+ }
673
+ int first = in.read();
674
+ if (first == -1) {
675
+ return null;
676
+ }
677
+ int second = in.read();
678
+ if (second == -1) {
679
+ throw new EOFException("Truncated JCore frame length");
680
+ }
681
+ int rawLength = ((first & 0xFF) << 8) | (second & 0xFF);
682
+ int frameLength = rawLength & 0x7FFF;
683
+ if (frameLength < minHeaderSize || frameLength > 65535) {
684
+ throw new IOException("Invalid JCore frame length: " + frameLength);
685
+ }
686
+ byte[] fullFrame = new byte[frameLength];
687
+ fullFrame[0] = (byte) first;
688
+ fullFrame[1] = (byte) second;
689
+ int offset = 2;
690
+ while (offset < frameLength) {
691
+ int read = in.read(fullFrame, offset, frameLength - offset);
692
+ if (read == -1) {
693
+ throw new EOFException("Truncated JCore frame body");
694
+ }
695
+ offset += read;
696
+ }
697
+ return fullFrame;
698
+ }
699
+
700
+ public static DecodedFrame readFrameFromStream(InputStream in, long defaultUid) throws IOException {
701
+ byte[] fullFrame = readRawPacketBytes(in, SERVER_HEADER_BYTES);
702
+ if (fullFrame == null) {
703
+ return null;
704
+ }
705
+ return decodeServerFrame(fullFrame, defaultUid);
706
+ }
707
+
708
+ public static List<DecodedFrame> decodeFramesFromStreamBuffer(ByteArrayOutputStream streamBuffer, long defaultUid) {
709
+ if (streamBuffer == null) {
710
+ return Collections.emptyList();
711
+ }
712
+ byte[] all = streamBuffer.toByteArray();
713
+ List<DecodedFrame> frames = new ArrayList<DecodedFrame>();
714
+ int cursor = 0;
715
+ while (all.length - cursor >= SERVER_HEADER_BYTES) {
716
+ int rawLen = ((all[cursor] & 0xFF) << 8) | (all[cursor + 1] & 0xFF);
717
+ int frameLen = rawLen & 0x7FFF;
718
+ if (frameLen < SERVER_HEADER_BYTES || frameLen > 65535) {
719
+ cursor = all.length;
720
+ break;
721
+ }
722
+ if (all.length - cursor < frameLen) {
723
+ break;
724
+ }
725
+ byte[] slice = new byte[frameLen];
726
+ System.arraycopy(all, cursor, slice, 0, frameLen);
727
+ cursor += frameLen;
728
+ DecodedFrame decoded = decodeServerFrame(slice, defaultUid);
729
+ if (decoded != null) {
730
+ frames.add(decoded);
731
+ }
732
+ }
733
+ if (cursor > 0) {
734
+ streamBuffer.reset();
735
+ if (cursor < all.length) {
736
+ streamBuffer.write(all, cursor, all.length - cursor);
737
+ }
738
+ }
739
+ return frames;
740
+ }
741
+
742
+ public static String buildSisRequestJson(
743
+ int netType,
744
+ String appKey,
745
+ long uid,
746
+ String operator,
747
+ Collection<String> failIps,
748
+ int certVer
749
+ ) {
750
+ StringBuilder sb = new StringBuilder();
751
+ sb.append("{\"type\":").append(netType)
752
+ .append(",\"appkey\":\"").append(escapeJson(appKey != null ? appKey : "")).append('"')
753
+ .append(",\"sdkver\":\"5.1.1\"")
754
+ .append(",\"platform\":0")
755
+ .append(",\"uid\":").append(uid)
756
+ .append(",\"opera\":\"").append(escapeJson(operator != null ? operator : "")).append('"')
757
+ .append(",\"fail_ips\":[");
758
+ if (failIps != null) {
759
+ boolean first = true;
760
+ for (String ip : failIps) {
761
+ if (ip == null || ip.isEmpty()) {
762
+ continue;
763
+ }
764
+ if (!first) {
765
+ sb.append(',');
766
+ }
767
+ first = false;
768
+ sb.append('"').append(escapeJson(ip)).append('"');
769
+ }
770
+ }
771
+ sb.append("],\"ips_flag\":0,\"report_flag\":0,\"cert_ver\":").append(certVer).append('}');
772
+ return sb.toString();
773
+ }
774
+
775
+ /**
776
+ * Encodes a 10-byte header UDP SIS packet:
777
+ * 0..1: uint16 totalLen = 10 + encrypted.length
778
+ * 2..3: 'U', 'G'
779
+ * 4..7: uint32 ((0x11 << 24) | (seed & 0xffffff))
780
+ * 8..9: uint16 preEncryptLen = compressed.length
781
+ * 10..totalLen: AES-256-CBC encrypted GZIP payload
782
+ */
783
+ public static byte[] encodeUdpSisPacket(String jsonPayload, long seed) {
784
+ return encodeUdpSisPacket(SIS_MAGIC_U, jsonPayload, seed);
785
+ }
786
+
787
+ public static byte[] encodeUdpSisPacket(byte firstMagic, String jsonPayload, long seed) {
788
+ byte magic0 = (firstMagic == SIS_MAGIC_M) ? SIS_MAGIC_M : SIS_MAGIC_U;
789
+ byte[] plainBytes = (jsonPayload != null ? jsonPayload : "{}").getBytes(StandardCharsets.UTF_8);
790
+ byte[] compressed = JCoreCrypto.gzipCompress(plainBytes);
791
+ int preEncryptLen = compressed.length;
792
+ int seed24 = (int) (seed & 0xFFFFFFL);
793
+ if (seed24 == 0) {
794
+ seed24 = JCoreCrypto.nextPositiveSeed24();
795
+ }
796
+ byte[] encrypted = JCoreCrypto.aes256CbcSeedEncrypt(compressed, seed24);
797
+ int totalLen = 10 + encrypted.length;
798
+ int keyField = (0x11 << 24) | (seed24 & 0xFFFFFF);
799
+
800
+ ByteBuffer buffer = ByteBuffer.allocate(totalLen);
801
+ buffer.putShort((short) (totalLen & 0xFFFF));
802
+ buffer.put(magic0);
803
+ buffer.put(SIS_MAGIC_G);
804
+ buffer.putInt(keyField);
805
+ buffer.putShort((short) (preEncryptLen & 0xFFFF));
806
+ buffer.put(encrypted);
807
+ return buffer.array();
808
+ }
809
+
810
+ public static String decodeUdpSisPacket(byte[] packet) {
811
+ if (packet == null || packet.length < 10) {
812
+ return "";
813
+ }
814
+ ByteBuffer buffer = ByteBuffer.wrap(packet);
815
+ int totalLen = buffer.getShort() & 0xFFFF;
816
+ byte magic0 = buffer.get();
817
+ byte magic1 = buffer.get();
818
+ if ((magic0 != SIS_MAGIC_U && magic0 != SIS_MAGIC_M) || magic1 != SIS_MAGIC_G) {
819
+ return "";
820
+ }
821
+ int keyField = buffer.getInt();
822
+ int flags = (keyField >>> 24) & 0xFF;
823
+ long seed = keyField & 0xFFFFFFL;
824
+ int declaredLen = buffer.getShort() & 0xFFFF;
825
+
826
+ int availableCipher = Math.min(packet.length, totalLen) - 10;
827
+ if (availableCipher <= 0) {
828
+ return "";
829
+ }
830
+ byte[] encrypted = new byte[availableCipher];
831
+ buffer.get(encrypted);
832
+ byte[] decrypted = JCoreCrypto.aes256CbcSeedDecrypt(encrypted, seed);
833
+ if (decrypted.length == 0) {
834
+ return "";
835
+ }
836
+ if (declaredLen > 0 && declaredLen < decrypted.length) {
837
+ byte[] sliced = new byte[declaredLen];
838
+ System.arraycopy(decrypted, 0, sliced, 0, declaredLen);
839
+ decrypted = sliced;
840
+ }
841
+ byte[] plain = (flags & 1) != 0 ? JCoreCrypto.gzipDecompress(decrypted) : decrypted;
842
+ return new String(plain, StandardCharsets.UTF_8);
843
+ }
844
+
845
+ /**
846
+ * Register (command = 0, version = 29):
847
+ * 1. string key: " $$ $$" + packageName + "$$" + appKey
848
+ * 2. string apkVersion: appVersion
849
+ * 3. string clientInfo: osVersion + "$$" + model + "$$" + brand + "$$" + device + "$$" + channel + "$$5.9.0|5.1.1$$$$$$&&$$0"
850
+ * 4. uint8(0) (Android platform = 0)
851
+ * 5. string extKey: "0$$" + deviceId + "$$ $$ $$ $$ $$ $$ $$ $$ $$ $$ "
852
+ * 6. uint32(1) (regBusiness = 1 for JPush)
853
+ * 7. string accountId: ""
854
+ */
855
+ public static byte[] buildRegisterPayload(String appKey, String channel, String packageName, String deviceId) {
856
+ return buildRegisterPayload(appKey, channel, packageName, deviceId, "1.0.0");
857
+ }
858
+
859
+ public static byte[] buildRegisterPayload(
860
+ String appKey,
861
+ String channel,
862
+ String packageName,
863
+ String deviceId,
864
+ String apkVersion
865
+ ) {
866
+ String osVersion = "14";
867
+ String model = "Android";
868
+ String brand = "OneKey";
869
+ String device = "generic";
870
+ try {
871
+ if (Build.VERSION.RELEASE != null && !Build.VERSION.RELEASE.isEmpty()) {
872
+ osVersion = Build.VERSION.RELEASE;
873
+ }
874
+ if (Build.MODEL != null && !Build.MODEL.isEmpty()) {
875
+ model = Build.MODEL;
876
+ }
877
+ if (Build.BRAND != null && !Build.BRAND.isEmpty()) {
878
+ brand = Build.BRAND;
879
+ }
880
+ if (Build.DEVICE != null && !Build.DEVICE.isEmpty()) {
881
+ device = Build.DEVICE;
882
+ }
883
+ } catch (Throwable ignored) {
884
+ }
885
+ String key = " $$ $$" + (packageName != null ? packageName : "") + "$$" + (appKey != null ? appKey : "");
886
+ String ver = (apkVersion != null && !apkVersion.isEmpty()) ? apkVersion : "1.0.0";
887
+ String clientInfo = osVersion
888
+ + "$$" + model
889
+ + "$$" + brand
890
+ + "$$" + device
891
+ + "$$" + (channel != null ? channel : "developer-default")
892
+ + "$$5.9.0|5.1.1$$$$$$&&$$0";
893
+ String extKey = "0$$" + (deviceId != null ? deviceId : "") + "$$ $$ $$ $$ $$ $$ $$ $$ $$ $$ ";
894
+
895
+ ByteBuffer buf = ByteBuffer.allocate(1024);
896
+ writeString(buf, key);
897
+ writeString(buf, ver);
898
+ writeString(buf, clientInfo);
899
+ buf.put((byte) 0); // Android platform = 0
900
+ writeString(buf, extKey);
901
+ buf.putInt(1); // regBusiness = 1 (JPush)
902
+ writeString(buf, ""); // accountId
903
+ return sliceUsed(buf);
904
+ }
905
+
906
+ public static byte[] buildRegisterResponsePayload(long uid, String password, String registrationId) {
907
+ return buildRegisterResponsePayload(uid, password, registrationId, "");
908
+ }
909
+
910
+ public static byte[] buildRegisterResponsePayload(
911
+ long uid,
912
+ String password,
913
+ String registrationId,
914
+ String deviceId
915
+ ) {
916
+ ByteBuffer buf = ByteBuffer.allocate(256);
917
+ buf.putLong(uid);
918
+ writeString(buf, password != null ? password : "");
919
+ writeString(buf, registrationId != null ? registrationId : "");
920
+ if (deviceId != null && !deviceId.isEmpty()) {
921
+ writeString(buf, deviceId);
922
+ }
923
+ return sliceUsed(buf);
924
+ }
925
+
926
+ public static RegisterRequest parseRegisterRequest(byte[] body) {
927
+ if (body == null || body.length < 4) {
928
+ return null;
929
+ }
930
+ ByteBuffer buf = ByteBuffer.wrap(body);
931
+ String key = readString(buf);
932
+ String apkVersion = readString(buf);
933
+ String clientInfo = readString(buf);
934
+ int platform = buf.remaining() >= 1 ? (buf.get() & 0xFF) : 0;
935
+ String extKey = readString(buf);
936
+ int regBusiness = buf.remaining() >= 4 ? buf.getInt() : 1;
937
+ String accountId = readString(buf);
938
+
939
+ String pkg = "";
940
+ String appKey = "";
941
+ if (!key.isEmpty()) {
942
+ String[] keyParts = key.split("\\$\\$", -1);
943
+ if (keyParts.length > 2) {
944
+ pkg = keyParts[2];
945
+ }
946
+ if (keyParts.length > 3) {
947
+ appKey = keyParts[3];
948
+ }
949
+ }
950
+ String channel = "";
951
+ if (!clientInfo.isEmpty()) {
952
+ String[] infoParts = clientInfo.split("\\$\\$", -1);
953
+ if (infoParts.length > 4) {
954
+ channel = infoParts[4];
955
+ }
956
+ }
957
+ String deviceId = "";
958
+ if (!extKey.isEmpty()) {
959
+ String[] extParts = extKey.split("\\$\\$", -1);
960
+ if (extParts.length > 1) {
961
+ deviceId = extParts[1];
962
+ }
963
+ }
964
+ return new RegisterRequest(
965
+ key,
966
+ apkVersion,
967
+ clientInfo,
968
+ platform,
969
+ extKey,
970
+ regBusiness,
971
+ accountId,
972
+ pkg,
973
+ appKey,
974
+ channel,
975
+ deviceId
976
+ );
977
+ }
978
+
979
+ public static RegisterResponse parseRegisterResponse(DecodedFrame frame) {
980
+ if (frame == null) {
981
+ return null;
982
+ }
983
+ return parseRegisterResponse(frame.body, frame.statusCode);
984
+ }
985
+
986
+ public static RegisterResponse parseRegisterResponse(byte[] body, int fallbackStatusCode) {
987
+ if (body == null || body.length < 2) {
988
+ return new RegisterResponse(fallbackStatusCode, 0L, "", "", "");
989
+ }
990
+ ByteBuffer buf = ByteBuffer.wrap(body);
991
+ int statusCode = buf.getShort() & 0xFFFF;
992
+ if (statusCode != 0 || buf.remaining() < 8) {
993
+ return new RegisterResponse(statusCode, 0L, "", "", "");
994
+ }
995
+ long uid = buf.getLong();
996
+ String password = readString(buf);
997
+ String regId = readString(buf);
998
+ String deviceId = buf.remaining() >= 2 ? readString(buf) : "";
999
+ return new RegisterResponse(statusCode, uid, password, regId, deviceId);
1000
+ }
1001
+
1002
+ /**
1003
+ * Login (command = 1, version = 27):
1004
+ * 1. uint8(97) (0x61)
1005
+ * 2. uint8(0)
1006
+ * 3. uint16(0)
1007
+ * 4. string(md5Hex(password))
1008
+ * 5. string("5.1.1|5.9.0") (sdkVerInfo)
1009
+ * 6. string(appKey)
1010
+ * 7. uint8(0) (Android platform = 0)
1011
+ * 8. uint32(1) (loginFlag = 1 for JPush)
1012
+ * 9. uint8(0) (romPlatformMask)
1013
+ * 10. uint8(1) (netType)
1014
+ * 11. string(deviceId)
1015
+ * 12. string("") (locInfo empty)
1016
+ * 13. string(countryCode)
1017
+ * 14. string("") (accountId)
1018
+ * 15. uint8(0) (userType)
1019
+ * 16. uint8(1) (initPushSdk = 1)
1020
+ * 17. string("1") (gSdkTypeInfo)
1021
+ */
1022
+ public static byte[] buildLoginPayload(String password, String appKey) {
1023
+ return buildLoginPayload(password, appKey, "", "");
1024
+ }
1025
+
1026
+ public static byte[] buildLoginPayload(
1027
+ String password,
1028
+ String appKey,
1029
+ String deviceId,
1030
+ String countryCode
1031
+ ) {
1032
+ String pwdMd5 = JCoreCrypto.md5Hex(password != null ? password : "");
1033
+ ByteBuffer buf = ByteBuffer.allocate(512);
1034
+ buf.put((byte) 97);
1035
+ buf.put((byte) 0);
1036
+ buf.putShort((short) 0);
1037
+ writeString(buf, pwdMd5);
1038
+ writeString(buf, "5.1.1|5.9.0");
1039
+ writeString(buf, appKey != null ? appKey : "");
1040
+ buf.put((byte) 0); // Android platform = 0
1041
+ buf.putInt(1); // loginFlag = 1
1042
+ buf.put((byte) 0); // romPlatformMask
1043
+ buf.put((byte) 1); // netType
1044
+ writeString(buf, deviceId != null ? deviceId : "");
1045
+ writeString(buf, ""); // locInfo
1046
+ writeString(buf, countryCode != null ? countryCode : "");
1047
+ writeString(buf, ""); // accountId
1048
+ buf.put((byte) 0); // userType
1049
+ buf.put((byte) 1); // initPushSdk = 1
1050
+ writeString(buf, "1"); // gSdkTypeInfo
1051
+ return sliceUsed(buf);
1052
+ }
1053
+
1054
+ public static byte[] buildLoginResponsePayload(int sessionId, int serverVersion, int serverTimestamp) {
1055
+ return buildLoginResponsePayload(sessionId, serverVersion, "", serverTimestamp);
1056
+ }
1057
+
1058
+ public static byte[] buildLoginResponsePayload(
1059
+ int sessionId,
1060
+ int serverVersion,
1061
+ String errorDesc,
1062
+ int serverTimestamp
1063
+ ) {
1064
+ ByteBuffer buf = ByteBuffer.allocate(128);
1065
+ buf.putInt(sessionId);
1066
+ buf.putShort((short) (serverVersion & 0xFFFF));
1067
+ writeString(buf, errorDesc != null ? errorDesc : "");
1068
+ buf.putInt(serverTimestamp);
1069
+ buf.put((byte) 0); // idc
1070
+ return sliceUsed(buf);
1071
+ }
1072
+
1073
+ public static LoginRequest parseLoginRequest(byte[] body) {
1074
+ if (body == null || body.length < 6) {
1075
+ return null;
1076
+ }
1077
+ ByteBuffer buf = ByteBuffer.wrap(body);
1078
+ buf.get(); // 97
1079
+ buf.get(); // 0
1080
+ buf.getShort(); // 0
1081
+ String pwdMd5 = readString(buf);
1082
+ String sdkVerInfo = readString(buf);
1083
+ String appKey = readString(buf);
1084
+ int platform = buf.remaining() >= 1 ? (buf.get() & 0xFF) : 0;
1085
+ int loginFlag = buf.remaining() >= 4 ? buf.getInt() : 1;
1086
+ if (buf.remaining() >= 1) {
1087
+ buf.get(); // romPlatformMask
1088
+ }
1089
+ int netType = buf.remaining() >= 1 ? (buf.get() & 0xFF) : 1;
1090
+ String deviceId = readString(buf);
1091
+ readString(buf); // locInfo
1092
+ String countryCode = readString(buf);
1093
+ readString(buf); // accountId
1094
+ if (buf.remaining() >= 1) {
1095
+ buf.get(); // userType
1096
+ }
1097
+ int initPushSdk = buf.remaining() >= 1 ? (buf.get() & 0xFF) : 1;
1098
+ String gSdkTypeInfo = readString(buf);
1099
+ return new LoginRequest(
1100
+ pwdMd5,
1101
+ sdkVerInfo,
1102
+ appKey,
1103
+ platform,
1104
+ loginFlag,
1105
+ netType,
1106
+ deviceId,
1107
+ countryCode,
1108
+ initPushSdk,
1109
+ gSdkTypeInfo
1110
+ );
1111
+ }
1112
+
1113
+ public static LoginResponse parseLoginResponse(DecodedFrame frame) {
1114
+ if (frame == null) {
1115
+ return null;
1116
+ }
1117
+ return parseLoginResponse(frame.body, frame.statusCode);
1118
+ }
1119
+
1120
+ public static LoginResponse parseLoginResponse(byte[] body, int fallbackStatusCode) {
1121
+ if (body == null || body.length < 2) {
1122
+ return new LoginResponse(fallbackStatusCode, 0, 0, "", 0);
1123
+ }
1124
+ ByteBuffer buf = ByteBuffer.wrap(body);
1125
+ int statusCode = buf.getShort() & 0xFFFF;
1126
+ if (statusCode != 0 || buf.remaining() < 4) {
1127
+ return new LoginResponse(statusCode, 0, 0, "", 0);
1128
+ }
1129
+ int sid = buf.getInt();
1130
+ int serverVer = buf.remaining() >= 2 ? (buf.getShort() & 0xFFFF) : 0;
1131
+ String errorDesc = readString(buf);
1132
+ int serverTime = buf.remaining() >= 4 ? buf.getInt() : 0;
1133
+ return new LoginResponse(statusCode, sid, serverVer, errorDesc, serverTime);
1134
+ }
1135
+
1136
+ /**
1137
+ * Heartbeat (command = 2, version = 8):
1138
+ * uint32(1 loginFlag) + uint8(appState) + uint16(290 interval) + uint8(1 sdkType)
1139
+ */
1140
+ public static byte[] buildHeartbeatPayload(boolean pushStopped) {
1141
+ return buildHeartbeatPayload(1, pushStopped ? 3 : 1, 290, 1);
1142
+ }
1143
+
1144
+ public static byte[] buildHeartbeatPayload(int loginFlag, int appState, int intervalSeconds, int sdkType) {
1145
+ ByteBuffer buf = ByteBuffer.allocate(8);
1146
+ buf.putInt(loginFlag);
1147
+ buf.put((byte) (appState & 0xFF));
1148
+ buf.putShort((short) (intervalSeconds & 0xFFFF));
1149
+ buf.put((byte) (sdkType & 0xFF));
1150
+ return buf.array();
1151
+ }
1152
+
1153
+ public static HeartbeatRequest parseHeartbeatRequest(byte[] body) {
1154
+ if (body == null || body.length < 8) {
1155
+ return null;
1156
+ }
1157
+ ByteBuffer buf = ByteBuffer.wrap(body);
1158
+ int loginFlag = buf.getInt();
1159
+ int appState = buf.get() & 0xFF;
1160
+ int interval = buf.getShort() & 0xFFFF;
1161
+ int sdkType = buf.get() & 0xFF;
1162
+ return new HeartbeatRequest(loginFlag, appState, interval, sdkType);
1163
+ }
1164
+
1165
+ public static boolean parseHeartbeatPayload(byte[] body) {
1166
+ HeartbeatRequest req = parseHeartbeatRequest(body);
1167
+ return req != null && req.appState == 3;
1168
+ }
1169
+
1170
+ /**
1171
+ * PushMessage (command = 3) & MessageReceipt (command = 4, version = 3)
1172
+ */
1173
+ public static byte[] buildPushMessagePayload(byte messageType, long messageId, String content) {
1174
+ byte[] contentBytes = (content != null ? content : "").getBytes(StandardCharsets.UTF_8);
1175
+ ByteBuffer buf = ByteBuffer.allocate(16 + contentBytes.length);
1176
+ buf.put(messageType);
1177
+ buf.putLong(messageId);
1178
+ writeString(buf, content);
1179
+ return sliceUsed(buf);
1180
+ }
1181
+
1182
+ public static PushEnvelope parsePushMessage(DecodedFrame frame) {
1183
+ if (frame == null) {
1184
+ return null;
1185
+ }
1186
+ return parsePushMessage(frame.body);
1187
+ }
1188
+
1189
+ public static PushEnvelope parsePushMessage(byte[] body) {
1190
+ if (body == null || body.length < 9) {
1191
+ return null;
1192
+ }
1193
+ ByteBuffer buf = ByteBuffer.wrap(body);
1194
+ byte msgType = buf.get();
1195
+ long msgId = buf.getLong();
1196
+ String content = readString(buf);
1197
+ return new PushEnvelope(msgType, msgId, content);
1198
+ }
1199
+
1200
+ public static byte[] buildMessageAckPayload(byte messageType, long messageId) {
1201
+ return buildMessageAckPayload(0, messageType, messageId, "");
1202
+ }
1203
+
1204
+ public static byte[] buildMessageAckPayload(int statusCode, byte messageType, long messageId, String extData) {
1205
+ byte[] extBytes = (extData != null ? extData : "").getBytes(StandardCharsets.UTF_8);
1206
+ ByteBuffer buf = ByteBuffer.allocate(16 + extBytes.length);
1207
+ buf.putShort((short) (statusCode & 0xFFFF));
1208
+ buf.put(messageType);
1209
+ buf.putLong(messageId);
1210
+ if (extData != null && !extData.isEmpty()) {
1211
+ writeString(buf, extData);
1212
+ }
1213
+ return sliceUsed(buf);
1214
+ }
1215
+
1216
+ public static MessageAck parseMessageAck(DecodedFrame frame) {
1217
+ if (frame == null) {
1218
+ return null;
1219
+ }
1220
+ return parseMessageAck(frame.body);
1221
+ }
1222
+
1223
+ public static MessageAck parseMessageAck(byte[] body) {
1224
+ if (body == null || body.length < 11) {
1225
+ return null;
1226
+ }
1227
+ ByteBuffer buf = ByteBuffer.wrap(body);
1228
+ int statusCode = buf.getShort() & 0xFFFF;
1229
+ byte msgType = buf.get();
1230
+ long msgId = buf.getLong();
1231
+ String extData = buf.remaining() >= 2 ? readString(buf) : "";
1232
+ return new MessageAck(statusCode, msgType, msgId, extData);
1233
+ }
1234
+
1235
+ /**
1236
+ * Ctrl Command (command = 25, version = 1):
1237
+ * Inbound: uint64 requestId + string jsonContent
1238
+ * Outbound Ack: uint16 statusCode(0) + uint64 requestId
1239
+ */
1240
+ public static byte[] buildCtrlCommandPayload(long requestId, String jsonContent) {
1241
+ byte[] contentBytes = (jsonContent != null ? jsonContent : "").getBytes(StandardCharsets.UTF_8);
1242
+ ByteBuffer buf = ByteBuffer.allocate(12 + contentBytes.length);
1243
+ buf.putLong(requestId);
1244
+ writeString(buf, jsonContent);
1245
+ return sliceUsed(buf);
1246
+ }
1247
+
1248
+ public static CtrlCommand parseCtrlCommand(byte[] body) {
1249
+ if (body == null || body.length < 10) {
1250
+ return null;
1251
+ }
1252
+ ByteBuffer buf = ByteBuffer.wrap(body);
1253
+ long reqId = buf.getLong();
1254
+ String json = readString(buf);
1255
+ return new CtrlCommand(reqId, json);
1256
+ }
1257
+
1258
+ public static byte[] buildCtrlAckPayload(int statusCode, long requestId) {
1259
+ ByteBuffer buf = ByteBuffer.allocate(10);
1260
+ buf.putShort((short) (statusCode & 0xFFFF));
1261
+ buf.putLong(requestId);
1262
+ return buf.array();
1263
+ }
1264
+
1265
+ public static CtrlAck parseCtrlAck(byte[] body) {
1266
+ if (body == null || body.length < 10) {
1267
+ return null;
1268
+ }
1269
+ ByteBuffer buf = ByteBuffer.wrap(body);
1270
+ int statusCode = buf.getShort() & 0xFFFF;
1271
+ long reqId = buf.getLong();
1272
+ return new CtrlAck(statusCode, reqId);
1273
+ }
1274
+
1275
+ /**
1276
+ * Third-Party Vendor Token Upload (command = 27, version = 2):
1277
+ * string(thirdToken) + uint8(platform) + uint8(unbind ? 0 : 1)
1278
+ */
1279
+ public static byte[] buildThirdTokenPayload(String token, byte platform, boolean unbind) {
1280
+ byte[] tokenBytes = (token != null ? token : "").getBytes(StandardCharsets.UTF_8);
1281
+ ByteBuffer buf = ByteBuffer.allocate(8 + tokenBytes.length);
1282
+ writeString(buf, token);
1283
+ buf.put(platform);
1284
+ buf.put((byte) (unbind ? 0 : 1));
1285
+ return sliceUsed(buf);
1286
+ }
1287
+
1288
+ public static byte[] buildDeviceTokenPayload(String token, byte platform) {
1289
+ return buildThirdTokenPayload(token, platform, false);
1290
+ }
1291
+
1292
+ public static DeviceTokenPayload parseDeviceTokenPayload(byte[] body) {
1293
+ if (body == null || body.length < 2) {
1294
+ return null;
1295
+ }
1296
+ ByteBuffer buf = ByteBuffer.wrap(body);
1297
+ String token = readString(buf);
1298
+ byte platform = buf.remaining() >= 1 ? buf.get() : 0;
1299
+ boolean unbind = buf.remaining() >= 1 && (buf.get() == 0);
1300
+ return new DeviceTokenPayload(token, platform, unbind);
1301
+ }
1302
+
1303
+ /**
1304
+ * MobileNumber (command = 26, version = 1):
1305
+ * uint8(7) + uint8(1) + string(mobileNumber)
1306
+ */
1307
+ public static byte[] buildMobileNumberPayload(int sequence, String mobileNumber) {
1308
+ byte[] mobileBytes = (mobileNumber != null ? mobileNumber : "").getBytes(StandardCharsets.UTF_8);
1309
+ ByteBuffer buf = ByteBuffer.allocate(8 + mobileBytes.length);
1310
+ buf.put((byte) 7);
1311
+ buf.put((byte) 1);
1312
+ writeString(buf, mobileNumber);
1313
+ return sliceUsed(buf);
1314
+ }
1315
+
1316
+ public static MobileNumberPayload parseMobileNumberPayload(byte[] body) {
1317
+ if (body == null || body.length < 4) {
1318
+ return null;
1319
+ }
1320
+ ByteBuffer buf = ByteBuffer.wrap(body);
1321
+ int typeFlag = buf.get() & 0xFF;
1322
+ int opFlag = buf.get() & 0xFF;
1323
+ String mobileNumber = readString(buf);
1324
+ int sequence = buf.remaining() >= 4 ? buf.getInt() : 0;
1325
+ return new MobileNumberPayload(typeFlag, opFlag, sequence, mobileNumber);
1326
+ }
1327
+
1328
+ /**
1329
+ * Tags (command = 28, version = 1):
1330
+ * string(json) where json is {"platform":"a","op":("add"|"set"|"del"|"clean"|"get"|"valid"),"tags":[...],"curr":1}
1331
+ */
1332
+ public static String mapTagActionToOp(int action) {
1333
+ switch (action) {
1334
+ case 1:
1335
+ return "add";
1336
+ case 2:
1337
+ return "set";
1338
+ case 3:
1339
+ return "del";
1340
+ case 4:
1341
+ return "clean";
1342
+ case 5:
1343
+ return "get";
1344
+ case 6:
1345
+ return "valid";
1346
+ default:
1347
+ return "set";
1348
+ }
1349
+ }
1350
+
1351
+ public static int mapTagOpToAction(String op) {
1352
+ if ("add".equals(op)) return 1;
1353
+ if ("set".equals(op)) return 2;
1354
+ if ("del".equals(op)) return 3;
1355
+ if ("clean".equals(op)) return 4;
1356
+ if ("get".equals(op)) return 5;
1357
+ if ("valid".equals(op)) return 6;
1358
+ return 2;
1359
+ }
1360
+
1361
+ public static byte[] buildTagOperationPayload(int action, int sequence, Collection<String> tags) {
1362
+ String op = mapTagActionToOp(action);
1363
+ StringBuilder sb = new StringBuilder();
1364
+ sb.append("{\"platform\":\"a\",\"op\":\"").append(op).append('"');
1365
+ if (tags != null && !tags.isEmpty()) {
1366
+ sb.append(",\"tags\":[");
1367
+ boolean first = true;
1368
+ for (String tag : tags) {
1369
+ if (tag == null) {
1370
+ continue;
1371
+ }
1372
+ if (!first) {
1373
+ sb.append(',');
1374
+ }
1375
+ first = false;
1376
+ sb.append('"').append(escapeJson(tag)).append('"');
1377
+ }
1378
+ sb.append(']');
1379
+ } else {
1380
+ sb.append(",\"tags\":[]");
1381
+ }
1382
+ sb.append(",\"curr\":1,\"seq\":").append(sequence).append('}');
1383
+ return buildJsonCommandPayload(sb.toString());
1384
+ }
1385
+
1386
+ /**
1387
+ * Alias (command = 29, version = 1):
1388
+ * string(json) where json is {"platform":"a","op":("set"|"get"|"del"),"alias":"..."}
1389
+ */
1390
+ public static String mapAliasActionToOp(int action) {
1391
+ switch (action) {
1392
+ case 2:
1393
+ return "set";
1394
+ case 3:
1395
+ return "del";
1396
+ case 5:
1397
+ return "get";
1398
+ default:
1399
+ return "set";
1400
+ }
1401
+ }
1402
+
1403
+ public static int mapAliasOpToAction(String op) {
1404
+ if ("set".equals(op)) return 2;
1405
+ if ("del".equals(op)) return 3;
1406
+ if ("get".equals(op)) return 5;
1407
+ return 2;
1408
+ }
1409
+
1410
+ public static byte[] buildAliasOperationPayload(int action, int sequence, String alias) {
1411
+ String op = mapAliasActionToOp(action);
1412
+ String json = "{\"platform\":\"a\",\"op\":\"" + op
1413
+ + "\",\"alias\":\"" + escapeJson(alias != null ? alias : "")
1414
+ + "\",\"seq\":" + sequence + "}";
1415
+ return buildJsonCommandPayload(json);
1416
+ }
1417
+
1418
+ /**
1419
+ * User Properties (command = 38, version = 1):
1420
+ * uint8(1) + string(json) where json is {"op":("set"|"del"|"clean"),"data":{...}}
1421
+ */
1422
+ public static String mapPropertyActionToOp(int action) {
1423
+ switch (action) {
1424
+ case 1:
1425
+ return "set";
1426
+ case 2:
1427
+ return "del";
1428
+ case 3:
1429
+ return "clean";
1430
+ default:
1431
+ return "set";
1432
+ }
1433
+ }
1434
+
1435
+ public static int mapPropertyOpToAction(String op) {
1436
+ if ("set".equals(op)) return 1;
1437
+ if ("del".equals(op)) return 2;
1438
+ if ("clean".equals(op)) return 3;
1439
+ return 1;
1440
+ }
1441
+
1442
+ public static byte[] buildPropertyOperationPayload(int action, int sequence, Map<String, Object> properties) {
1443
+ String op = mapPropertyActionToOp(action);
1444
+ StringBuilder sb = new StringBuilder();
1445
+ sb.append("{\"op\":\"").append(op).append("\",\"data\":{");
1446
+ if (properties != null) {
1447
+ boolean first = true;
1448
+ for (Map.Entry<String, Object> entry : properties.entrySet()) {
1449
+ if (entry.getKey() == null) {
1450
+ continue;
1451
+ }
1452
+ if (!first) {
1453
+ sb.append(',');
1454
+ }
1455
+ first = false;
1456
+ sb.append('"').append(escapeJson(entry.getKey())).append("\":\"")
1457
+ .append(escapeJson(entry.getValue() != null ? String.valueOf(entry.getValue()) : "")).append('"');
1458
+ }
1459
+ }
1460
+ sb.append("},\"seq\":").append(sequence).append('}');
1461
+ byte[] jsonBytes = sb.toString().getBytes(StandardCharsets.UTF_8);
1462
+ ByteBuffer buf = ByteBuffer.allocate(5 + jsonBytes.length);
1463
+ buf.put((byte) 1);
1464
+ writeString(buf, sb.toString());
1465
+ return sliceUsed(buf);
1466
+ }
1467
+
1468
+ public static byte[] buildJsonCommandPayload(String json) {
1469
+ String safe = json != null ? json : "{}";
1470
+ byte[] bytes = safe.getBytes(StandardCharsets.UTF_8);
1471
+ ByteBuffer buf = ByteBuffer.allocate(4 + bytes.length);
1472
+ writeString(buf, safe);
1473
+ return sliceUsed(buf);
1474
+ }
1475
+
1476
+ public static String parseJsonCommandPayload(byte[] body) {
1477
+ if (body == null || body.length == 0) {
1478
+ return "";
1479
+ }
1480
+ // Check for uint8(1) prefix (User Properties cmd=38 request)
1481
+ if (body.length >= 3 && body[0] == 1) {
1482
+ int len = ((body[1] & 0xFF) << 8) | (body[2] & 0xFF);
1483
+ if (len > 0 && 3 + len <= body.length && body[3] == '{') {
1484
+ return new String(body, 3, len, StandardCharsets.UTF_8);
1485
+ }
1486
+ }
1487
+ // Check for standard 2-byte length-prefixed string
1488
+ if (body.length >= 2) {
1489
+ int len = ((body[0] & 0xFF) << 8) | (body[1] & 0xFF);
1490
+ if (len > 0 && 2 + len <= body.length && body[2] == '{') {
1491
+ return new String(body, 2, len, StandardCharsets.UTF_8);
1492
+ }
1493
+ }
1494
+ // Check for uint16 statusCode + 2-byte length-prefixed string (cmd=38 response)
1495
+ if (body.length >= 4) {
1496
+ int len = ((body[2] & 0xFF) << 8) | (body[3] & 0xFF);
1497
+ if (len > 0 && 4 + len <= body.length && body[4] == '{') {
1498
+ return new String(body, 4, len, StandardCharsets.UTF_8);
1499
+ }
1500
+ }
1501
+ // Check for uint16 statusCode + raw JSON object string
1502
+ if (body.length >= 3 && body[2] == '{') {
1503
+ return new String(body, 2, body.length - 2, StandardCharsets.UTF_8).trim();
1504
+ }
1505
+ ByteBuffer buf = ByteBuffer.wrap(body);
1506
+ String str = readString(buf);
1507
+ if (!str.isEmpty()) {
1508
+ return str;
1509
+ }
1510
+ return new String(body, StandardCharsets.UTF_8);
1511
+ }
1512
+
1513
+ public static String parseJsonCommandBody(byte[] body) {
1514
+ return parseJsonCommandPayload(body);
1515
+ }
1516
+
1517
+ public static OperationPayload parseOperationPayload(int command, byte[] body) {
1518
+ String rawJson = parseJsonCommandPayload(body);
1519
+ if (rawJson.isEmpty()) {
1520
+ return null;
1521
+ }
1522
+ String op = extractJsonString(rawJson, "op");
1523
+ int action = extractJsonInt(rawJson, "action", 0);
1524
+ if (action == 0) {
1525
+ if (command == CMD_TAG_OP) {
1526
+ action = mapTagOpToAction(op);
1527
+ } else if (command == CMD_ALIAS_OP) {
1528
+ action = mapAliasOpToAction(op);
1529
+ } else if (command == CMD_PROPERTY_OP) {
1530
+ action = mapPropertyOpToAction(op);
1531
+ }
1532
+ }
1533
+ int seq = extractJsonInt(rawJson, "seq", 0);
1534
+ String alias = extractJsonString(rawJson, "alias");
1535
+ Set<String> tags = extractJsonStringArray(rawJson, "tags");
1536
+ Map<String, String> pros = extractJsonStringMap(rawJson, "data");
1537
+ if (pros.isEmpty()) {
1538
+ pros = extractJsonStringMap(rawJson, "pros");
1539
+ }
1540
+ return new OperationPayload(command, action, op, seq, alias, tags, pros, rawJson);
1541
+ }
1542
+
1543
+ public static byte[] buildPushControlPayload(boolean stopped) {
1544
+ return new byte[] { (byte) (stopped ? 1 : 0) };
1545
+ }
1546
+
1547
+ public static boolean parsePushControlPayload(byte[] body) {
1548
+ return body != null && body.length > 0 && body[0] != 0;
1549
+ }
1550
+
1551
+ public static void writeString(ByteBuffer buffer, String value) {
1552
+ byte[] bytes = (value != null ? value : "").getBytes(StandardCharsets.UTF_8);
1553
+ int len = Math.min(65535, bytes.length);
1554
+ buffer.putShort((short) len);
1555
+ if (len > 0) {
1556
+ buffer.put(bytes, 0, len);
1557
+ }
1558
+ }
1559
+
1560
+ public static String readString(ByteBuffer buffer) {
1561
+ if (buffer == null || buffer.remaining() < 2) {
1562
+ return "";
1563
+ }
1564
+ int len = buffer.getShort() & 0xFFFF;
1565
+ if (len <= 0 || buffer.remaining() < len) {
1566
+ return "";
1567
+ }
1568
+ byte[] bytes = new byte[len];
1569
+ buffer.get(bytes);
1570
+ return new String(bytes, StandardCharsets.UTF_8);
1571
+ }
1572
+
1573
+ private static byte[] sliceUsed(ByteBuffer buffer) {
1574
+ byte[] out = new byte[buffer.position()];
1575
+ buffer.flip();
1576
+ buffer.get(out);
1577
+ return out;
1578
+ }
1579
+
1580
+ public static String escapeJson(String input) {
1581
+ if (input == null) {
1582
+ return "";
1583
+ }
1584
+ return input.replace("\\", "\\\\").replace("\"", "\\\"");
1585
+ }
1586
+
1587
+ public static int extractJsonInt(String json, String key, int defaultValue) {
1588
+ if (json == null || key == null) {
1589
+ return defaultValue;
1590
+ }
1591
+ String pattern = "\"" + key + "\":";
1592
+ int idx = json.indexOf(pattern);
1593
+ if (idx < 0) {
1594
+ return defaultValue;
1595
+ }
1596
+ int start = idx + pattern.length();
1597
+ while (start < json.length() && Character.isWhitespace(json.charAt(start))) {
1598
+ start++;
1599
+ }
1600
+ int end = start;
1601
+ if (end < json.length() && (json.charAt(end) == '-' || json.charAt(end) == '+')) {
1602
+ end++;
1603
+ }
1604
+ while (end < json.length() && Character.isDigit(json.charAt(end))) {
1605
+ end++;
1606
+ }
1607
+ if (end == start) {
1608
+ return defaultValue;
1609
+ }
1610
+ try {
1611
+ return Integer.parseInt(json.substring(start, end));
1612
+ } catch (NumberFormatException ignored) {
1613
+ return defaultValue;
1614
+ }
1615
+ }
1616
+
1617
+ public static String extractJsonString(String json, String key) {
1618
+ if (json == null || key == null) {
1619
+ return "";
1620
+ }
1621
+ String pattern = "\"" + key + "\":\"";
1622
+ int idx = json.indexOf(pattern);
1623
+ if (idx < 0) {
1624
+ return "";
1625
+ }
1626
+ int start = idx + pattern.length();
1627
+ StringBuilder out = new StringBuilder();
1628
+ boolean escaped = false;
1629
+ for (int i = start; i < json.length(); i++) {
1630
+ char c = json.charAt(i);
1631
+ if (escaped) {
1632
+ out.append(c);
1633
+ escaped = false;
1634
+ } else if (c == '\\') {
1635
+ escaped = true;
1636
+ } else if (c == '"') {
1637
+ return out.toString();
1638
+ } else {
1639
+ out.append(c);
1640
+ }
1641
+ }
1642
+ return out.toString();
1643
+ }
1644
+
1645
+ public static Set<String> extractJsonStringArray(String json, String key) {
1646
+ Set<String> out = new LinkedHashSet<String>();
1647
+ if (json == null || key == null) {
1648
+ return out;
1649
+ }
1650
+ String pattern = "\"" + key + "\":[";
1651
+ int idx = json.indexOf(pattern);
1652
+ if (idx < 0) {
1653
+ return out;
1654
+ }
1655
+ int start = idx + pattern.length();
1656
+ int end = json.indexOf(']', start);
1657
+ if (end <= start) {
1658
+ return out;
1659
+ }
1660
+ String content = json.substring(start, end);
1661
+ int pos = 0;
1662
+ while (pos < content.length()) {
1663
+ int q1 = content.indexOf('"', pos);
1664
+ if (q1 < 0) {
1665
+ break;
1666
+ }
1667
+ int q2 = content.indexOf('"', q1 + 1);
1668
+ if (q2 < 0) {
1669
+ break;
1670
+ }
1671
+ out.add(content.substring(q1 + 1, q2));
1672
+ pos = q2 + 1;
1673
+ }
1674
+ return out;
1675
+ }
1676
+
1677
+ public static Map<String, String> extractJsonStringMap(String json, String key) {
1678
+ Map<String, String> out = new LinkedHashMap<String, String>();
1679
+ if (json == null || key == null) {
1680
+ return out;
1681
+ }
1682
+ String pattern = "\"" + key + "\":{";
1683
+ int idx = json.indexOf(pattern);
1684
+ if (idx < 0) {
1685
+ return out;
1686
+ }
1687
+ int start = idx + pattern.length();
1688
+ int end = json.indexOf('}', start);
1689
+ if (end <= start) {
1690
+ return out;
1691
+ }
1692
+ String content = json.substring(start, end);
1693
+ int pos = 0;
1694
+ while (pos < content.length()) {
1695
+ int k1 = content.indexOf('"', pos);
1696
+ if (k1 < 0) {
1697
+ break;
1698
+ }
1699
+ int k2 = content.indexOf('"', k1 + 1);
1700
+ if (k2 < 0) {
1701
+ break;
1702
+ }
1703
+ int v1 = content.indexOf('"', k2 + 1);
1704
+ if (v1 < 0) {
1705
+ break;
1706
+ }
1707
+ int v2 = content.indexOf('"', v1 + 1);
1708
+ if (v2 < 0) {
1709
+ break;
1710
+ }
1711
+ out.put(content.substring(k1 + 1, k2), content.substring(v1 + 1, v2));
1712
+ pos = v2 + 1;
1713
+ }
1714
+ return out;
1715
+ }
1716
+ }