@onekeyfe/react-native-jpush 0.0.0-stage → 6.7.0-alpha.435

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 (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -2
  3. package/ReactNativeJpush.podspec +66 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +143 -0
  6. package/android/gradle.properties +4 -0
  7. package/android/src/main/AndroidManifest.xml +143 -0
  8. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  9. package/android/src/main/java/cn/android/service/JTransitActivity.java +19 -0
  10. package/android/src/main/java/cn/jiguang/android/BuildConfig.java +10 -0
  11. package/android/src/main/java/cn/jiguang/android/IDataShare.java +190 -0
  12. package/android/src/main/java/cn/jiguang/api/BaseLogger.java +82 -0
  13. package/android/src/main/java/cn/jiguang/api/JAction.java +22 -0
  14. package/android/src/main/java/cn/jiguang/api/JActionExtra.java +21 -0
  15. package/android/src/main/java/cn/jiguang/api/JAnalyticsAction.java +11 -0
  16. package/android/src/main/java/cn/jiguang/api/JCoreInterface.java +228 -0
  17. package/android/src/main/java/cn/jiguang/api/JCoreManager.java +156 -0
  18. package/android/src/main/java/cn/jiguang/api/JDispatchAction.java +66 -0
  19. package/android/src/main/java/cn/jiguang/api/MultiSpHelper.java +114 -0
  20. package/android/src/main/java/cn/jiguang/api/ReportCallBack.java +9 -0
  21. package/android/src/main/java/cn/jiguang/api/SdkType.java +13 -0
  22. package/android/src/main/java/cn/jiguang/api/utils/JCollectionAuth.java +23 -0
  23. package/android/src/main/java/cn/jiguang/core/JCoreCrypto.java +545 -0
  24. package/android/src/main/java/cn/jiguang/core/JCoreStorage.java +737 -0
  25. package/android/src/main/java/cn/jiguang/core/JCoreTcpEngine.java +1394 -0
  26. package/android/src/main/java/cn/jiguang/core/JCoreWireCodec.java +1716 -0
  27. package/android/src/main/java/cn/jiguang/internal/ActionManager.java +63 -0
  28. package/android/src/main/java/cn/jiguang/internal/JConstants.java +61 -0
  29. package/android/src/main/java/cn/jiguang/internal/JCoreHelperAction.java +12 -0
  30. package/android/src/main/java/cn/jiguang/internal/JCoreInternalHelper.java +102 -0
  31. package/android/src/main/java/cn/jiguang/net/DefaultHostVerifier.java +40 -0
  32. package/android/src/main/java/cn/jiguang/net/HttpRequest.java +78 -0
  33. package/android/src/main/java/cn/jiguang/net/HttpResponse.java +48 -0
  34. package/android/src/main/java/cn/jiguang/net/HttpUtils.java +106 -0
  35. package/android/src/main/java/cn/jiguang/plugins/core/JCoreModule.java +91 -0
  36. package/android/src/main/java/cn/jiguang/plugins/core/JCorePackage.java +27 -0
  37. package/android/src/main/java/cn/jiguang/plugins/push/JPushModule.java +773 -0
  38. package/android/src/main/java/cn/jiguang/plugins/push/JPushPackage.java +25 -0
  39. package/android/src/main/java/cn/jiguang/plugins/push/common/JConstants.java +88 -0
  40. package/android/src/main/java/cn/jiguang/plugins/push/common/JLogger.java +122 -0
  41. package/android/src/main/java/cn/jiguang/plugins/push/helper/JPushHelper.java +710 -0
  42. package/android/src/main/java/cn/jiguang/plugins/push/receiver/JPushBroadcastReceiver.java +37 -0
  43. package/android/src/main/java/cn/jiguang/plugins/push/receiver/JPushModuleReceiver.java +264 -0
  44. package/android/src/main/java/cn/jiguang/plugins/service/JCoreModuleService.java +7 -0
  45. package/android/src/main/java/cn/jpush/android/api/BasicPushNotificationBuilder.java +83 -0
  46. package/android/src/main/java/cn/jpush/android/api/CallBackParams.java +50 -0
  47. package/android/src/main/java/cn/jpush/android/api/CmdMessage.java +33 -0
  48. package/android/src/main/java/cn/jpush/android/api/CustomMessage.java +28 -0
  49. package/android/src/main/java/cn/jpush/android/api/CustomPushNotificationBuilder.java +81 -0
  50. package/android/src/main/java/cn/jpush/android/api/DefaultPushNotificationBuilder.java +90 -0
  51. package/android/src/main/java/cn/jpush/android/api/InAppSlotParams.java +65 -0
  52. package/android/src/main/java/cn/jpush/android/api/InstrumentedActivity.java +17 -0
  53. package/android/src/main/java/cn/jpush/android/api/InstrumentedListActivity.java +17 -0
  54. package/android/src/main/java/cn/jpush/android/api/JPushInterface.java +488 -0
  55. package/android/src/main/java/cn/jpush/android/api/JPushMessage.java +121 -0
  56. package/android/src/main/java/cn/jpush/android/api/JThirdPlatFormInterface.java +147 -0
  57. package/android/src/main/java/cn/jpush/android/api/MultiActionsNotificationBuilder.java +72 -0
  58. package/android/src/main/java/cn/jpush/android/api/NotificationCustomButton.java +50 -0
  59. package/android/src/main/java/cn/jpush/android/api/NotificationMessage.java +67 -0
  60. package/android/src/main/java/cn/jpush/android/api/PushNotificationBuilder.java +13 -0
  61. package/android/src/main/java/cn/jpush/android/api/TagAliasCallback.java +8 -0
  62. package/android/src/main/java/cn/jpush/android/data/JPushCollectControl.java +96 -0
  63. package/android/src/main/java/cn/jpush/android/data/JPushConfig.java +79 -0
  64. package/android/src/main/java/cn/jpush/android/data/JPushConfigEx.java +41 -0
  65. package/android/src/main/java/cn/jpush/android/data/JPushLocalNotification.java +155 -0
  66. package/android/src/main/java/cn/jpush/android/helper/JCoreHelper.java +70 -0
  67. package/android/src/main/java/cn/jpush/android/helper/Logger.java +82 -0
  68. package/android/src/main/java/cn/jpush/android/local/ActionHelper.java +1198 -0
  69. package/android/src/main/java/cn/jpush/android/local/ActivityLifeCallBack.java +47 -0
  70. package/android/src/main/java/cn/jpush/android/local/JPushAction.java +67 -0
  71. package/android/src/main/java/cn/jpush/android/local/JPushConstants.java +34 -0
  72. package/android/src/main/java/cn/jpush/android/local/JPushResponse.java +26 -0
  73. package/android/src/main/java/cn/jpush/android/local/ProxyActivityAction.java +34 -0
  74. package/android/src/main/java/cn/jpush/android/service/AlarmReceiver.java +15 -0
  75. package/android/src/main/java/cn/jpush/android/service/BActivity.java +19 -0
  76. package/android/src/main/java/cn/jpush/android/service/DActivity.java +19 -0
  77. package/android/src/main/java/cn/jpush/android/service/DaemonService.java +17 -0
  78. package/android/src/main/java/cn/jpush/android/service/DataProvider.java +136 -0
  79. package/android/src/main/java/cn/jpush/android/service/DataShare.java +129 -0
  80. package/android/src/main/java/cn/jpush/android/service/DownloadProvider.java +139 -0
  81. package/android/src/main/java/cn/jpush/android/service/InitProvider.java +48 -0
  82. package/android/src/main/java/cn/jpush/android/service/JCommonService.java +79 -0
  83. package/android/src/main/java/cn/jpush/android/service/JNotifyActivity.java +28 -0
  84. package/android/src/main/java/cn/jpush/android/service/JPushMessageReceiver.java +93 -0
  85. package/android/src/main/java/cn/jpush/android/service/PushReceiver.java +26 -0
  86. package/android/src/main/java/cn/jpush/android/service/PushService.java +28 -0
  87. package/android/src/main/java/cn/jpush/android/service/SchedulerReceiver.java +17 -0
  88. package/android/src/main/java/cn/jpush/android/service/TagAliasReceiver.java +11 -0
  89. package/android/src/main/java/cn/jpush/android/service/WakedResultReceiver.java +24 -0
  90. package/android/src/main/java/cn/jpush/android/ui/FullScreenView.java +11 -0
  91. package/android/src/main/java/cn/jpush/android/ui/PopWinActivity.java +16 -0
  92. package/android/src/main/java/cn/jpush/android/ui/PushActivity.java +27 -0
  93. package/android/src/main/java/cn/jpush/android/ui/RoundedImageView.java +63 -0
  94. package/android/src/main/java/cn/jpush/android/ui/ShadowViewCard.java +56 -0
  95. package/android/src/main/java/cn/jpush/android/ups/ICallbackResult.java +5 -0
  96. package/android/src/main/java/cn/jpush/android/ups/JPushUPSManager.java +37 -0
  97. package/android/src/main/java/cn/jpush/android/ups/TokenResult.java +47 -0
  98. package/android/src/main/java/cn/jpush/android/ups/UPSRegisterCallBack.java +4 -0
  99. package/android/src/main/java/cn/jpush/android/ups/UPSTurnCallBack.java +4 -0
  100. package/android/src/main/java/cn/jpush/android/ups/UPSUnRegisterCallBack.java +4 -0
  101. package/android/src/main/java/com/margelo/nitro/reactnativejpush/ReactNativeJpush.kt +455 -0
  102. package/android/src/main/java/com/margelo/nitro/reactnativejpush/ReactNativeJpushPackage.kt +50 -0
  103. package/android/src/main/res/values/jpush_string.xml +7 -0
  104. package/android/src/main/res/values/jpush_style.xml +22 -0
  105. package/android/src/test/java/com/margelo/nitro/reactnativejpush/ReactNativeJpushProtocolTest.java +1016 -0
  106. package/ios/ReactNativeJpush.swift +522 -0
  107. package/ios/bridge/RCTJCoreModule.h +5 -0
  108. package/ios/bridge/RCTJCoreModule.m +31 -0
  109. package/ios/bridge/RCTJPushEventQueue.h +14 -0
  110. package/ios/bridge/RCTJPushEventQueue.m +23 -0
  111. package/ios/bridge/RCTJPushModule.h +37 -0
  112. package/ios/bridge/RCTJPushModule.m +1165 -0
  113. package/ios/include/JCOREService.h +3 -0
  114. package/ios/include/JGCOREAPI.h +14 -0
  115. package/ios/include/JGInforCollectionAuth.h +17 -0
  116. package/ios/include/JGInforCollectionAuthItems.h +2 -0
  117. package/ios/include/JGPushNotificationTrigger.h +2 -0
  118. package/ios/include/JPUSHService.h +359 -0
  119. package/ios/jcore/JCOREConfiguration.h +141 -0
  120. package/ios/jcore/JCOREConfiguration.m +703 -0
  121. package/ios/jcore/JCORECrypto.h +73 -0
  122. package/ios/jcore/JCORECrypto.m +649 -0
  123. package/ios/jcore/JCORESisResolver.h +56 -0
  124. package/ios/jcore/JCORESisResolver.m +525 -0
  125. package/ios/jcore/JCORETcpSession.h +42 -0
  126. package/ios/jcore/JCORETcpSession.m +956 -0
  127. package/ios/jcore/JCOREWireProtocol.h +274 -0
  128. package/ios/jcore/JCOREWireProtocol.m +1453 -0
  129. package/ios/jpush/JPUSHModels.m +214 -0
  130. package/ios/jpush/JPUSHNotificationManager.h +34 -0
  131. package/ios/jpush/JPUSHNotificationManager.m +360 -0
  132. package/ios/jpush/JPUSHService.m +794 -0
  133. package/ios/tests/ReactNativeJpushTests.m +148 -0
  134. package/lib/module/ReactNativeJpush.nitro.js +4 -0
  135. package/lib/module/ReactNativeJpush.nitro.js.map +1 -0
  136. package/lib/module/index.js +801 -0
  137. package/lib/module/index.js.map +1 -0
  138. package/lib/module/package.json +1 -0
  139. package/lib/typescript/package.json +1 -0
  140. package/lib/typescript/src/ReactNativeJpush.nitro.d.ts +60 -0
  141. package/lib/typescript/src/ReactNativeJpush.nitro.d.ts.map +1 -0
  142. package/lib/typescript/src/index.d.ts +235 -0
  143. package/lib/typescript/src/index.d.ts.map +1 -0
  144. package/nitro.json +23 -0
  145. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  146. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__string.hpp +76 -0
  147. package/nitrogen/generated/android/c++/JHybridReactNativeJpushSpec.cpp +310 -0
  148. package/nitrogen/generated/android/c++/JHybridReactNativeJpushSpec.hpp +115 -0
  149. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/Func_void_bool.kt +78 -0
  150. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/Func_void_std__string_std__string.kt +78 -0
  151. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/HybridReactNativeJpushSpec.kt +274 -0
  152. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativejpush/reactnativejpushOnLoad.kt +35 -0
  153. package/nitrogen/generated/android/reactnativejpush+autolinking.cmake +81 -0
  154. package/nitrogen/generated/android/reactnativejpush+autolinking.gradle +27 -0
  155. package/nitrogen/generated/android/reactnativejpushOnLoad.cpp +58 -0
  156. package/nitrogen/generated/android/reactnativejpushOnLoad.hpp +34 -0
  157. package/nitrogen/generated/ios/ReactNativeJpush+autolinking.rb +62 -0
  158. package/nitrogen/generated/ios/ReactNativeJpush-Swift-Cxx-Bridge.cpp +49 -0
  159. package/nitrogen/generated/ios/ReactNativeJpush-Swift-Cxx-Bridge.hpp +147 -0
  160. package/nitrogen/generated/ios/ReactNativeJpush-Swift-Cxx-Umbrella.hpp +46 -0
  161. package/nitrogen/generated/ios/ReactNativeJpushAutolinking.mm +33 -0
  162. package/nitrogen/generated/ios/ReactNativeJpushAutolinking.swift +26 -0
  163. package/nitrogen/generated/ios/c++/HybridReactNativeJpushSpecSwift.cpp +11 -0
  164. package/nitrogen/generated/ios/c++/HybridReactNativeJpushSpecSwift.hpp +402 -0
  165. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  166. package/nitrogen/generated/ios/swift/Func_void_std__string_std__string.swift +46 -0
  167. package/nitrogen/generated/ios/swift/HybridReactNativeJpushSpec.swift +107 -0
  168. package/nitrogen/generated/ios/swift/HybridReactNativeJpushSpec_cxx.swift +723 -0
  169. package/nitrogen/generated/shared/c++/HybridReactNativeJpushSpec.cpp +73 -0
  170. package/nitrogen/generated/shared/c++/HybridReactNativeJpushSpec.hpp +116 -0
  171. package/package.json +173 -4
  172. package/src/ReactNativeJpush.nitro.ts +93 -0
  173. package/src/index.tsx +1090 -0
@@ -0,0 +1,1453 @@
1
+ #import "JCOREWireProtocol.h"
2
+ #import "JCOREConfiguration.h"
3
+ #import "JCORECrypto.h"
4
+ #if __has_include(<UIKit/UIKit.h>)
5
+ #import <UIKit/UIKit.h>
6
+ #endif
7
+
8
+ static NSString *const kJCOREReportRsaPublicKey =
9
+ @"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCGHnOJjf1yD7SOWt0w4q5ZqER+VMOTJ9oPh+"
10
+ @"9TNgW8WpXwCklSLxFDM46eHWzT2TV46gZ0A6xTNVHKX4zAW621jfb6qzljTvwzM5Cxa5n2440kU8HF/"
11
+ @"EDu04yqUpWCsNXs+jQD4R2gjGE203LW87fBivmfq0mxPvIR94UdKf10YQIDAQAB";
12
+
13
+ static void JCoreAppendUInt8(NSMutableData *data, uint8_t value) {
14
+ [data appendBytes:&value length:sizeof(uint8_t)];
15
+ }
16
+
17
+ static void JCoreAppendUInt16(NSMutableData *data, uint16_t value) {
18
+ uint16_t be = CFSwapInt16HostToBig(value);
19
+ [data appendBytes:&be length:sizeof(uint16_t)];
20
+ }
21
+
22
+ static void JCoreAppendUInt32(NSMutableData *data, uint32_t value) {
23
+ uint32_t be = CFSwapInt32HostToBig(value);
24
+ [data appendBytes:&be length:sizeof(uint32_t)];
25
+ }
26
+
27
+ static void JCoreAppendUInt64(NSMutableData *data, uint64_t value) {
28
+ uint64_t be = CFSwapInt64HostToBig(value);
29
+ [data appendBytes:&be length:sizeof(uint64_t)];
30
+ }
31
+
32
+ static void JCoreAppendString(NSMutableData *data, NSString *string) {
33
+ NSData *utf8 = [string ?: @"" dataUsingEncoding:NSUTF8StringEncoding];
34
+ uint16_t len = (uint16_t)MIN((NSUInteger)UINT16_MAX, utf8.length);
35
+ JCoreAppendUInt16(data, len);
36
+ if (len > 0) {
37
+ [data appendBytes:utf8.bytes length:len];
38
+ }
39
+ }
40
+
41
+ static BOOL JCoreReadUInt8(NSData *data, NSUInteger *offset, uint8_t *outValue) {
42
+ if (*offset + 1 > data.length) {
43
+ return NO;
44
+ }
45
+ [data getBytes:outValue range:NSMakeRange(*offset, 1)];
46
+ *offset += 1;
47
+ return YES;
48
+ }
49
+
50
+ static BOOL JCoreReadUInt16(NSData *data, NSUInteger *offset, uint16_t *outValue) {
51
+ if (*offset + 2 > data.length) {
52
+ return NO;
53
+ }
54
+ uint16_t raw = 0;
55
+ [data getBytes:&raw range:NSMakeRange(*offset, 2)];
56
+ *outValue = CFSwapInt16BigToHost(raw);
57
+ *offset += 2;
58
+ return YES;
59
+ }
60
+
61
+ static BOOL JCoreReadUInt32(NSData *data, NSUInteger *offset, uint32_t *outValue) {
62
+ if (*offset + 4 > data.length) {
63
+ return NO;
64
+ }
65
+ uint32_t raw = 0;
66
+ [data getBytes:&raw range:NSMakeRange(*offset, 4)];
67
+ *outValue = CFSwapInt32BigToHost(raw);
68
+ *offset += 4;
69
+ return YES;
70
+ }
71
+
72
+ static BOOL JCoreReadUInt64(NSData *data, NSUInteger *offset, uint64_t *outValue) {
73
+ if (*offset + 8 > data.length) {
74
+ return NO;
75
+ }
76
+ uint64_t raw = 0;
77
+ [data getBytes:&raw range:NSMakeRange(*offset, 8)];
78
+ *outValue = CFSwapInt64BigToHost(raw);
79
+ *offset += 8;
80
+ return YES;
81
+ }
82
+
83
+ static NSString *JCoreReadString(NSData *data, NSUInteger *offset) {
84
+ uint16_t len = 0;
85
+ if (!JCoreReadUInt16(data, offset, &len)) {
86
+ return nil;
87
+ }
88
+ if (*offset + len > data.length) {
89
+ return nil;
90
+ }
91
+ NSData *slice = [data subdataWithRange:NSMakeRange(*offset, len)];
92
+ *offset += len;
93
+ return [[NSString alloc] initWithData:slice encoding:NSUTF8StringEncoding] ?: @"";
94
+ }
95
+
96
+ @implementation JCOREWireFrame
97
+ @end
98
+
99
+ @implementation JCORERegisterResponse
100
+ @end
101
+
102
+ @implementation JCORELoginResponse
103
+ @end
104
+
105
+ @implementation JCOREPushEnvelope
106
+ @end
107
+
108
+ @implementation JCORECtrlEnvelope
109
+ @end
110
+
111
+ @implementation JCORELiveActivityResponse
112
+ @end
113
+
114
+ @implementation JCOREWireProtocol
115
+
116
+ #pragma mark - Body Encryption & Decryption
117
+
118
+ + (nullable NSData *)encryptBodyData:(NSData *)plainData
119
+ withSeed:(uint64_t)seed
120
+ algorithm:(uint8_t)algorithm {
121
+ NSData *input = plainData ?: [NSData data];
122
+ NSString *key = [JCORECrypto generateSeedKey:seed];
123
+ NSString *ivStr = [key substringToIndex:MIN((NSUInteger)16, key.length)];
124
+ if (algorithm == 2) {
125
+ return [JCORECrypto sm4CBCEncryptData:input withKey:key iv:ivStr.UTF8String];
126
+ }
127
+ return [JCORECrypto aesCbcEncrypt:input key:key iv:ivStr.UTF8String keySize:32];
128
+ }
129
+
130
+ + (nullable NSData *)decryptBodyData:(NSData *)cipherData
131
+ withSeed:(uint64_t)seed
132
+ algorithm:(uint8_t)algorithm {
133
+ if (cipherData.length == 0) {
134
+ return [NSData data];
135
+ }
136
+ NSString *key = [JCORECrypto generateSeedKey:seed];
137
+ NSString *ivStr = [key substringToIndex:MIN((NSUInteger)16, key.length)];
138
+ if (algorithm == 2) {
139
+ return [JCORECrypto sm4CBCDecryptData:cipherData withKey:key iv:ivStr.UTF8String];
140
+ }
141
+ return [JCORECrypto aesCbcDecrypt:cipherData key:key iv:ivStr.UTF8String keySize:32];
142
+ }
143
+
144
+ #pragma mark - Outbound Client -> Server Framing (24-byte header)
145
+
146
+ + (NSData *)encodeClientFrameWithCommand:(uint8_t)command
147
+ version:(uint8_t)version
148
+ requestId:(uint16_t)requestId
149
+ sessionId:(uint32_t)sessionId
150
+ uid:(uint64_t)uid
151
+ algorithm:(uint8_t)algorithm
152
+ bodyData:(NSData *)bodyData {
153
+ uint8_t effectiveAlg = (algorithm == 2) ? 2 : 1;
154
+ uint32_t seedOrSid = sessionId;
155
+ uint64_t packetUid = uid;
156
+
157
+ if (command == JCOREWireCommandRegister) {
158
+ packetUid = 0;
159
+ if (seedOrSid == 0) {
160
+ uint64_t savedSeed = [JCOREConfiguration shared].lastRegisterSeed;
161
+ if (savedSeed > 0 && savedSeed <= UINT32_MAX) {
162
+ seedOrSid = (uint32_t)savedSeed;
163
+ } else {
164
+ seedOrSid = (arc4random() % 10000000U) + 1U;
165
+ [JCOREConfiguration shared].lastRegisterSeed = seedOrSid;
166
+ }
167
+ } else {
168
+ [JCOREConfiguration shared].lastRegisterSeed = seedOrSid;
169
+ }
170
+ }
171
+
172
+ uint64_t encryptionSeed = (command == JCOREWireCommandRegister || packetUid == 0)
173
+ ? (uint64_t)seedOrSid
174
+ : packetUid;
175
+
176
+ NSData *encryptedBody = [self encryptBodyData:(bodyData ?: [NSData data])
177
+ withSeed:encryptionSeed
178
+ algorithm:effectiveAlg];
179
+ if (encryptedBody == nil) {
180
+ return nil;
181
+ }
182
+
183
+ uint16_t totalLength = (uint16_t)(24 + encryptedBody.length);
184
+ NSMutableData *packet = [NSMutableData dataWithCapacity:totalLength];
185
+ JCoreAppendUInt16(packet, (uint16_t)(totalLength | 0x8000));
186
+ JCoreAppendUInt8(packet, version);
187
+ JCoreAppendUInt8(packet, command);
188
+ JCoreAppendUInt8(packet, effectiveAlg);
189
+ JCoreAppendUInt8(packet, 0); // flag
190
+ JCoreAppendUInt32(packet, 0); // reserved
191
+ JCoreAppendUInt16(packet, requestId);
192
+ JCoreAppendUInt32(packet, seedOrSid);
193
+ JCoreAppendUInt64(packet, packetUid);
194
+ if (encryptedBody.length > 0) {
195
+ [packet appendData:encryptedBody];
196
+ }
197
+ return packet;
198
+ }
199
+
200
+ + (NSData *)encodeFrameWithCommand:(uint8_t)command
201
+ version:(uint8_t)version
202
+ requestId:(uint16_t)requestId
203
+ sessionId:(uint32_t)sessionId
204
+ uid:(uint64_t)uid
205
+ bodyData:(NSData *)bodyData {
206
+ uint8_t alg = [JCOREConfiguration shared].encryptionAlgorithm;
207
+ if (alg != 1 && alg != 2) {
208
+ alg = 1;
209
+ }
210
+ return [self encodeClientFrameWithCommand:command
211
+ version:version
212
+ requestId:requestId
213
+ sessionId:sessionId
214
+ uid:uid
215
+ algorithm:alg
216
+ bodyData:bodyData];
217
+ }
218
+
219
+ #pragma mark - Inbound Server -> Client Framing (20-byte header)
220
+
221
+ + (NSData *)encodeServerFrameWithCommand:(uint8_t)command
222
+ version:(uint8_t)version
223
+ requestId:(uint16_t)requestId
224
+ uid:(uint64_t)uid
225
+ algorithm:(uint8_t)algorithm
226
+ statusCode:(uint16_t)statusCode
227
+ bodyData:(NSData *)bodyData {
228
+ uint8_t effectiveAlg = (algorithm == 2) ? 2 : 1;
229
+ NSMutableData *plainBody = [NSMutableData data];
230
+
231
+ BOOL isServerPushOrCtrl = (command == JCOREWireCommandPushMessage ||
232
+ command == JCOREWireCommandCtrlCommand);
233
+ BOOL isTagOrAlias = (command == JCOREWireCommandTagOperation ||
234
+ command == JCOREWireCommandAliasOperation);
235
+
236
+ if (isServerPushOrCtrl) {
237
+ if (bodyData.length > 0) {
238
+ [plainBody appendData:bodyData];
239
+ }
240
+ } else if (isTagOrAlias) {
241
+ if (bodyData.length > 0) {
242
+ [plainBody appendData:bodyData];
243
+ } else {
244
+ JCoreAppendUInt16(plainBody, statusCode);
245
+ }
246
+ } else {
247
+ if (bodyData.length == 0) {
248
+ JCoreAppendUInt16(plainBody, statusCode);
249
+ } else {
250
+ [plainBody appendData:bodyData];
251
+ if (statusCode != 0 && plainBody.length >= 2) {
252
+ uint16_t existingStatus = 0;
253
+ NSUInteger zeroOffset = 0;
254
+ JCoreReadUInt16(plainBody, &zeroOffset, &existingStatus);
255
+ if (existingStatus == 0) {
256
+ uint16_t beStatus = CFSwapInt16HostToBig(statusCode);
257
+ [plainBody replaceBytesInRange:NSMakeRange(0, 2) withBytes:&beStatus length:2];
258
+ }
259
+ }
260
+ }
261
+ }
262
+
263
+ uint64_t encryptionSeed = uid;
264
+ if (command == JCOREWireCommandRegister) {
265
+ uint64_t regSeed = [JCOREConfiguration shared].lastRegisterSeed;
266
+ encryptionSeed = (regSeed != 0) ? regSeed : uid;
267
+ } else if (encryptionSeed == 0) {
268
+ encryptionSeed = [JCOREConfiguration shared].lastRegisterSeed;
269
+ }
270
+
271
+ NSData *encryptedBody = [self encryptBodyData:plainBody
272
+ withSeed:encryptionSeed
273
+ algorithm:effectiveAlg];
274
+ if (encryptedBody == nil) {
275
+ return nil;
276
+ }
277
+
278
+ uint16_t totalLength = (uint16_t)(20 + encryptedBody.length);
279
+ NSMutableData *packet = [NSMutableData dataWithCapacity:totalLength];
280
+ JCoreAppendUInt16(packet, (uint16_t)(totalLength | 0x8000));
281
+ JCoreAppendUInt8(packet, version);
282
+ JCoreAppendUInt8(packet, command);
283
+ JCoreAppendUInt8(packet, effectiveAlg);
284
+ JCoreAppendUInt8(packet, 0); // flag
285
+ JCoreAppendUInt32(packet, 0); // reserved
286
+ JCoreAppendUInt16(packet, requestId);
287
+ JCoreAppendUInt64(packet, uid);
288
+ if (encryptedBody.length > 0) {
289
+ [packet appendData:encryptedBody];
290
+ }
291
+ return packet;
292
+ }
293
+
294
+ + (NSData *)encodeFrameWithCommand:(uint8_t)command
295
+ version:(uint8_t)version
296
+ requestId:(uint16_t)requestId
297
+ sessionId:(uint32_t)sessionId
298
+ uid:(uint64_t)uid
299
+ statusCode:(uint16_t)statusCode
300
+ bodyData:(NSData *)bodyData {
301
+ (void)sessionId;
302
+ uint8_t alg = [JCOREConfiguration shared].encryptionAlgorithm;
303
+ if (alg != 1 && alg != 2) {
304
+ alg = 1;
305
+ }
306
+ return [self encodeServerFrameWithCommand:command
307
+ version:version
308
+ requestId:requestId
309
+ uid:uid
310
+ algorithm:alg
311
+ statusCode:statusCode
312
+ bodyData:bodyData];
313
+ }
314
+
315
+ #pragma mark - Stream Decoders
316
+
317
+ + (nullable JCOREWireFrame *)decodeClientSingleFrame:(NSData *)frameBytes {
318
+ if (frameBytes.length < 24) {
319
+ return nil;
320
+ }
321
+ NSUInteger cursor = 2;
322
+ uint8_t version = 0;
323
+ uint8_t command = 0;
324
+ uint8_t algorithm = 1;
325
+ uint8_t flag = 0;
326
+ uint32_t reserved = 0;
327
+ uint16_t requestId = 0;
328
+ uint32_t seedOrSid = 0;
329
+ uint64_t packetUid = 0;
330
+
331
+ if (!JCoreReadUInt8(frameBytes, &cursor, &version) ||
332
+ !JCoreReadUInt8(frameBytes, &cursor, &command) ||
333
+ !JCoreReadUInt8(frameBytes, &cursor, &algorithm) ||
334
+ !JCoreReadUInt8(frameBytes, &cursor, &flag) ||
335
+ !JCoreReadUInt32(frameBytes, &cursor, &reserved) ||
336
+ !JCoreReadUInt16(frameBytes, &cursor, &requestId) ||
337
+ !JCoreReadUInt32(frameBytes, &cursor, &seedOrSid) ||
338
+ !JCoreReadUInt64(frameBytes, &cursor, &packetUid)) {
339
+ return nil;
340
+ }
341
+
342
+ if (command == JCOREWireCommandRegister && seedOrSid != 0) {
343
+ [JCOREConfiguration shared].lastRegisterSeed = seedOrSid;
344
+ }
345
+
346
+ NSData *rawBody = frameBytes.length > 24
347
+ ? [frameBytes subdataWithRange:NSMakeRange(24, frameBytes.length - 24)]
348
+ : [NSData data];
349
+
350
+ uint64_t seed = (command == JCOREWireCommandRegister || packetUid == 0)
351
+ ? (uint64_t)seedOrSid
352
+ : packetUid;
353
+
354
+ NSData *decodedBody = [self decryptBodyData:rawBody withSeed:seed algorithm:algorithm];
355
+ if (decodedBody == nil) {
356
+ return nil;
357
+ }
358
+
359
+ JCOREWireFrame *frame = [[JCOREWireFrame alloc] init];
360
+ frame.version = version;
361
+ frame.command = command;
362
+ frame.algorithm = algorithm;
363
+ frame.flags = flag;
364
+ frame.requestId = requestId;
365
+ frame.sessionId = seedOrSid;
366
+ frame.uid = packetUid;
367
+ frame.statusCode = 0;
368
+ frame.bodyData = decodedBody;
369
+ return frame;
370
+ }
371
+
372
+ + (nullable JCOREWireFrame *)decodeServerSingleFrame:(NSData *)frameBytes
373
+ defaultUid:(uint64_t)defaultUid {
374
+ if (frameBytes.length < 20) {
375
+ return nil;
376
+ }
377
+ NSUInteger cursor = 2;
378
+ uint8_t version = 0;
379
+ uint8_t command = 0;
380
+ uint8_t algorithm = 1;
381
+ uint8_t flag = 0;
382
+ uint32_t reserved = 0;
383
+ uint16_t requestId = 0;
384
+ uint64_t packetUid = 0;
385
+
386
+ if (!JCoreReadUInt8(frameBytes, &cursor, &version) ||
387
+ !JCoreReadUInt8(frameBytes, &cursor, &command) ||
388
+ !JCoreReadUInt8(frameBytes, &cursor, &algorithm) ||
389
+ !JCoreReadUInt8(frameBytes, &cursor, &flag) ||
390
+ !JCoreReadUInt32(frameBytes, &cursor, &reserved) ||
391
+ !JCoreReadUInt16(frameBytes, &cursor, &requestId) ||
392
+ !JCoreReadUInt64(frameBytes, &cursor, &packetUid)) {
393
+ return nil;
394
+ }
395
+
396
+ NSData *rawBody = frameBytes.length > 20
397
+ ? [frameBytes subdataWithRange:NSMakeRange(20, frameBytes.length - 20)]
398
+ : [NSData data];
399
+
400
+ uint64_t lastRegSeed = [JCOREConfiguration shared].lastRegisterSeed;
401
+ uint64_t primarySeed = 0;
402
+ if (command == JCOREWireCommandRegister) {
403
+ primarySeed = (lastRegSeed != 0) ? lastRegSeed : (packetUid != 0 ? packetUid : defaultUid);
404
+ } else {
405
+ primarySeed = (packetUid != 0) ? packetUid : (defaultUid != 0 ? defaultUid : lastRegSeed);
406
+ }
407
+
408
+ NSData *decodedBody = [self decryptBodyData:rawBody withSeed:primarySeed algorithm:algorithm];
409
+ if (decodedBody == nil && packetUid != 0 && packetUid != primarySeed) {
410
+ decodedBody = [self decryptBodyData:rawBody withSeed:packetUid algorithm:algorithm];
411
+ }
412
+ if (decodedBody == nil && defaultUid != 0 && defaultUid != primarySeed && defaultUid != packetUid) {
413
+ decodedBody = [self decryptBodyData:rawBody withSeed:defaultUid algorithm:algorithm];
414
+ }
415
+ if (decodedBody == nil && lastRegSeed != 0 && lastRegSeed != primarySeed) {
416
+ decodedBody = [self decryptBodyData:rawBody withSeed:lastRegSeed algorithm:algorithm];
417
+ }
418
+ if (decodedBody == nil) {
419
+ return nil;
420
+ }
421
+
422
+ if ((flag & 0x01) != 0 && decodedBody.length >= 2) {
423
+ const uint8_t *b = decodedBody.bytes;
424
+ if (b[0] == 0x1f && b[1] == 0x8b) {
425
+ NSData *unzipped = [JCORECrypto gzipDecompress:decodedBody];
426
+ if (unzipped != nil) {
427
+ decodedBody = unzipped;
428
+ }
429
+ }
430
+ }
431
+
432
+ uint16_t statusCode = 0;
433
+ if (command != JCOREWireCommandPushMessage && command != JCOREWireCommandCtrlCommand) {
434
+ if (command == JCOREWireCommandTagOperation || command == JCOREWireCommandAliasOperation) {
435
+ if (decodedBody.length == 2) {
436
+ NSUInteger scOffset = 0;
437
+ JCoreReadUInt16(decodedBody, &scOffset, &statusCode);
438
+ }
439
+ } else if (decodedBody.length >= 2) {
440
+ NSUInteger scOffset = 0;
441
+ JCoreReadUInt16(decodedBody, &scOffset, &statusCode);
442
+ }
443
+ }
444
+
445
+ JCOREWireFrame *frame = [[JCOREWireFrame alloc] init];
446
+ frame.version = version;
447
+ frame.command = command;
448
+ frame.algorithm = algorithm;
449
+ frame.flags = flag;
450
+ frame.requestId = requestId;
451
+ frame.sessionId = 0;
452
+ frame.uid = packetUid;
453
+ frame.statusCode = statusCode;
454
+ frame.bodyData = decodedBody;
455
+ return frame;
456
+ }
457
+
458
+ + (NSArray<JCOREWireFrame *> *)decodeServerFramesFromStreamBuffer:(NSMutableData *)streamBuffer
459
+ defaultUid:(uint64_t)defaultUid {
460
+ NSMutableArray<JCOREWireFrame *> *frames = [NSMutableArray array];
461
+ while (streamBuffer.length >= 20) {
462
+ const uint8_t *bytes = streamBuffer.bytes;
463
+ uint16_t rawLen = ((uint16_t)bytes[0] << 8) | bytes[1];
464
+ uint16_t frameLen = rawLen & 0x7FFF;
465
+ if (frameLen < 20 || frameLen > 65535) {
466
+ [streamBuffer setLength:0];
467
+ break;
468
+ }
469
+ if (streamBuffer.length < frameLen) {
470
+ break;
471
+ }
472
+ NSData *frameBytes = [streamBuffer subdataWithRange:NSMakeRange(0, frameLen)];
473
+ [streamBuffer replaceBytesInRange:NSMakeRange(0, frameLen) withBytes:NULL length:0];
474
+
475
+ JCOREWireFrame *frame = [self decodeServerSingleFrame:frameBytes defaultUid:defaultUid];
476
+ if (frame != nil) {
477
+ [frames addObject:frame];
478
+ }
479
+ }
480
+ return frames;
481
+ }
482
+
483
+ + (NSArray<JCOREWireFrame *> *)decodeClientFramesFromStreamBuffer:(NSMutableData *)streamBuffer {
484
+ NSMutableArray<JCOREWireFrame *> *frames = [NSMutableArray array];
485
+ while (streamBuffer.length >= 24) {
486
+ const uint8_t *bytes = streamBuffer.bytes;
487
+ uint16_t rawLen = ((uint16_t)bytes[0] << 8) | bytes[1];
488
+ uint16_t frameLen = rawLen & 0x7FFF;
489
+ if (frameLen < 24 || frameLen > 65535) {
490
+ [streamBuffer setLength:0];
491
+ break;
492
+ }
493
+ if (streamBuffer.length < frameLen) {
494
+ break;
495
+ }
496
+ NSData *frameBytes = [streamBuffer subdataWithRange:NSMakeRange(0, frameLen)];
497
+ [streamBuffer replaceBytesInRange:NSMakeRange(0, frameLen) withBytes:NULL length:0];
498
+
499
+ JCOREWireFrame *frame = [self decodeClientSingleFrame:frameBytes];
500
+ if (frame != nil) {
501
+ [frames addObject:frame];
502
+ }
503
+ }
504
+ return frames;
505
+ }
506
+
507
+ + (NSArray<JCOREWireFrame *> *)decodeFramesFromStreamBuffer:(NSMutableData *)streamBuffer
508
+ uid:(uint64_t)uid {
509
+ NSMutableArray<JCOREWireFrame *> *frames = [NSMutableArray array];
510
+ while (streamBuffer.length >= 20) {
511
+ const uint8_t *bytes = streamBuffer.bytes;
512
+ uint16_t rawLen = ((uint16_t)bytes[0] << 8) | bytes[1];
513
+ uint16_t frameLen = rawLen & 0x7FFF;
514
+ if (frameLen < 20 || frameLen > 65535) {
515
+ [streamBuffer setLength:0];
516
+ break;
517
+ }
518
+ if (streamBuffer.length < frameLen) {
519
+ break;
520
+ }
521
+ NSData *frameBytes = [streamBuffer subdataWithRange:NSMakeRange(0, frameLen)];
522
+ [streamBuffer replaceBytesInRange:NSMakeRange(0, frameLen) withBytes:NULL length:0];
523
+
524
+ JCOREWireFrame *frame = nil;
525
+ if (frameLen >= 24 && ((frameLen - 24) % 16 == 0)) {
526
+ frame = [self decodeClientSingleFrame:frameBytes];
527
+ } else {
528
+ frame = [self decodeServerSingleFrame:frameBytes defaultUid:uid];
529
+ }
530
+ if (frame != nil) {
531
+ [frames addObject:frame];
532
+ }
533
+ }
534
+ return frames;
535
+ }
536
+
537
+ #pragma mark - Register (cmd = 0, ver = 30)
538
+
539
+ + (NSData *)buildRegisterPayloadWithAppKey:(NSString *)appKey
540
+ channel:(NSString *)channel
541
+ bundleIdHash:(NSString *)bundleId {
542
+ JCOREConfiguration *config = [JCOREConfiguration shared];
543
+ NSString *systemVersion = [config osVersionString];
544
+ NSString *modelName = [config modelNameString];
545
+ NSString *appVersion = [config appVersionString];
546
+ NSString *effectiveBundleId = bundleId.length > 0 ? bundleId : [config bundleIdentifierString];
547
+ NSString *deviceId = config.deviceID;
548
+ NSString *fakeImei = [config fakeIMEI];
549
+
550
+ NSString *keyString = [NSString stringWithFormat:@"%@$$ $$%@$$%@",
551
+ fakeImei ?: @"",
552
+ effectiveBundleId ?: @"",
553
+ appKey ?: @""];
554
+ NSString *clientString = [NSString stringWithFormat:@"%@$$%@$$ $$ $$%@$$6.0.0|||5.2.0||||$$ $$ $$ $$ $$1",
555
+ systemVersion ?: @"",
556
+ modelName ?: @"",
557
+ channel.length > 0 ? channel : @"App Store"];
558
+ NSString *deviceToken = config.deviceTokenHex ?: @"";
559
+ NSString *voipToken = config.voipTokenHex ?: @"";
560
+ NSString *tokenString = (deviceToken.length == 0 && voipToken.length == 0)
561
+ ? @" "
562
+ : [NSString stringWithFormat:@"%@$$%@", deviceToken, voipToken];
563
+ NSString *advertisingString = config.advertisingIdentifier.length > 0
564
+ ? config.advertisingIdentifier
565
+ : @" ";
566
+ NSString *deviceString = [NSString stringWithFormat:@"1$$%@", deviceId ?: @""];
567
+
568
+ NSMutableData *body = [NSMutableData data];
569
+ JCoreAppendString(body, keyString); // 1. key
570
+ JCoreAppendString(body, appVersion ?: @""); // 2. appVersion
571
+ JCoreAppendString(body, clientString); // 3. client
572
+ JCoreAppendString(body, tokenString); // 4. token
573
+ JCoreAppendString(body, advertisingString); // 5. advertising
574
+ JCoreAppendUInt8(body, config.buildType); // 6. buildType (0=debug, 1=release)
575
+ JCoreAppendUInt8(body, config.apsType); // 7. apsType (0=dev, 1=prod)
576
+ JCoreAppendUInt8(body, 1); // 8. platform (1=iOS)
577
+ JCoreAppendString(body, deviceString); // 9. device ("1$${deviceID}")
578
+ JCoreAppendUInt32(body, 1); // 10. integrateBusiness (1=push)
579
+ JCoreAppendString(body, @""); // 11. account
580
+ return body;
581
+ }
582
+
583
+ + (NSData *)buildRegisterResponsePayloadWithStatusCode:(uint16_t)statusCode
584
+ uid:(uint64_t)uid
585
+ password:(NSString *)password
586
+ registrationID:(NSString *)registrationID
587
+ deviceID:(NSString *)deviceID {
588
+ NSMutableData *body = [NSMutableData data];
589
+ JCoreAppendUInt16(body, statusCode);
590
+ if (statusCode == 0) {
591
+ JCoreAppendUInt64(body, uid);
592
+ JCoreAppendString(body, password ?: @"");
593
+ JCoreAppendString(body, registrationID ?: @"");
594
+ JCoreAppendString(body, deviceID ?: @"");
595
+ }
596
+ return body;
597
+ }
598
+
599
+ + (NSData *)buildRegisterResponsePayloadWithUid:(uint64_t)uid
600
+ password:(NSString *)password
601
+ registrationID:(NSString *)registrationID {
602
+ return [self buildRegisterResponsePayloadWithStatusCode:0
603
+ uid:uid
604
+ password:password
605
+ registrationID:registrationID
606
+ deviceID:[JCOREConfiguration shared].deviceID];
607
+ }
608
+
609
+ + (JCORERegisterResponse *)parseRegisterResponse:(JCOREWireFrame *)frame {
610
+ if (frame == nil) {
611
+ return nil;
612
+ }
613
+ JCORERegisterResponse *response = [[JCORERegisterResponse alloc] init];
614
+ response.statusCode = frame.statusCode;
615
+
616
+ NSData *body = frame.bodyData;
617
+ if (body.length < 2) {
618
+ return response;
619
+ }
620
+
621
+ NSUInteger offset = 0;
622
+ uint16_t bodyStatus = 0;
623
+ if (!JCoreReadUInt16(body, &offset, &bodyStatus)) {
624
+ return response;
625
+ }
626
+ response.statusCode = bodyStatus;
627
+ if (bodyStatus != 0 || body.length < offset + 8) {
628
+ return response;
629
+ }
630
+
631
+ uint64_t uid = 0;
632
+ if (!JCoreReadUInt64(body, &offset, &uid)) {
633
+ return response;
634
+ }
635
+ response.uid = uid;
636
+ response.password = JCoreReadString(body, &offset);
637
+ response.registrationID = JCoreReadString(body, &offset);
638
+ if (offset + 2 <= body.length) {
639
+ response.deviceID = JCoreReadString(body, &offset);
640
+ }
641
+ return response;
642
+ }
643
+
644
+ #pragma mark - Login (cmd = 1, ver = 27)
645
+
646
+ + (NSData *)buildLoginPayloadWithPassword:(NSString *)password
647
+ appKey:(NSString *)appKey {
648
+ JCOREConfiguration *config = [JCOREConfiguration shared];
649
+ NSString *passwordMd5 = [JCORECrypto md5Hex:password ?: @""];
650
+ NSString *modelName = [config modelNameString];
651
+ NSString *appVersion = [config appVersionString];
652
+ NSString *systemVersion = [config osVersionString];
653
+ NSString *countryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode] ?: @"CN";
654
+
655
+ NSMutableData *body = [NSMutableData data];
656
+ JCoreAppendUInt32(body, 105); // Fixed magic 0x00000069
657
+ JCoreAppendString(body, passwordMd5); // MD5(password)
658
+ JCoreAppendUInt32(body, 0x00050200); // Core SDK version 5.2.0
659
+ JCoreAppendString(body, appKey ?: @""); // appKey
660
+ JCoreAppendUInt8(body, 1); // platform = 1 (iOS)
661
+ JCoreAppendUInt32(body, 0); // idc
662
+ JCoreAppendUInt8(body, config.apsType); // apsType (1=prod, 0=dev)
663
+ JCoreAppendUInt8(body, config.buildType); // buildType (0=debug, 1=release)
664
+ JCoreAppendString(body, [NSString stringWithFormat:@"$$%@$$%@$$ ", modelName, appVersion]);
665
+ JCoreAppendString(body, @"5.2.0|6.0.0"); // versions
666
+ JCoreAppendUInt16(body, 0); // connectInfo
667
+ JCoreAppendString(body, @"|||1$$0$$0$$0"); // extInfo
668
+ JCoreAppendString(body, [NSString stringWithFormat:@"%@;;%@", systemVersion, countryCode]);
669
+ return body;
670
+ }
671
+
672
+ + (NSData *)buildLoginResponsePayloadWithStatusCode:(uint16_t)statusCode
673
+ sessionId:(uint32_t)sessionId
674
+ serverVersion:(uint16_t)serverVersion
675
+ errorMessage:(NSString *)errorMessage
676
+ serverTimestamp:(uint32_t)serverTimestamp
677
+ idc:(int8_t)idc {
678
+ NSMutableData *body = [NSMutableData data];
679
+ JCoreAppendUInt16(body, statusCode);
680
+ if (statusCode == 0) {
681
+ JCoreAppendUInt32(body, sessionId);
682
+ JCoreAppendUInt16(body, serverVersion);
683
+ JCoreAppendString(body, errorMessage ?: @"");
684
+ JCoreAppendUInt32(body, serverTimestamp);
685
+ JCoreAppendUInt8(body, (uint8_t)idc);
686
+ } else {
687
+ JCoreAppendString(body, errorMessage ?: @"");
688
+ }
689
+ return body;
690
+ }
691
+
692
+ + (NSData *)buildLoginResponsePayloadWithSessionId:(uint32_t)sessionId
693
+ serverVersion:(uint16_t)serverVersion
694
+ serverTimestamp:(uint32_t)serverTimestamp {
695
+ return [self buildLoginResponsePayloadWithStatusCode:0
696
+ sessionId:sessionId
697
+ serverVersion:serverVersion
698
+ errorMessage:@""
699
+ serverTimestamp:serverTimestamp
700
+ idc:0];
701
+ }
702
+
703
+ + (JCORELoginResponse *)parseLoginResponse:(JCOREWireFrame *)frame {
704
+ if (frame == nil) {
705
+ return nil;
706
+ }
707
+ JCORELoginResponse *response = [[JCORELoginResponse alloc] init];
708
+ response.statusCode = frame.statusCode;
709
+
710
+ NSData *body = frame.bodyData;
711
+ if (body.length < 2) {
712
+ return response;
713
+ }
714
+
715
+ NSUInteger offset = 0;
716
+ uint16_t bodyStatus = 0;
717
+ if (!JCoreReadUInt16(body, &offset, &bodyStatus)) {
718
+ return response;
719
+ }
720
+ response.statusCode = bodyStatus;
721
+ if (bodyStatus != 0) {
722
+ if (offset + 2 <= body.length) {
723
+ response.errorMessage = JCoreReadString(body, &offset);
724
+ }
725
+ return response;
726
+ }
727
+ if (body.length < offset + 6) {
728
+ return response;
729
+ }
730
+
731
+ uint32_t sid = 0;
732
+ uint16_t serverVer = 0;
733
+ uint32_t serverTime = 0;
734
+ JCoreReadUInt32(body, &offset, &sid);
735
+ JCoreReadUInt16(body, &offset, &serverVer);
736
+ NSString *errMsg = JCoreReadString(body, &offset);
737
+ JCoreReadUInt32(body, &offset, &serverTime);
738
+ uint8_t idcByte = 0;
739
+ if (JCoreReadUInt8(body, &offset, &idcByte)) {
740
+ response.idc = (int8_t)idcByte;
741
+ }
742
+ response.sessionId = sid;
743
+ response.serverVersion = serverVer;
744
+ response.errorMessage = errMsg;
745
+ response.serverTimestamp = serverTime;
746
+ return response;
747
+ }
748
+
749
+ #pragma mark - Heartbeat (cmd = 2, ver = 8)
750
+
751
+ + (NSData *)buildHeartbeatPayloadWithPushStopped:(BOOL)pushStopped {
752
+ NSMutableData *body = [NSMutableData dataWithCapacity:8];
753
+ uint32_t sdkFlag = 1;
754
+ uint8_t appState = pushStopped ? 0 : 1;
755
+ uint16_t beatInterval = 290;
756
+ uint8_t beatType = 1;
757
+ JCoreAppendUInt32(body, sdkFlag);
758
+ JCoreAppendUInt8(body, appState);
759
+ JCoreAppendUInt16(body, beatInterval);
760
+ JCoreAppendUInt8(body, beatType);
761
+ return body;
762
+ }
763
+
764
+ #pragma mark - PushMessage (cmd = 3, ver = 4) & MessageReceipt (cmd = 4, ver = 3)
765
+
766
+ + (NSData *)buildPushMessagePayloadWithMessageType:(uint8_t)messageType
767
+ messageId:(uint64_t)messageId
768
+ content:(NSString *)content {
769
+ NSMutableData *body = [NSMutableData data];
770
+ JCoreAppendUInt8(body, messageType);
771
+ JCoreAppendUInt64(body, messageId);
772
+ JCoreAppendString(body, content ?: @"");
773
+ return body;
774
+ }
775
+
776
+ + (JCOREPushEnvelope *)parsePushEnvelope:(JCOREWireFrame *)frame {
777
+ if (frame.bodyData.length < 11) {
778
+ return nil;
779
+ }
780
+ NSUInteger offset = 0;
781
+ uint8_t msgType = 0;
782
+ uint64_t msgId = 0;
783
+ if (!JCoreReadUInt8(frame.bodyData, &offset, &msgType) ||
784
+ !JCoreReadUInt64(frame.bodyData, &offset, &msgId)) {
785
+ return nil;
786
+ }
787
+ NSString *content = JCoreReadString(frame.bodyData, &offset);
788
+ JCOREPushEnvelope *envelope = [[JCOREPushEnvelope alloc] init];
789
+ envelope.messageType = msgType;
790
+ envelope.messageId = msgId;
791
+ envelope.content = content;
792
+ if (content.length > 0) {
793
+ NSArray<NSString *> *segments = [content componentsSeparatedByString:@"\n"];
794
+ NSString *jsonCandidate = segments.lastObject ?: content;
795
+ NSData *jsonData = [jsonCandidate dataUsingEncoding:NSUTF8StringEncoding];
796
+ id parsed = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
797
+ if ([parsed isKindOfClass:[NSDictionary class]]) {
798
+ envelope.jsonPayload = parsed;
799
+ }
800
+ }
801
+ return envelope;
802
+ }
803
+
804
+ + (NSData *)buildMessageAckPayloadWithStatusCode:(uint16_t)statusCode
805
+ messageType:(uint8_t)messageType
806
+ messageId:(uint64_t)messageId
807
+ msgData:(NSString *)msgData {
808
+ NSMutableData *body = [NSMutableData data];
809
+ JCoreAppendUInt16(body, statusCode);
810
+ JCoreAppendUInt8(body, messageType);
811
+ JCoreAppendUInt64(body, messageId);
812
+ if (msgData != nil) {
813
+ JCoreAppendString(body, msgData);
814
+ }
815
+ return body;
816
+ }
817
+
818
+ + (NSData *)buildMessageAckPayloadWithMessageType:(uint8_t)messageType
819
+ messageId:(uint64_t)messageId {
820
+ return [self buildMessageAckPayloadWithStatusCode:0
821
+ messageType:messageType
822
+ messageId:messageId
823
+ msgData:nil];
824
+ }
825
+
826
+ + (BOOL)parseMessageAckFrame:(JCOREWireFrame *)frame
827
+ outMessageType:(uint8_t *)outMessageType
828
+ outMessageId:(uint64_t *)outMessageId {
829
+ if (frame.bodyData.length < 11) {
830
+ return NO;
831
+ }
832
+ NSUInteger offset = 0;
833
+ uint16_t statusCode = 0;
834
+ uint8_t msgType = 0;
835
+ uint64_t msgId = 0;
836
+ if (!JCoreReadUInt16(frame.bodyData, &offset, &statusCode) ||
837
+ !JCoreReadUInt8(frame.bodyData, &offset, &msgType) ||
838
+ !JCoreReadUInt64(frame.bodyData, &offset, &msgId)) {
839
+ return NO;
840
+ }
841
+ if (outMessageType != NULL) {
842
+ *outMessageType = msgType;
843
+ }
844
+ if (outMessageId != NULL) {
845
+ *outMessageId = msgId;
846
+ }
847
+ return YES;
848
+ }
849
+
850
+ #pragma mark - DeviceToken (cmd = 13, ver = 3) & VoIP Token (cmd = 33, ver = 2)
851
+
852
+ + (NSData *)buildDeviceTokenPayloadWithTokenHex:(NSString *)tokenHex {
853
+ JCOREConfiguration *config = [JCOREConfiguration shared];
854
+ NSMutableData *body = [NSMutableData data];
855
+ JCoreAppendString(body, tokenHex ?: @"");
856
+ JCoreAppendUInt8(body, config.buildType);
857
+ JCoreAppendUInt8(body, config.apsType);
858
+ JCoreAppendUInt8(body, 1);
859
+ return body;
860
+ }
861
+
862
+ + (NSString *)parseDeviceTokenFromFrame:(JCOREWireFrame *)frame {
863
+ if (frame.bodyData.length < 2) {
864
+ return nil;
865
+ }
866
+ NSUInteger offset = 0;
867
+ return JCoreReadString(frame.bodyData, &offset);
868
+ }
869
+
870
+ + (NSData *)buildVoipTokenPayloadWithTokenHex:(NSString *)tokenHex {
871
+ JCOREConfiguration *config = [JCOREConfiguration shared];
872
+ NSMutableData *body = [NSMutableData data];
873
+ JCoreAppendString(body, tokenHex ?: @"");
874
+ JCoreAppendUInt8(body, 1);
875
+ JCoreAppendUInt8(body, config.buildType);
876
+ JCoreAppendUInt8(body, config.apsType);
877
+ return body;
878
+ }
879
+
880
+ + (NSString *)parseVoipTokenFromFrame:(JCOREWireFrame *)frame {
881
+ if (frame.bodyData.length < 2) {
882
+ return nil;
883
+ }
884
+ NSUInteger offset = 0;
885
+ return JCoreReadString(frame.bodyData, &offset);
886
+ }
887
+
888
+ #pragma mark - LiveActivity Token (cmd = 39, ver = 1)
889
+
890
+ + (NSData *)buildLiveActivityPayloadWithTokenHex:(NSString *)tokenHex
891
+ aliasOrAttributesType:(NSString *)aliasOrAttributesType
892
+ isPushToken:(BOOL)isPushToken {
893
+ JCOREConfiguration *config = [JCOREConfiguration shared];
894
+ NSMutableData *body = [NSMutableData data];
895
+ JCoreAppendString(body, tokenHex ?: @"");
896
+ JCoreAppendString(body, aliasOrAttributesType ?: @"");
897
+ JCoreAppendUInt8(body, isPushToken ? 1 : 0);
898
+ JCoreAppendUInt8(body, config.buildType);
899
+ JCoreAppendUInt8(body, config.apsType);
900
+ JCoreAppendUInt8(body, 1);
901
+ return body;
902
+ }
903
+
904
+ + (NSData *)buildLiveActivityResponsePayloadWithStatusCode:(uint16_t)statusCode
905
+ alias:(NSString *)alias {
906
+ NSMutableData *body = [NSMutableData data];
907
+ JCoreAppendUInt16(body, statusCode);
908
+ JCoreAppendString(body, alias ?: @"");
909
+ return body;
910
+ }
911
+
912
+ + (NSInteger)mapLiveActivityStatusCode:(uint16_t)statusCode {
913
+ switch (statusCode) {
914
+ case 0:
915
+ return 0;
916
+ case 3900:
917
+ return 6039;
918
+ case 3901:
919
+ return 6040;
920
+ case 3902:
921
+ return 6042;
922
+ case 3950:
923
+ return 6041;
924
+ default:
925
+ return (NSInteger)statusCode;
926
+ }
927
+ }
928
+
929
+ + (JCORELiveActivityResponse *)parseLiveActivityResponse:(JCOREWireFrame *)frame {
930
+ if (frame == nil) {
931
+ return nil;
932
+ }
933
+ JCORELiveActivityResponse *resp = [[JCORELiveActivityResponse alloc] init];
934
+ resp.statusCode = frame.statusCode;
935
+ if (frame.bodyData.length >= 2) {
936
+ NSUInteger offset = 0;
937
+ uint16_t bodyStatus = 0;
938
+ if (JCoreReadUInt16(frame.bodyData, &offset, &bodyStatus)) {
939
+ resp.statusCode = bodyStatus;
940
+ }
941
+ if (offset + 2 <= frame.bodyData.length) {
942
+ resp.alias = JCoreReadString(frame.bodyData, &offset);
943
+ }
944
+ }
945
+ resp.mappedErrorCode = [self mapLiveActivityStatusCode:resp.statusCode];
946
+ return resp;
947
+ }
948
+
949
+ #pragma mark - CtrlCommand (cmd = 25, ver = 1)
950
+
951
+ + (NSData *)buildCtrlCommandPayloadWithRequestId:(uint64_t)requestId
952
+ jsonPayload:(NSDictionary *)jsonPayload {
953
+ NSMutableData *body = [NSMutableData data];
954
+ JCoreAppendUInt64(body, requestId);
955
+ NSData *jsonData = [NSJSONSerialization isValidJSONObject:jsonPayload]
956
+ ? [NSJSONSerialization dataWithJSONObject:jsonPayload options:0 error:nil]
957
+ : nil;
958
+ NSString *jsonString = jsonData != nil
959
+ ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]
960
+ : @"{}";
961
+ JCoreAppendString(body, jsonString);
962
+ return body;
963
+ }
964
+
965
+ + (JCORECtrlEnvelope *)parseCtrlEnvelope:(JCOREWireFrame *)frame {
966
+ if (frame.bodyData.length < 10) {
967
+ return nil;
968
+ }
969
+ NSUInteger offset = 0;
970
+ uint64_t reqId = 0;
971
+ if (!JCoreReadUInt64(frame.bodyData, &offset, &reqId)) {
972
+ return nil;
973
+ }
974
+ NSString *rawJson = JCoreReadString(frame.bodyData, &offset);
975
+ JCORECtrlEnvelope *env = [[JCORECtrlEnvelope alloc] init];
976
+ env.requestId = reqId;
977
+ env.rawJson = rawJson;
978
+ if (rawJson.length > 0) {
979
+ NSData *data = [rawJson dataUsingEncoding:NSUTF8StringEncoding];
980
+ id parsed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
981
+ if ([parsed isKindOfClass:[NSDictionary class]]) {
982
+ env.jsonPayload = parsed;
983
+ }
984
+ }
985
+ return env;
986
+ }
987
+
988
+ + (NSData *)buildCtrlAckPayloadWithStatusCode:(uint16_t)statusCode
989
+ requestId:(uint64_t)requestId {
990
+ NSMutableData *body = [NSMutableData dataWithCapacity:10];
991
+ JCoreAppendUInt16(body, statusCode);
992
+ JCoreAppendUInt64(body, requestId);
993
+ return body;
994
+ }
995
+
996
+ #pragma mark - User Property / MobileNumber / SDKVersion (cmd = 26, ver = 0)
997
+
998
+ + (NSData *)buildMobileNumberPayload:(NSString *)mobileNumber {
999
+ NSMutableData *body = [NSMutableData data];
1000
+ JCoreAppendUInt8(body, 7); // property type 7 = mobile number
1001
+ JCoreAppendUInt8(body, 1); // version = 1
1002
+ JCoreAppendString(body, mobileNumber ?: @"");
1003
+ return body;
1004
+ }
1005
+
1006
+ + (NSString *)parseMobileNumberFromFrame:(JCOREWireFrame *)frame {
1007
+ if (frame.bodyData.length < 4) {
1008
+ return nil;
1009
+ }
1010
+ NSUInteger offset = 2;
1011
+ return JCoreReadString(frame.bodyData, &offset);
1012
+ }
1013
+
1014
+ + (NSData *)buildSdkVersionPropertyPayload:(NSString *)sdkVersion {
1015
+ NSMutableData *body = [NSMutableData data];
1016
+ JCoreAppendUInt8(body, 1); // property type 1 = JPush SDK version
1017
+ JCoreAppendUInt8(body, 1); // version = 1
1018
+ JCoreAppendString(body, sdkVersion ?: @"6.0.0");
1019
+ return body;
1020
+ }
1021
+
1022
+ #pragma mark - Tags (cmd = 28, ver = 1), Alias (cmd = 29, ver = 1), User Properties (cmd = 38, ver = 1)
1023
+
1024
+ + (NSData *)buildTagOperationPayloadWithOperator:(NSString *)opString
1025
+ tags:(NSArray<NSString *> *)tags
1026
+ currPage:(NSInteger)currPage {
1027
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1028
+ dict[@"platform"] = @"i";
1029
+ dict[@"op"] = opString ?: @"set";
1030
+ if (tags != nil && ![opString isEqualToString:@"clean"] && ![opString isEqualToString:@"get"]) {
1031
+ dict[@"tags"] = tags;
1032
+ }
1033
+ if ([opString isEqualToString:@"get"] || currPage > 0) {
1034
+ dict[@"curr"] = @(currPage > 0 ? currPage : 1);
1035
+ }
1036
+ return [self buildJsonCommandPayload:dict];
1037
+ }
1038
+
1039
+ + (NSData *)buildAliasOperationPayloadWithOperator:(NSString *)opString
1040
+ alias:(NSString *)alias {
1041
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1042
+ dict[@"platform"] = @"i";
1043
+ dict[@"op"] = opString ?: @"set";
1044
+ if (alias.length > 0 && ![opString isEqualToString:@"del"] && ![opString isEqualToString:@"get"]) {
1045
+ dict[@"alias"] = alias;
1046
+ }
1047
+ return [self buildJsonCommandPayload:dict];
1048
+ }
1049
+
1050
+ + (NSData *)buildUserPropertiesPayloadWithOperator:(NSString *)opString
1051
+ properties:(NSDictionary *)properties {
1052
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1053
+ dict[@"op"] = opString ?: @"set";
1054
+ dict[@"data"] = properties ?: @{};
1055
+ return [self buildJsonCommandPayload:dict];
1056
+ }
1057
+
1058
+ + (NSData *)buildUserPropertiesResponsePayloadWithStatusCode:(uint16_t)statusCode
1059
+ jsonPayload:(NSDictionary *)jsonPayload {
1060
+ NSMutableData *body = [NSMutableData data];
1061
+ JCoreAppendUInt16(body, statusCode);
1062
+ if (jsonPayload != nil && [NSJSONSerialization isValidJSONObject:jsonPayload]) {
1063
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonPayload options:0 error:nil];
1064
+ NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] ?: @"{}";
1065
+ JCoreAppendString(body, jsonString);
1066
+ } else {
1067
+ JCoreAppendString(body, @"{}");
1068
+ }
1069
+ return body;
1070
+ }
1071
+
1072
+ + (NSData *)buildJsonCommandPayload:(NSDictionary *)payload {
1073
+ NSMutableData *body = [NSMutableData data];
1074
+ NSData *jsonData = [NSJSONSerialization isValidJSONObject:payload]
1075
+ ? [NSJSONSerialization dataWithJSONObject:payload options:0 error:nil]
1076
+ : nil;
1077
+ NSString *jsonString = jsonData != nil
1078
+ ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]
1079
+ : @"{}";
1080
+ JCoreAppendString(body, jsonString);
1081
+ return body;
1082
+ }
1083
+
1084
+ + (NSDictionary *)parseJsonBodyFromFrame:(JCOREWireFrame *)frame {
1085
+ if (frame.bodyData.length == 0) {
1086
+ return nil;
1087
+ }
1088
+ NSData *body = frame.bodyData;
1089
+
1090
+ // 1. Try reading length-prefixed string at offset 0 (Tags/Alias request & response, Properties request)
1091
+ NSUInteger offset = 0;
1092
+ NSString *jsonString = JCoreReadString(body, &offset);
1093
+ if (jsonString.length > 0) {
1094
+ NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
1095
+ id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
1096
+ if ([obj isKindOfClass:[NSDictionary class]]) {
1097
+ return obj;
1098
+ }
1099
+ }
1100
+
1101
+ // 2. Try reading length-prefixed string at offset 2 (after 2-byte statusCode in Properties response)
1102
+ if (body.length > 4) {
1103
+ NSUInteger offsetAfterStatus = 2;
1104
+ NSString *jsonAfterStatus = JCoreReadString(body, &offsetAfterStatus);
1105
+ if (jsonAfterStatus.length > 0) {
1106
+ NSData *data = [jsonAfterStatus dataUsingEncoding:NSUTF8StringEncoding];
1107
+ id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
1108
+ if ([obj isKindOfClass:[NSDictionary class]]) {
1109
+ return obj;
1110
+ }
1111
+ }
1112
+ }
1113
+
1114
+ // 3. Fallback: raw UTF-8 JSON body
1115
+ id rawObj = [NSJSONSerialization JSONObjectWithData:body options:0 error:nil];
1116
+ return [rawObj isKindOfClass:[NSDictionary class]] ? rawObj : nil;
1117
+ }
1118
+
1119
+ + (NSData *)buildPushControlPayloadWithStopped:(BOOL)stopped {
1120
+ NSDictionary *dict = @{
1121
+ @"type": @"notif_state",
1122
+ @"enabled": @(!stopped),
1123
+ @"itime": @((uint64_t)[[NSDate date] timeIntervalSince1970]),
1124
+ };
1125
+ return [self buildJsonCommandPayload:dict];
1126
+ }
1127
+
1128
+ @end
1129
+
1130
+ #pragma mark - HTTP Reporter (Part 4.4)
1131
+
1132
+ @interface JCOREHttpReporter ()
1133
+ @property (nonatomic, strong) NSURLSession *reportSession;
1134
+ @end
1135
+
1136
+ @implementation JCOREHttpReporter
1137
+
1138
+ + (instancetype)shared {
1139
+ static JCOREHttpReporter *instance = nil;
1140
+ static dispatch_once_t onceToken;
1141
+ dispatch_once(&onceToken, ^{
1142
+ instance = [[JCOREHttpReporter alloc] init];
1143
+ });
1144
+ return instance;
1145
+ }
1146
+
1147
+ - (instancetype)init {
1148
+ self = [super init];
1149
+ if (self) {
1150
+ NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration ephemeralSessionConfiguration];
1151
+ cfg.timeoutIntervalForRequest = 10.0;
1152
+ cfg.timeoutIntervalForResource = 15.0;
1153
+ _reportSession = [NSURLSession sessionWithConfiguration:cfg];
1154
+ }
1155
+ return self;
1156
+ }
1157
+
1158
+ + (NSDictionary *)buildReportEnvelopeWithContentItems:(NSArray<NSDictionary *> *)items {
1159
+ JCOREConfiguration *config = [JCOREConfiguration shared];
1160
+ return @{
1161
+ @"platform": @"i",
1162
+ @"uid": @(config.uid),
1163
+ @"app_key": config.appKey ?: @"",
1164
+ @"sdk_ver": @"5.2.0",
1165
+ @"core_sdk_ver": @"5.2.0",
1166
+ @"os_ver": [config osVersionString],
1167
+ @"model": [config modelNameString],
1168
+ @"bundle_id": [config bundleIdentifierString],
1169
+ @"ver": [config appVersionString],
1170
+ @"content": items ?: @[],
1171
+ };
1172
+ }
1173
+
1174
+ + (NSURLRequest *)buildHttpReportRequestWithURL:(NSURL *)url
1175
+ contentItems:(NSArray<NSDictionary *> *)items
1176
+ uid:(uint64_t)uid
1177
+ password:(NSString *)password
1178
+ appKey:(NSString *)appKey
1179
+ outAesKey:(NSString **)outAesKey {
1180
+ if (url == nil) {
1181
+ return nil;
1182
+ }
1183
+ NSMutableDictionary *envelope = [[self buildReportEnvelopeWithContentItems:items] mutableCopy];
1184
+ envelope[@"uid"] = @(uid);
1185
+ envelope[@"app_key"] = appKey ?: @"";
1186
+
1187
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:envelope options:0 error:nil];
1188
+ if (jsonData.length == 0) {
1189
+ return nil;
1190
+ }
1191
+ NSData *compressed = [JCORECrypto gzipCompress:jsonData];
1192
+ if (compressed.length == 0) {
1193
+ return nil;
1194
+ }
1195
+
1196
+ uint32_t seed = (arc4random() % 10000000U) + 1U;
1197
+ NSString *aesKey = [JCORECrypto generateSeedKey:seed];
1198
+ if (outAesKey != NULL) {
1199
+ *outAesKey = aesKey;
1200
+ }
1201
+
1202
+ NSData *encryptedBody = [JCORECrypto aes256CBCEncryptWithFixedHttpIV:compressed key:aesKey];
1203
+ if (encryptedBody.length == 0) {
1204
+ return nil;
1205
+ }
1206
+
1207
+ NSString *bodySha1 = [JCORECrypto sha1Hex:encryptedBody];
1208
+ NSString *pwdMd5 = [JCORECrypto md5Hex:password ?: @""];
1209
+ NSString *tokenInput = [NSString stringWithFormat:@"%llu%@%@", uid, pwdMd5, bodySha1];
1210
+ NSString *authToken = [JCORECrypto sha1HexForString:tokenInput];
1211
+ NSString *rawAuth = [NSString stringWithFormat:@"%llu:%@:%@", uid, authToken, aesKey];
1212
+ NSString *base64Auth = [[rawAuth dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0];
1213
+
1214
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
1215
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
1216
+ timeoutInterval:10.0];
1217
+ request.HTTPMethod = @"POST";
1218
+ request.HTTPBody = encryptedBody;
1219
+ [request setValue:[NSString stringWithFormat:@"Basic %@", base64Auth] forHTTPHeaderField:@"Authorization"];
1220
+ [request setValue:(appKey ?: @"") forHTTPHeaderField:@"X-App-Key"];
1221
+ [request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
1222
+ [request setValue:@"application/jason" forHTTPHeaderField:@"Accept"];
1223
+ return request;
1224
+ }
1225
+
1226
+ + (NSDictionary *)verifyAndDecryptHttpReportBody:(NSData *)bodyData
1227
+ authorizationHeader:(NSString *)authHeader
1228
+ expectedUid:(uint64_t)expectedUid
1229
+ password:(NSString *)password {
1230
+ if (bodyData.length == 0 || ![authHeader hasPrefix:@"Basic "]) {
1231
+ return nil;
1232
+ }
1233
+ NSString *b64 = [authHeader substringFromIndex:6];
1234
+ NSData *decodedAuthData = [[NSData alloc] initWithBase64EncodedString:b64 options:0];
1235
+ NSString *decodedAuth = [[NSString alloc] initWithData:decodedAuthData encoding:NSUTF8StringEncoding];
1236
+ NSArray<NSString *> *parts = [decodedAuth componentsSeparatedByString:@":"];
1237
+ if (parts.count != 3) {
1238
+ return nil;
1239
+ }
1240
+ uint64_t headerUid = strtoull(parts[0].UTF8String, NULL, 10);
1241
+ if (expectedUid != 0 && headerUid != expectedUid) {
1242
+ return nil;
1243
+ }
1244
+ NSString *authToken = parts[1];
1245
+ NSString *aesKey = parts[2];
1246
+
1247
+ NSString *bodySha1 = [JCORECrypto sha1Hex:bodyData];
1248
+ NSString *pwdMd5 = [JCORECrypto md5Hex:password ?: @""];
1249
+ NSString *expectedToken = [JCORECrypto sha1HexForString:[NSString stringWithFormat:@"%llu%@%@", headerUid, pwdMd5, bodySha1]];
1250
+ if (![authToken isEqualToString:expectedToken]) {
1251
+ return nil;
1252
+ }
1253
+
1254
+ NSData *decryptedCompressed = [JCORECrypto aes256CBCDecryptWithFixedHttpIV:bodyData key:aesKey];
1255
+ if (decryptedCompressed.length == 0) {
1256
+ return nil;
1257
+ }
1258
+ NSData *jsonData = [JCORECrypto gzipDecompress:decryptedCompressed];
1259
+ if (jsonData.length == 0) {
1260
+ return nil;
1261
+ }
1262
+ id obj = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
1263
+ return [obj isKindOfClass:[NSDictionary class]] ? obj : nil;
1264
+ }
1265
+
1266
+ + (NSURLRequest *)buildBadgeReportRequestWithBaseURL:(NSURL *)baseURL
1267
+ badgeValue:(NSInteger)badgeValue
1268
+ uid:(uint64_t)uid
1269
+ appKey:(NSString *)appKey
1270
+ outAesKey16:(NSString **)outAesKey16 {
1271
+ NSString *baseStr = baseURL.absoluteString.length > 0
1272
+ ? baseURL.absoluteString
1273
+ : @"https://ali-stats.jpush.cn/v3/push/sdk/badge";
1274
+ NSString *urlStr = [NSString stringWithFormat:@"%@%@platform=i&uid=%llu&value=%ld",
1275
+ baseStr,
1276
+ ([baseStr containsString:@"?"] ? @"&" : @"?"),
1277
+ uid,
1278
+ (long)badgeValue];
1279
+ NSURL *fullURL = [NSURL URLWithString:urlStr];
1280
+ if (fullURL == nil) {
1281
+ return nil;
1282
+ }
1283
+
1284
+ uint32_t seed = (arc4random() % 10000000U) + 1U;
1285
+ NSString *seedKey32 = [JCORECrypto generateSeedKey:seed];
1286
+ NSString *aesKey16 = [seedKey32 substringToIndex:16];
1287
+ if (outAesKey16 != NULL) {
1288
+ *outAesKey16 = aesKey16;
1289
+ }
1290
+
1291
+ NSString *rsaEncryptedKey = [JCORECrypto rsaEncryptString:aesKey16 publicKeyBase64:kJCOREReportRsaPublicKey] ?: @"";
1292
+ NSString *rawAuth = [NSString stringWithFormat:@"%@:%@", appKey ?: @"", rsaEncryptedKey];
1293
+ NSString *authHeaderValue = [NSString stringWithFormat:@"Basic %@",
1294
+ [[rawAuth dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0]];
1295
+
1296
+ uint64_t timestampMs = (uint64_t)([[NSDate date] timeIntervalSince1970] * 1000.0);
1297
+ uint64_t timestampSec = timestampMs / 1000ULL;
1298
+ NSString *timestampStr = [NSString stringWithFormat:@"%llu", timestampMs];
1299
+
1300
+ NSInteger clampedBadge = badgeValue < 0 ? 0 : badgeValue;
1301
+ NSDictionary *badgeDict = @{
1302
+ @"platform": @"i",
1303
+ @"uid": @(uid),
1304
+ @"app_key": appKey ?: @"",
1305
+ @"badge": @(clampedBadge),
1306
+ @"content": @{@"badge": @(clampedBadge)},
1307
+ @"itime": @(timestampSec),
1308
+ };
1309
+ NSData *badgeJsonData = [NSJSONSerialization dataWithJSONObject:badgeDict options:0 error:nil];
1310
+ NSData *encryptedBadge = [JCORECrypto aes128CBCEncryptData:badgeJsonData
1311
+ key16:aesKey16
1312
+ iv16:@"0000000000000000"];
1313
+ NSString *bodyString = [encryptedBadge base64EncodedStringWithOptions:0] ?: @"";
1314
+
1315
+ NSString *hmacKey = [JCORECrypto md5Hex:[NSString stringWithFormat:@"%@%@", appKey ?: @"", timestampStr]];
1316
+ NSString *signInput = [NSString stringWithFormat:@"%@%@1.0%@", authHeaderValue, timestampStr, bodyString];
1317
+ NSString *signHex = [JCORECrypto hmacSHA256HexWithKey:hmacKey content:signInput];
1318
+
1319
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fullURL
1320
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
1321
+ timeoutInterval:10.0];
1322
+ request.HTTPMethod = @"POST";
1323
+ request.HTTPBody = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
1324
+ [request setValue:authHeaderValue forHTTPHeaderField:@"Authorization"];
1325
+ [request setValue:@"1.0" forHTTPHeaderField:@"x-jg-version"];
1326
+ [request setValue:timestampStr forHTTPHeaderField:@"x-jg-timestamp"];
1327
+ [request setValue:signHex forHTTPHeaderField:@"x-jg-sign"];
1328
+ [request setValue:@"text/plain;charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
1329
+ return request;
1330
+ }
1331
+
1332
+ + (NSDictionary *)verifyAndDecryptBadgeReportHeaders:(NSDictionary<NSString *, NSString *> *)headers
1333
+ bodyString:(NSString *)bodyString
1334
+ appKey:(NSString *)appKey
1335
+ aesKey16:(NSString *)aesKey16 {
1336
+ NSString *auth = headers[@"Authorization"] ?: headers[@"authorization"];
1337
+ NSString *version = headers[@"x-jg-version"] ?: headers[@"X-Jg-Version"];
1338
+ NSString *timestamp = headers[@"x-jg-timestamp"] ?: headers[@"X-Jg-Timestamp"];
1339
+ NSString *sign = headers[@"x-jg-sign"] ?: headers[@"X-Jg-Sign"];
1340
+ if (auth.length == 0 || ![version isEqualToString:@"1.0"] || timestamp.length == 0 || sign.length == 0) {
1341
+ return nil;
1342
+ }
1343
+
1344
+ NSString *hmacKey = [JCORECrypto md5Hex:[NSString stringWithFormat:@"%@%@", appKey ?: @"", timestamp]];
1345
+ NSString *expectedSign = [JCORECrypto hmacSHA256HexWithKey:hmacKey
1346
+ content:[NSString stringWithFormat:@"%@%@1.0%@", auth, timestamp, bodyString ?: @""]];
1347
+ if (![sign isEqualToString:expectedSign]) {
1348
+ return nil;
1349
+ }
1350
+
1351
+ if (bodyString.length > 0 && aesKey16.length == 16) {
1352
+ NSData *cipherData = [[NSData alloc] initWithBase64EncodedString:bodyString options:0];
1353
+ NSData *plainData = [JCORECrypto aes128CBCDecryptData:cipherData
1354
+ key16:aesKey16
1355
+ iv16:@"0000000000000000"];
1356
+ if (plainData.length > 0) {
1357
+ id obj = [NSJSONSerialization JSONObjectWithData:plainData options:0 error:nil];
1358
+ if ([obj isKindOfClass:[NSDictionary class]]) {
1359
+ return obj;
1360
+ }
1361
+ }
1362
+ }
1363
+ return @{@"verified": @YES};
1364
+ }
1365
+
1366
+ - (void)sendHttpReportItems:(NSArray<NSDictionary *> *)items
1367
+ completion:(void (^)(BOOL, NSInteger))completion {
1368
+ JCOREConfiguration *config = [JCOREConfiguration shared];
1369
+ if (config.uid == 0 || config.appKey.length == 0) {
1370
+ if (completion) {
1371
+ completion(NO, -1);
1372
+ }
1373
+ return;
1374
+ }
1375
+ NSString *urlStr = config.httpReportUrl;
1376
+ if (urlStr.length == 0) {
1377
+ NSString *firstAddr = config.httpsReportAddresses.firstObject ?: config.httpReportAddresses.firstObject;
1378
+ if (firstAddr.length > 0) {
1379
+ urlStr = [firstAddr hasPrefix:@"http"] ? firstAddr : [NSString stringWithFormat:@"https://%@/v3/report", firstAddr];
1380
+ } else {
1381
+ urlStr = @"https://stats.jpush.cn/v3/report";
1382
+ }
1383
+ }
1384
+ NSURL *url = [NSURL URLWithString:urlStr];
1385
+ NSURLRequest *req = [JCOREHttpReporter buildHttpReportRequestWithURL:url
1386
+ contentItems:items
1387
+ uid:config.uid
1388
+ password:config.password ?: @""
1389
+ appKey:config.appKey
1390
+ outAesKey:NULL];
1391
+ if (req == nil) {
1392
+ if (completion) {
1393
+ completion(NO, -1);
1394
+ }
1395
+ return;
1396
+ }
1397
+ [[self.reportSession dataTaskWithRequest:req
1398
+ completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
1399
+ (void)data;
1400
+ NSInteger status = [response isKindOfClass:[NSHTTPURLResponse class]]
1401
+ ? [(NSHTTPURLResponse *)response statusCode]
1402
+ : 0;
1403
+ BOOL ok = (error == nil && status == 200);
1404
+ if (completion) {
1405
+ completion(ok, status);
1406
+ }
1407
+ }] resume];
1408
+ }
1409
+
1410
+ - (void)reportBadgeValue:(NSInteger)badgeValue
1411
+ completion:(void (^)(BOOL, NSInteger))completion {
1412
+ JCOREConfiguration *config = [JCOREConfiguration shared];
1413
+ if (config.uid == 0 || config.appKey.length == 0) {
1414
+ if (completion) {
1415
+ completion(NO, -1);
1416
+ }
1417
+ return;
1418
+ }
1419
+ NSString *urlStr = config.httpReportUrl;
1420
+ if (urlStr.length == 0) {
1421
+ NSString *firstAddr = config.httpsReportAddresses.firstObject ?: config.httpReportAddresses.firstObject;
1422
+ if (firstAddr.length > 0) {
1423
+ urlStr = [firstAddr hasPrefix:@"http"] ? firstAddr : [NSString stringWithFormat:@"https://%@/v3/push/sdk/badge", firstAddr];
1424
+ } else {
1425
+ urlStr = @"https://ali-stats.jpush.cn/v3/push/sdk/badge";
1426
+ }
1427
+ }
1428
+ NSURL *baseUrl = [NSURL URLWithString:urlStr];
1429
+ NSURLRequest *req = [JCOREHttpReporter buildBadgeReportRequestWithBaseURL:baseUrl
1430
+ badgeValue:badgeValue
1431
+ uid:config.uid
1432
+ appKey:config.appKey ?: @""
1433
+ outAesKey16:NULL];
1434
+ if (req == nil) {
1435
+ if (completion) {
1436
+ completion(NO, -1);
1437
+ }
1438
+ return;
1439
+ }
1440
+ [[self.reportSession dataTaskWithRequest:req
1441
+ completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
1442
+ (void)data;
1443
+ NSInteger status = [response isKindOfClass:[NSHTTPURLResponse class]]
1444
+ ? [(NSHTTPURLResponse *)response statusCode]
1445
+ : 0;
1446
+ BOOL ok = (error == nil && status == 200);
1447
+ if (completion) {
1448
+ completion(ok, status);
1449
+ }
1450
+ }] resume];
1451
+ }
1452
+
1453
+ @end