@onekeyfe/react-native-jpush 6.7.0-22 → 6.7.0-24

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 (24) hide show
  1. package/android/fcm/src/test/java/com/onekey/jpush/fcm/FcmAdapterTest.java +212 -0
  2. package/android/src/main/cpp/cpp-adapter.cpp +41 -1
  3. package/android/src/main/java/cn/jiguang/core/JCoreCrypto.java +6 -9
  4. package/android/src/main/java/cn/jiguang/core/JCoreStorage.java +14 -2
  5. package/android/src/main/java/cn/jiguang/core/JCoreTcpEngine.java +476 -348
  6. package/android/src/main/java/cn/jiguang/core/JCoreWireCodec.java +1 -0
  7. package/android/src/main/java/cn/jiguang/plugins/core/JCoreModule.java +13 -2
  8. package/android/src/main/java/cn/jiguang/plugins/push/JPushModule.java +38 -2
  9. package/android/src/main/java/cn/jiguang/plugins/push/helper/JPushHelper.java +46 -15
  10. package/android/src/main/java/cn/jiguang/plugins/push/receiver/JPushModuleReceiver.java +0 -3
  11. package/android/src/main/java/cn/jpush/android/api/JPushInterface.java +0 -1
  12. package/android/src/main/java/cn/jpush/android/local/ActionHelper.java +223 -272
  13. package/android/src/main/java/cn/jpush/android/service/JNotifyActivity.java +1 -1
  14. package/android/src/main/java/com/margelo/nitro/reactnativejpush/ReactNativeJpush.kt +51 -16
  15. package/android/src/test/java/com/margelo/nitro/reactnativejpush/ReactNativeJpushProtocolTest.java +76 -32
  16. package/ios/bridge/RCTJPushModule.m +215 -150
  17. package/ios/jcore/JCOREConfiguration.h +5 -0
  18. package/ios/jcore/JCOREConfiguration.m +44 -4
  19. package/ios/jcore/JCORECrypto.m +10 -12
  20. package/ios/jcore/JCORETcpSession.m +197 -45
  21. package/ios/jcore/JCOREWireProtocol.m +20 -7
  22. package/ios/jpush/JPUSHNotificationManager.m +7 -1
  23. package/ios/jpush/JPUSHService.m +7 -9
  24. package/package.json +2 -2
@@ -52,6 +52,10 @@ public final class JCoreTcpEngine {
52
52
  final long timeoutMs;
53
53
  final int maxRetries;
54
54
  final RequestCallback callback;
55
+ volatile Attempt owner;
56
+ final long policyGeneration;
57
+ final String configuration;
58
+ final java.util.concurrent.atomic.AtomicBoolean completed = new java.util.concurrent.atomic.AtomicBoolean();
55
59
 
56
60
  QueuedRequest(
57
61
  int command,
@@ -60,29 +64,64 @@ public final class JCoreTcpEngine {
60
64
  byte[] body,
61
65
  long timeoutMs,
62
66
  int maxRetries,
63
- RequestCallback callback
67
+ RequestCallback callback,
68
+ long policyGeneration,
69
+ String configuration
64
70
  ) {
65
71
  this.command = command;
66
72
  this.version = version;
67
73
  this.rid = rid;
68
- this.body = body != null ? body : new byte[0];
74
+ this.body = body != null ? body.clone() : new byte[0];
69
75
  this.timeoutMs = timeoutMs > 0L ? timeoutMs : 5000L;
70
76
  this.maxRetries = Math.max(0, maxRetries);
71
77
  this.callback = callback;
78
+ this.policyGeneration = policyGeneration;
79
+ this.configuration = configuration;
80
+ }
81
+ void complete(JCoreWireCodec.DecodedFrame frame, int code) {
82
+ if (code == 0 && (owner == null || !configuration.equals(owner.configuration) || !INSTANCE.owns(owner, null)
83
+ || policyGeneration != JCoreStorage.getContentGeneration() || !JCoreStorage.isCollectionAuth(null))) {
84
+ frame = null;
85
+ code = JPushInterface.ErrorCode.TIMEOUT;
86
+ }
87
+ if (completed.compareAndSet(false, true) && callback != null) callback.onResult(frame, code);
88
+ }
89
+ }
90
+
91
+ private static final class Attempt {
92
+ final long generation;
93
+ final CountDownLatch latch;
94
+ final String configuration;
95
+ volatile Socket socket;
96
+ volatile OutputStream output;
97
+ final Object writerLock = new Object();
98
+ boolean stopScheduled;
99
+ Attempt(long generation, CountDownLatch latch, String configuration) {
100
+ this.generation = generation; this.latch = latch; this.configuration = configuration;
72
101
  }
73
102
  }
74
103
 
75
104
  private static final class ResponseSlot {
76
105
  final CountDownLatch latch = new CountDownLatch(1);
77
106
  volatile JCoreWireCodec.DecodedFrame response;
78
-
79
- void complete(JCoreWireCodec.DecodedFrame frame) {
107
+ final int command;
108
+ final Attempt owner;
109
+ final Socket socket;
110
+ private boolean completed;
111
+ ResponseSlot(Attempt owner, Socket socket, int command) { this.owner = owner; this.socket = socket; this.command = command; }
112
+
113
+ synchronized void complete(JCoreWireCodec.DecodedFrame frame) {
114
+ if (completed) return;
115
+ completed = true;
80
116
  this.response = frame;
81
117
  this.latch.countDown();
82
118
  }
83
119
  }
84
120
 
85
121
  private static final JCoreTcpEngine INSTANCE = new JCoreTcpEngine();
122
+ private static final long STOP_CONTROL_GRACE_MS = 200L;
123
+ // Only the captured final stop control may send after local ownership is disabled.
124
+ private static final long STOP_CONTROL_POLICY = Long.MIN_VALUE;
86
125
  private static final String DEFAULT_HOST = "im64.jpush.cn";
87
126
  private static final int DEFAULT_PORT = 7000;
88
127
  private static final String SIS_URL = "https://tsis.jpush.cn/v3/srv";
@@ -122,6 +161,9 @@ public final class JCoreTcpEngine {
122
161
  new CopyOnWriteArrayList<JCoreWireCodec.CtrlCommand>();
123
162
  private final Object socketLock = new Object();
124
163
 
164
+ private volatile Attempt activeAttempt;
165
+ private volatile String terminalConfiguration = "";
166
+ private Object drainingToken;
125
167
  private volatile boolean started = false;
126
168
  private volatile boolean loggedIn = false;
127
169
  private volatile boolean autoReconnectEnabled = true;
@@ -191,7 +233,8 @@ public final class JCoreTcpEngine {
191
233
  }
192
234
  }
193
235
 
194
- private void startHeartbeatTimer(final Context context, final Socket targetSocket) {
236
+ private synchronized void startHeartbeatTimer(final Context context, final Socket targetSocket, final Attempt owner) {
237
+ if (!owns(owner, context) || activeSocket != targetSocket) return;
195
238
  cancelHeartbeatTimer();
196
239
  long interval = this.heartbeatIntervalMs > 0L ? this.heartbeatIntervalMs : 290000L;
197
240
  this.activeHeartbeatFuture = heartbeatExecutor.scheduleWithFixedDelay(
@@ -199,22 +242,24 @@ public final class JCoreTcpEngine {
199
242
  @Override
200
243
  public void run() {
201
244
  if (!loggedIn || activeSocket != targetSocket || targetSocket == null || targetSocket.isClosed()) {
202
- cancelHeartbeatTimer();
203
245
  return;
204
246
  }
205
- try {
206
- long currentUid = JCoreStorage.getUid(context);
207
- byte[] hbPayload = JCoreWireCodec.buildHeartbeatPayload(JCoreStorage.isPushStopped(context));
208
- sendFrameRaw(
209
- JCoreWireCodec.CMD_HEARTBEAT,
210
- JCoreWireCodec.VER_HEARTBEAT,
211
- JCoreStorage.nextRid(),
212
- sessionId,
213
- currentUid,
214
- hbPayload
215
- );
216
- } catch (Throwable ignored) {
217
- }
247
+ ioExecutor.execute(() -> {
248
+ if (!owns(owner, context)) return;
249
+ try {
250
+ long currentUid = JCoreStorage.getUid(context);
251
+ byte[] hbPayload = JCoreWireCodec.buildHeartbeatPayload(JCoreStorage.isPushStopped(context));
252
+ sendFrameOwned(owner, targetSocket,
253
+ JCoreWireCodec.CMD_HEARTBEAT,
254
+ JCoreWireCodec.VER_HEARTBEAT,
255
+ JCoreStorage.nextRid(),
256
+ sessionId,
257
+ currentUid,
258
+ hbPayload
259
+ );
260
+ } catch (Throwable ignored) {
261
+ }
262
+ });
218
263
  }
219
264
  },
220
265
  interval,
@@ -353,7 +398,13 @@ public final class JCoreTcpEngine {
353
398
  }
354
399
 
355
400
  public synchronized void closeSocket() {
401
+ closeSocket(null, false);
402
+ }
403
+
404
+ private synchronized void closeSocket(Context context, boolean preserveUnboundRequests) {
356
405
  this.connectionGeneration++;
406
+ drainingToken = null;
407
+ activeAttempt = null;
357
408
  cancelHeartbeatTimer();
358
409
  synchronized (socketLock) {
359
410
  if (activeSocket != null) {
@@ -370,75 +421,119 @@ public final class JCoreTcpEngine {
370
421
  }
371
422
  pendingRequests.clear();
372
423
  loggedIn = false;
424
+ JCoreStorage.setConnected(null, false);
425
+ String configuration = preserveUnboundRequests ? configurationIdentity(context) : "";
426
+ long policyGeneration = JCoreStorage.getContentGeneration();
427
+ boolean mayPreserve = preserveUnboundRequests && JCoreStorage.isCollectionAuth(context)
428
+ && !JCoreStorage.isPushStopped(context) && !terminalConfiguration.equals(configuration);
429
+ List<QueuedRequest> retained = new ArrayList<QueuedRequest>();
430
+ QueuedRequest queued;
431
+ while ((queued = preLoginQueue.poll()) != null) {
432
+ if (mayPreserve && queued.owner == null && !queued.completed.get()
433
+ && queued.policyGeneration == policyGeneration && queued.configuration.equals(configuration)) {
434
+ retained.add(queued);
435
+ } else {
436
+ final QueuedRequest canceled = queued;
437
+ ioExecutor.execute(() -> canceled.complete(null, JPushInterface.ErrorCode.TIMEOUT));
438
+ }
439
+ }
440
+ preLoginQueue.addAll(retained);
373
441
  }
374
442
 
375
- public synchronized void stop(Context context) {
376
- closeSocket();
377
- this.started = false;
378
- JCoreStorage.setConnected(context, false);
379
- }
380
-
381
- public void start(final Context context) {
382
- JCoreStorage.setAppContext(context);
383
- final long startGen;
384
- final CountDownLatch latchForStart;
443
+ public void stop(Context context) {
444
+ final Attempt owner;
445
+ final Socket socket;
446
+ final long uid;
447
+ final int sid;
448
+ List<QueuedRequest> canceled;
385
449
  synchronized (this) {
386
- if (started) {
450
+ owner = activeAttempt;
451
+ if (owner != null && owner.stopScheduled && JCoreStorage.isPushStopped(context)) return;
452
+ if (owner == null || !loggedIn || activeSocket == null || !JCoreStorage.isPushStopped(context)) {
453
+ closeSocket();
454
+ started = false;
387
455
  return;
388
456
  }
389
- started = true;
390
- handshakeLatch = new CountDownLatch(1);
391
- startGen = this.connectionGeneration;
392
- latchForStart = this.handshakeLatch;
393
- }
394
- if (JCoreStorage.isPushStopped(JCoreStorage.getAppContext(context))) {
395
- latchForStart.countDown();
396
- return;
457
+ owner.stopScheduled = true;
458
+ drainingToken = null;
459
+ socket = activeSocket;
460
+ uid = JCoreStorage.getUid(context);
461
+ sid = sessionId;
462
+ started = false;
463
+ loggedIn = false;
464
+ cancelHeartbeatTimer();
465
+ JCoreStorage.setConnected(context, false);
466
+ for (java.util.Map.Entry<Long, ResponseSlot> entry : pendingRequests.entrySet()) {
467
+ ResponseSlot slot = entry.getValue();
468
+ if (slot.owner == owner && pendingRequests.remove(entry.getKey(), slot)) slot.complete(null);
469
+ }
470
+ canceled = new ArrayList<QueuedRequest>(preLoginQueue);
471
+ preLoginQueue.clear();
397
472
  }
398
- ioExecutor.execute(new Runnable() {
399
- @Override
400
- public void run() {
401
- if (!started || startGen != connectionGeneration) {
402
- latchForStart.countDown();
403
- return;
404
- }
405
- Context app = JCoreStorage.getAppContext(context);
406
- if (JCoreStorage.isPushStopped(app)) {
407
- latchForStart.countDown();
408
- return;
409
- }
410
- connectAndHandshake(app);
473
+ final ScheduledFuture<?> deadline = heartbeatExecutor.schedule(() -> closeOwnedSocket(owner),
474
+ STOP_CONTROL_GRACE_MS, TimeUnit.MILLISECONDS);
475
+ ioExecutor.execute(() -> {
476
+ try {
477
+ sendFrameOwned(owner, socket, JCoreWireCodec.CMD_PUSH_CTRL, JCoreWireCodec.VER_PUSH_CTRL,
478
+ JCoreStorage.nextRid(), sid, uid, JCoreWireCodec.buildPushControlPayload(true), STOP_CONTROL_POLICY);
479
+ } finally {
480
+ closeOwnedSocket(owner);
481
+ deadline.cancel(false);
411
482
  }
412
483
  });
484
+ for (QueuedRequest req : canceled) ioExecutor.execute(() -> req.complete(null, JPushInterface.ErrorCode.TIMEOUT));
485
+ }
486
+
487
+ private String configurationIdentity(Context context) {
488
+ Context app = JCoreStorage.getAppContext(context);
489
+ return JCoreStorage.getAppKey(context) + ":" + (app != null ? app.getPackageName() : "");
490
+ }
491
+
492
+ private boolean owns(Attempt owner, Context context) {
493
+ return owner != null && activeAttempt == owner && owner.generation == connectionGeneration
494
+ && started && !JCoreStorage.isPushStopped(context) && owner.configuration.equals(configurationIdentity(context));
495
+ }
496
+
497
+ private synchronized Attempt beginAttempt(Context context) {
498
+ if (JCoreStorage.isPushStopped(context) || terminalConfiguration.equals(configurationIdentity(context))) return null;
499
+ if (!terminalConfiguration.isEmpty()) terminalConfiguration = "";
500
+ drainingToken = null;
501
+ if (activeAttempt != null) closeOwnedSocket(activeAttempt);
502
+ started = true;
503
+ handshakeLatch = new CountDownLatch(1);
504
+ Attempt owner = new Attempt(++connectionGeneration, handshakeLatch, configurationIdentity(context));
505
+ activeAttempt = owner;
506
+ return owner;
507
+ }
508
+
509
+ public void start(final Context context) {
510
+ JCoreStorage.setAppContext(context);
511
+ final Attempt owner;
512
+ synchronized (this) {
513
+ if (started && owns(activeAttempt, context)) return;
514
+ owner = beginAttempt(context);
515
+ }
516
+ if (owner != null) ioExecutor.execute(() -> connectAndHandshake(context, owner));
413
517
  }
414
518
 
415
519
  public void triggerReconnectNow(final Context context) {
416
- final long reconnectGen;
417
- final CountDownLatch latchForReconnect;
520
+ final Attempt owner;
418
521
  synchronized (this) {
419
522
  closeSocket();
420
- this.started = true;
421
- this.handshakeLatch = new CountDownLatch(1);
422
- reconnectGen = this.connectionGeneration;
423
- latchForReconnect = this.handshakeLatch;
523
+ owner = beginAttempt(context);
424
524
  }
425
- ioExecutor.execute(new Runnable() {
426
- @Override
427
- public void run() {
428
- if (!started || reconnectGen != connectionGeneration) {
429
- latchForReconnect.countDown();
430
- return;
431
- }
432
- connectAndHandshake(JCoreStorage.getAppContext(context));
433
- }
434
- });
525
+ if (owner != null) ioExecutor.execute(() -> connectAndHandshake(context, owner));
435
526
  }
436
527
 
437
- public void onNetworkConnected(Context context) {
438
- this.reconnectAttempt = 0;
439
- if (!this.loggedIn) {
440
- triggerReconnectNow(context);
528
+ public void onNetworkConnected(final Context context) {
529
+ final Attempt owner;
530
+ synchronized (this) {
531
+ reconnectAttempt = 0;
532
+ if (loggedIn) return;
533
+ closeSocket(context, true);
534
+ owner = beginAttempt(context);
441
535
  }
536
+ if (owner != null) ioExecutor.execute(() -> connectAndHandshake(context, owner));
442
537
  }
443
538
 
444
539
  public void executeAsync(Runnable task) {
@@ -536,80 +631,87 @@ public final class JCoreTcpEngine {
536
631
  }
537
632
 
538
633
  public void connectAndHandshake(final Context context) {
634
+ Attempt owner;
635
+ synchronized (this) { owner = beginAttempt(context); }
636
+ if (owner != null) connectAndHandshake(context, owner);
637
+ }
638
+
639
+ private void scheduleReconnect(final Context context, final Attempt previous) {
640
+ final long delay;
641
+ synchronized (this) {
642
+ if (!owns(previous, context) || !terminalConfiguration.isEmpty()) return;
643
+ if (!autoReconnectEnabled) { reconnectAttempt++; return; }
644
+ delay = computeReconnectDelayMs(reconnectAttempt++, reconnectBaseDelayMs);
645
+ }
646
+ heartbeatExecutor.schedule(() -> {
647
+ final Attempt next;
648
+ synchronized (JCoreTcpEngine.this) {
649
+ if (!owns(previous, context) || loggedIn) return;
650
+ next = beginAttempt(context);
651
+ }
652
+ if (next != null) ioExecutor.execute(() -> connectAndHandshake(context, next));
653
+ }, delay, TimeUnit.MILLISECONDS);
654
+ }
655
+
656
+ private void closeOwnedSocket(Attempt owner) {
657
+ synchronized (this) {
658
+ if (activeAttempt != owner) {
659
+ if (owner.socket != null) try { owner.socket.close(); } catch (Exception ignored) {}
660
+ return;
661
+ }
662
+ drainingToken = null;
663
+ synchronized (socketLock) {
664
+ if (activeSocket == owner.socket) {
665
+ if (activeSocket != null) try { activeSocket.close(); } catch (Exception ignored) {}
666
+ activeSocket = null;
667
+ activeOutput = null;
668
+ loggedIn = false;
669
+ JCoreStorage.setConnected(null, false);
670
+ cancelHeartbeatTimer();
671
+ }
672
+ }
673
+ for (java.util.Map.Entry<Long, ResponseSlot> entry : pendingRequests.entrySet()) {
674
+ ResponseSlot slot = entry.getValue();
675
+ if (slot.owner == owner && pendingRequests.remove(entry.getKey(), slot)) slot.complete(null);
676
+ }
677
+ }
678
+ }
679
+
680
+ private void connectAndHandshake(final Context context, final Attempt owner) {
539
681
  try {
682
+ synchronized (this) { if (!owns(owner, context)) return; }
540
683
  List<String> candidates = buildOrderedCandidateList(context);
541
684
  if (customPort <= 0 && candidateEndpoints.isEmpty()) {
542
685
  if (!hasHealthyCandidate(context, candidates)) {
543
- String[] sisResolved = resolveSisEndpoint(context);
686
+ String[] resolved = resolveSisEndpoint(context, owner);
687
+ if (!owns(owner, context)) return;
544
688
  candidates = buildOrderedCandidateList(context);
545
- if (sisResolved != null && sisResolved.length == 2) {
546
- String resolvedEp = (sisResolved[0] + ":" + sisResolved[1]).trim();
547
- if (!resolvedEp.isEmpty() && splitHostPort(resolvedEp) != null && !candidates.contains(resolvedEp)) {
548
- candidates.add(0, resolvedEp);
549
- }
550
- }
551
- }
552
- if (!hasHealthyCandidate(context, candidates)) {
553
- String defaultEndpoint = DEFAULT_HOST + ":" + DEFAULT_PORT;
554
- if (!candidates.contains(defaultEndpoint)) {
555
- candidates.add(defaultEndpoint);
689
+ if (resolved != null && resolved.length == 2) {
690
+ String endpoint = resolved[0] + ":" + resolved[1];
691
+ if (!candidates.contains(endpoint)) candidates.add(0, endpoint);
556
692
  }
557
693
  }
694
+ if (!hasHealthyCandidate(context, candidates)) candidates.add(DEFAULT_HOST + ":" + DEFAULT_PORT);
558
695
  }
559
- if (candidates.isEmpty()) {
560
- JCoreStorage.setConnected(context, false);
561
- return;
562
- }
563
-
564
- boolean connectedSuccess = false;
565
696
  for (String endpoint : candidates) {
697
+ if (!owns(owner, context)) return;
566
698
  String[] hp = splitHostPort(endpoint);
567
- if (hp == null) {
568
- continue;
569
- }
570
- String host = hp[0];
571
- int port = Integer.parseInt(hp[1]);
572
- if (tryConnectAndHandshakeEndpoint(context, host, port)) {
573
- JCoreStorage.setLastGoodIp(context, host + ":" + port);
574
- connectedSuccess = true;
575
- break;
576
- } else {
577
- JCoreStorage.recordFailedIp(context, host + ":" + port);
699
+ if (hp == null) continue;
700
+ if (tryConnectAndHandshakeEndpoint(context, hp[0], Integer.parseInt(hp[1]), owner)) {
701
+ synchronized (this) { if (owns(owner, context)) JCoreStorage.setLastGoodIp(context, endpoint); }
702
+ return;
578
703
  }
704
+ if (!owns(owner, context) || !terminalConfiguration.isEmpty()) return;
705
+ synchronized (this) { if (owns(owner, context)) JCoreStorage.recordFailedIp(context, endpoint); }
579
706
  }
580
-
581
- if (!connectedSuccess) {
582
- JCoreStorage.setConnected(context, false);
583
- if (this.started && this.autoReconnectEnabled && !JCoreStorage.isPushStopped(context)) {
584
- final long delayMs = computeReconnectDelayMs(reconnectAttempt++, reconnectBaseDelayMs);
585
- final long scheduledGen = this.connectionGeneration;
586
- heartbeatExecutor.schedule(new Runnable() {
587
- @Override
588
- public void run() {
589
- if (!started || scheduledGen != connectionGeneration || loggedIn || JCoreStorage.isPushStopped(context)) {
590
- return;
591
- }
592
- ioExecutor.execute(new Runnable() {
593
- @Override
594
- public void run() {
595
- if (!started || scheduledGen != connectionGeneration || loggedIn || JCoreStorage.isPushStopped(context)) {
596
- return;
597
- }
598
- connectAndHandshake(context);
599
- }
600
- });
601
- }
602
- }, delayMs, TimeUnit.MILLISECONDS);
603
- } else {
604
- this.reconnectAttempt++;
605
- }
707
+ if (owns(owner, context)) {
708
+ synchronized (this) { if (owns(owner, context)) JCoreStorage.setConnected(context, false); }
709
+ scheduleReconnect(context, owner);
606
710
  }
607
- } finally {
608
- handshakeLatch.countDown();
609
- }
711
+ } finally { owner.latch.countDown(); }
610
712
  }
611
713
 
612
- private boolean tryConnectAndHandshakeEndpoint(final Context context, String host, int port) {
714
+ private boolean tryConnectAndHandshakeEndpoint(final Context context, String host, int port, final Attempt owner) {
613
715
  Socket socket = null;
614
716
  try {
615
717
  if (isPlainSocketEndpoint(host, port)) {
@@ -623,15 +725,17 @@ public final class JCoreTcpEngine {
623
725
  final Socket connectedSocket = socket;
624
726
  final InputStream in = connectedSocket.getInputStream();
625
727
  final OutputStream out = connectedSocket.getOutputStream();
626
- synchronized (socketLock) {
627
- this.activeSocket = connectedSocket;
628
- this.activeOutput = out;
728
+ synchronized (this) {
729
+ if (!owns(owner, context)) { connectedSocket.close(); return false; }
730
+ owner.socket = connectedSocket;
731
+ owner.output = out;
732
+ synchronized (socketLock) { activeSocket = connectedSocket; activeOutput = out; }
629
733
  }
630
734
 
631
735
  Thread readerThread = new Thread(new Runnable() {
632
736
  @Override
633
737
  public void run() {
634
- readLoop(context, connectedSocket, in);
738
+ readLoop(context, connectedSocket, in, owner);
635
739
  }
636
740
  }, "jcore-tcp-reader");
637
741
  readerThread.setDaemon(true);
@@ -662,8 +766,8 @@ public final class JCoreTcpEngine {
662
766
  && !JCoreStorage.getRegistrationId(context).isEmpty();
663
767
 
664
768
  if (!fastLogin) {
665
- if (!performRegisterHandshake(context, appKey, channel, pkgName, deviceId)) {
666
- closeSocket();
769
+ if (!performRegisterHandshake(context, appKey, channel, pkgName, deviceId, owner)) {
770
+ closeOwnedSocket(owner);
667
771
  return false;
668
772
  }
669
773
  }
@@ -671,7 +775,7 @@ public final class JCoreTcpEngine {
671
775
  long uid = JCoreStorage.getUid(context);
672
776
  String password = JCoreStorage.getPassword(context);
673
777
  byte[] loginBody = JCoreWireCodec.buildLoginPayload(password, appKey, deviceId, countryCode);
674
- JCoreWireCodec.DecodedFrame loginFrame = sendRequestOnSocket(
778
+ JCoreWireCodec.DecodedFrame loginFrame = sendRequestOnSocket(owner,
675
779
  JCoreWireCodec.CMD_LOGIN,
676
780
  JCoreWireCodec.VER_LOGIN,
677
781
  0,
@@ -680,26 +784,29 @@ public final class JCoreTcpEngine {
680
784
  defaultRequestTimeoutMs
681
785
  );
682
786
  if (loginFrame == null) {
683
- closeSocket();
787
+ closeOwnedSocket(owner);
684
788
  return false;
685
789
  }
686
790
  JCoreWireCodec.LoginResponse loginResp = JCoreWireCodec.parseLoginResponse(loginFrame);
687
791
  if (loginResp == null) {
688
- closeSocket();
792
+ closeOwnedSocket(owner);
689
793
  return false;
690
794
  }
691
795
 
692
796
  // Handle Login statusCode == 108 (clear registration and immediately re-register + login)
693
797
  if (loginResp.statusCode == 108) {
694
- JCoreStorage.clearRegistration(context);
695
- if (!performRegisterHandshake(context, appKey, channel, pkgName, deviceId)) {
696
- closeSocket();
798
+ synchronized (this) {
799
+ if (!owns(owner, context)) return false;
800
+ JCoreStorage.clearRegistration(context);
801
+ }
802
+ if (!performRegisterHandshake(context, appKey, channel, pkgName, deviceId, owner)) {
803
+ closeOwnedSocket(owner);
697
804
  return false;
698
805
  }
699
806
  uid = JCoreStorage.getUid(context);
700
807
  password = JCoreStorage.getPassword(context);
701
808
  loginBody = JCoreWireCodec.buildLoginPayload(password, appKey, deviceId, countryCode);
702
- loginFrame = sendRequestOnSocket(
809
+ loginFrame = sendRequestOnSocket(owner,
703
810
  JCoreWireCodec.CMD_LOGIN,
704
811
  JCoreWireCodec.VER_LOGIN,
705
812
  0,
@@ -708,29 +815,43 @@ public final class JCoreTcpEngine {
708
815
  defaultRequestTimeoutMs
709
816
  );
710
817
  if (loginFrame == null) {
711
- closeSocket();
818
+ closeOwnedSocket(owner);
712
819
  return false;
713
820
  }
714
821
  loginResp = JCoreWireCodec.parseLoginResponse(loginFrame);
715
822
  } else if (loginResp.statusCode == 1012) {
716
- JCoreStorage.resetSisCache(context);
717
- closeSocket();
823
+ synchronized (this) {
824
+ if (!owns(owner, context)) return false;
825
+ JCoreStorage.resetSisCache(context);
826
+ }
827
+ closeOwnedSocket(owner);
718
828
  return false;
719
829
  }
720
830
 
831
+ synchronized (this) {
832
+ if (loginResp != null && (loginResp.statusCode == 1005 || loginResp.statusCode == 1008 || loginResp.statusCode == 1009) && owns(owner, context)) {
833
+ terminalConfiguration = owner.configuration;
834
+ closeSocket();
835
+ started = false;
836
+ }
837
+ }
721
838
  if (loginResp == null || loginResp.statusCode != 0) {
722
- closeSocket();
839
+ closeOwnedSocket(owner);
723
840
  return false;
724
841
  }
725
842
 
726
- if (loginResp.sessionId != 0) {
727
- this.sessionId = loginResp.sessionId;
728
- } else if (loginFrame.sessionId != 0) {
729
- this.sessionId = loginFrame.sessionId;
730
- }
843
+ synchronized (this) {
844
+ if (!owns(owner, context)) return false;
845
+ if (loginResp.sessionId != 0) {
846
+ this.sessionId = loginResp.sessionId;
847
+ } else if (loginFrame.sessionId != 0) {
848
+ this.sessionId = loginFrame.sessionId;
849
+ }
731
850
 
732
- this.loggedIn = true;
733
- this.reconnectAttempt = 0;
851
+ this.loggedIn = true;
852
+ this.reconnectAttempt = 0;
853
+ JCoreStorage.setConnected(context, true);
854
+ }
734
855
  int idleReadTimeoutMs = (int) Math.min(
735
856
  (long) Integer.MAX_VALUE,
736
857
  Math.max(320000L, this.heartbeatIntervalMs + 30000L)
@@ -749,11 +870,11 @@ public final class JCoreTcpEngine {
749
870
  + " registrationId="
750
871
  + JCoreStorage.getRegistrationId(context)
751
872
  );
752
- JCoreStorage.setConnected(context, true);
873
+ if (!owns(owner, context)) return false;
753
874
  ActionHelper.getInstance().dispatchConnectedEvent(context, true);
754
875
 
755
876
  byte[] hbBody = JCoreWireCodec.buildHeartbeatPayload(JCoreStorage.isPushStopped(context));
756
- sendRequestOnSocket(
877
+ sendRequestOnSocket(owner,
757
878
  JCoreWireCodec.CMD_HEARTBEAT,
758
879
  JCoreWireCodec.VER_HEARTBEAT,
759
880
  this.sessionId,
@@ -762,11 +883,14 @@ public final class JCoreTcpEngine {
762
883
  defaultRequestTimeoutMs
763
884
  );
764
885
 
765
- startHeartbeatTimer(context, connectedSocket);
766
- flushPreLoginQueue(context);
886
+ if (!owns(owner, context)) return false;
887
+ startHeartbeatTimer(context, connectedSocket, owner);
888
+ flushPreLoginQueue(context, owner);
889
+ if (owns(owner, context)) cn.jpush.android.api.JThirdPlatFormInterface.initializeFcm(context);
767
890
  return true;
768
891
  } catch (Throwable ignored) {
769
- closeSocket();
892
+ if (socket != null && socket != owner.socket) try { socket.close(); } catch (Exception closeError) {}
893
+ closeOwnedSocket(owner);
770
894
  return false;
771
895
  }
772
896
  }
@@ -776,12 +900,17 @@ public final class JCoreTcpEngine {
776
900
  String appKey,
777
901
  String channel,
778
902
  String pkgName,
779
- String deviceId
903
+ String deviceId,
904
+ Attempt owner
780
905
  ) {
781
- int registerSeed = JCoreCrypto.nextRegisterSeed();
782
- JCoreWireCodec.setLastRegisterSeed(registerSeed);
906
+ final int registerSeed;
907
+ synchronized (this) {
908
+ if (!owns(owner, context)) return false;
909
+ registerSeed = JCoreCrypto.nextRegisterSeed();
910
+ JCoreWireCodec.setLastRegisterSeed(registerSeed);
911
+ }
783
912
  byte[] regBody = JCoreWireCodec.buildRegisterPayload(appKey, channel, pkgName, deviceId);
784
- JCoreWireCodec.DecodedFrame regFrame = sendRequestOnSocket(
913
+ JCoreWireCodec.DecodedFrame regFrame = sendRequestOnSocket(owner,
785
914
  JCoreWireCodec.CMD_REGISTER,
786
915
  JCoreWireCodec.VER_REGISTER,
787
916
  registerSeed,
@@ -794,165 +923,142 @@ public final class JCoreTcpEngine {
794
923
  }
795
924
  JCoreWireCodec.RegisterResponse regResp = JCoreWireCodec.parseRegisterResponse(regFrame);
796
925
  if (regResp == null || regResp.statusCode != 0 || regResp.uid <= 0L) {
926
+ synchronized (this) {
927
+ if (regResp != null && owns(owner, context) && (regResp.statusCode == 1005 || regResp.statusCode == 1008 || regResp.statusCode == 1009)) {
928
+ terminalConfiguration = owner.configuration;
929
+ closeSocket();
930
+ started = false;
931
+ }
932
+ }
797
933
  return false;
798
934
  }
799
- JCoreStorage.updateRegistration(
800
- context,
801
- regResp.uid,
802
- regResp.password,
803
- regResp.registrationId,
804
- regResp.deviceId
805
- );
806
- JCoreStorage.setServerRegistered(context, true);
935
+ synchronized (this) {
936
+ if (!owns(owner, context)) return false;
937
+ JCoreStorage.updateRegistration(
938
+ context,
939
+ regResp.uid,
940
+ regResp.password,
941
+ regResp.registrationId,
942
+ regResp.deviceId
943
+ );
944
+ JCoreStorage.setServerRegistered(context, true);
945
+ }
946
+ if (!owns(owner, context)) return false;
807
947
  JLogger.i("TCP register success uid=" + regResp.uid + " registrationId=" + regResp.registrationId);
808
948
  ActionHelper.getInstance().dispatchRegisterEvent(context, regResp.registrationId);
809
949
  return true;
810
950
  }
811
951
 
812
- private void flushPreLoginQueue(final Context context) {
813
- QueuedRequest item;
814
- while ((item = preLoginQueue.poll()) != null) {
815
- final QueuedRequest req = item;
816
- ioExecutor.execute(new Runnable() {
817
- @Override
818
- public void run() {
819
- executeQueuedRequestWithRetry(context, req);
820
- }
821
- });
822
- }
823
- }
824
-
825
- public void enqueueOrSendRequest(
826
- final Context context,
827
- int command,
828
- int version,
829
- long rid,
830
- byte[] body,
831
- long timeoutMs,
832
- int maxRetries,
833
- RequestCallback callback
834
- ) {
835
- final long effectiveRid = rid > 0L ? rid : JCoreStorage.nextRid();
836
- final QueuedRequest req = new QueuedRequest(
837
- command,
838
- version,
839
- effectiveRid,
840
- body,
841
- timeoutMs > 0L ? timeoutMs : defaultRequestTimeoutMs,
842
- maxRetries,
843
- callback
844
- );
845
- if (!this.loggedIn || this.activeOutput == null) {
846
- preLoginQueue.offer(req);
847
- return;
952
+ private void flushPreLoginQueue(final Context context, final Attempt expectedOwner) {
953
+ final Object token;
954
+ synchronized (this) {
955
+ if (!owns(expectedOwner, context) || !loggedIn || drainingToken != null || preLoginQueue.isEmpty()) return;
956
+ token = new Object();
957
+ drainingToken = token;
848
958
  }
849
- ioExecutor.execute(new Runnable() {
850
- @Override
851
- public void run() {
852
- executeQueuedRequestWithRetry(context, req);
959
+ ioExecutor.execute(() -> {
960
+ try {
961
+ while (true) {
962
+ final QueuedRequest req;
963
+ synchronized (JCoreTcpEngine.this) {
964
+ if (drainingToken != token) return;
965
+ if (!owns(expectedOwner, context) || !loggedIn || (req = preLoginQueue.poll()) == null) {
966
+ drainingToken = null;
967
+ return;
968
+ }
969
+ if (req.owner == null) req.owner = expectedOwner;
970
+ }
971
+ try { executeQueuedRequestWithRetry(context, req, req.owner); }
972
+ catch (Throwable callbackFailure) {
973
+ try { req.complete(null, JPushInterface.ErrorCode.TIMEOUT); } catch (Throwable ignored) {}
974
+ }
975
+ }
976
+ } finally {
977
+ boolean restart = false;
978
+ synchronized (JCoreTcpEngine.this) {
979
+ if (drainingToken == token) {
980
+ drainingToken = null;
981
+ restart = owns(expectedOwner, context) && loggedIn && !preLoginQueue.isEmpty();
982
+ }
983
+ }
984
+ if (restart) flushPreLoginQueue(context, expectedOwner);
853
985
  }
854
986
  });
855
987
  }
856
988
 
857
- private void executeQueuedRequestWithRetry(Context context, QueuedRequest req) {
858
- long uid = JCoreStorage.getUid(context);
859
- int attempts = 0;
860
- while (attempts <= req.maxRetries) {
861
- attempts++;
862
- long ridToUse = (attempts == 1) ? req.rid : JCoreStorage.nextRid();
863
- JCoreWireCodec.DecodedFrame resp = sendRequestWithRidOnSocket(
864
- req.command,
865
- req.version,
866
- ridToUse,
867
- this.sessionId,
868
- uid,
869
- req.body,
870
- req.timeoutMs
871
- );
872
- if (resp != null) {
873
- int code = resp.statusCode;
874
- if ((req.command == JCoreWireCodec.CMD_TAG_OP
875
- || req.command == JCoreWireCodec.CMD_ALIAS_OP
876
- || req.command == JCoreWireCodec.CMD_PROPERTY_OP)
877
- && resp.body != null && resp.body.length > 0) {
878
- String json = JCoreWireCodec.parseJsonCommandPayload(resp.body);
879
- code = JCoreWireCodec.extractJsonInt(json, "code", code);
880
- }
881
- if (req.callback != null) {
882
- req.callback.onResult(resp, code);
989
+ public void enqueueOrSendRequest(final Context context, int command, int version, long rid, byte[] body,
990
+ long timeoutMs, int maxRetries, RequestCallback callback) {
991
+ final QueuedRequest req;
992
+ final Attempt owner;
993
+ boolean rejected;
994
+ final boolean queueFull;
995
+ final boolean terminal;
996
+ synchronized (this) {
997
+ req = new QueuedRequest(command, version, rid > 0L ? rid : JCoreStorage.nextRid(), body,
998
+ timeoutMs > 0L ? timeoutMs : defaultRequestTimeoutMs, maxRetries, callback,
999
+ JCoreStorage.getContentGeneration(), configurationIdentity(context));
1000
+ queueFull = preLoginQueue.size() >= 128;
1001
+ terminal = terminalConfiguration.equals(configurationIdentity(context));
1002
+ rejected = queueFull || terminal || !JCoreStorage.isCollectionAuth(context) || JCoreStorage.isPushStopped(context);
1003
+ if (!rejected) {
1004
+ if (loggedIn && owns(activeAttempt, context)) req.owner = activeAttempt;
1005
+ preLoginQueue.offer(req);
1006
+ }
1007
+ owner = activeAttempt;
1008
+ }
1009
+ if (rejected) {
1010
+ JLogger.w(queueFull ? "Request rejected reason=queue_full" : terminal
1011
+ ? "Request rejected reason=configuration_terminal" : "Request rejected reason=policy_disabled");
1012
+ req.complete(null, JPushInterface.ErrorCode.TIMEOUT);
1013
+ } else flushPreLoginQueue(context, owner);
1014
+ }
1015
+
1016
+ private void executeQueuedRequestWithRetry(Context context, QueuedRequest req, Attempt owner) {
1017
+ final long uid = JCoreStorage.getUid(context);
1018
+ final int sid = sessionId;
1019
+ for (int attempt = 0; attempt <= req.maxRetries; attempt++) {
1020
+ if (!owns(owner, context) || !req.configuration.equals(owner.configuration)
1021
+ || req.policyGeneration != JCoreStorage.getContentGeneration()
1022
+ || !JCoreStorage.isCollectionAuth(context)) break;
1023
+ long rid = attempt == 0 ? req.rid : JCoreStorage.nextRid();
1024
+ JCoreWireCodec.DecodedFrame response = sendRequestWithRidOnSocket(owner, req.command, req.version, rid,
1025
+ sid, uid, req.body, req.timeoutMs, req.policyGeneration);
1026
+ if (response != null && owns(owner, context) && req.configuration.equals(owner.configuration)
1027
+ && req.policyGeneration == JCoreStorage.getContentGeneration()) {
1028
+ int code = response.statusCode;
1029
+ if ((req.command == JCoreWireCodec.CMD_TAG_OP || req.command == JCoreWireCodec.CMD_ALIAS_OP
1030
+ || req.command == JCoreWireCodec.CMD_PROPERTY_OP) && response.body != null && response.body.length > 0) {
1031
+ code = JCoreWireCodec.extractJsonInt(JCoreWireCodec.parseJsonCommandPayload(response.body), "code", code);
883
1032
  }
1033
+ req.complete(response, code);
884
1034
  return;
885
1035
  }
886
1036
  }
887
- if (req.callback != null) {
888
- req.callback.onResult(null, JPushInterface.ErrorCode.TIMEOUT);
889
- }
1037
+ req.complete(null, JPushInterface.ErrorCode.TIMEOUT);
890
1038
  }
891
1039
 
892
- private void readLoop(final Context context, Socket socket, InputStream in) {
1040
+ private void readLoop(final Context context, Socket socket, InputStream in, Attempt owner) {
893
1041
  try {
894
- while (!socket.isClosed()) {
895
- long defaultUid = JCoreStorage.getUid(context);
896
- JCoreWireCodec.DecodedFrame frame = JCoreWireCodec.readFrameFromStream(in, defaultUid);
897
- if (frame == null) {
898
- break;
899
- }
900
- handleIncomingFrame(context, frame);
1042
+ while (!socket.isClosed() && owns(owner, context) && activeSocket == socket) {
1043
+ JCoreWireCodec.DecodedFrame frame = JCoreWireCodec.readFrameFromStream(in, JCoreStorage.getUid(context));
1044
+ if (frame == null) break;
1045
+ if (owns(owner, context) && activeSocket == socket) handleIncomingFrame(context, frame, owner, socket);
901
1046
  }
902
1047
  } catch (Throwable ignored) {
903
1048
  } finally {
904
- boolean wasCurrentSocket = false;
905
- boolean wasLoggedIn = false;
906
- boolean droppedUnexpectedly = false;
907
- synchronized (socketLock) {
908
- if (this.activeSocket == socket) {
909
- wasCurrentSocket = true;
910
- wasLoggedIn = this.loggedIn;
911
- this.loggedIn = false;
912
- droppedUnexpectedly = !socket.isClosed();
913
- try {
914
- socket.close();
915
- } catch (Throwable ignored) {
916
- }
917
- this.activeSocket = null;
918
- this.activeOutput = null;
919
- }
920
- }
921
- if (wasCurrentSocket) {
922
- cancelHeartbeatTimer();
923
- if (wasLoggedIn) {
1049
+ boolean reconnect;
1050
+ synchronized (this) {
1051
+ reconnect = owns(owner, context) && activeSocket == socket && loggedIn && !socket.isClosed();
1052
+ if (activeAttempt == owner && activeSocket == socket) {
1053
+ closeOwnedSocket(owner);
924
1054
  JCoreStorage.setConnected(context, false);
925
1055
  }
926
- for (ResponseSlot slot : pendingRequests.values()) {
927
- slot.complete(null);
928
- }
929
- pendingRequests.clear();
930
- if (wasLoggedIn && autoReconnectEnabled && droppedUnexpectedly && !JCoreStorage.isPushStopped(context)) {
931
- final long delayMs = computeReconnectDelayMs(reconnectAttempt++, reconnectBaseDelayMs);
932
- final long scheduledGen = this.connectionGeneration;
933
- heartbeatExecutor.schedule(new Runnable() {
934
- @Override
935
- public void run() {
936
- if (!started || scheduledGen != connectionGeneration || loggedIn || JCoreStorage.isPushStopped(context)) {
937
- return;
938
- }
939
- ioExecutor.execute(new Runnable() {
940
- @Override
941
- public void run() {
942
- if (!started || scheduledGen != connectionGeneration || loggedIn || JCoreStorage.isPushStopped(context)) {
943
- return;
944
- }
945
- connectAndHandshake(context);
946
- }
947
- });
948
- }
949
- }, delayMs, TimeUnit.MILLISECONDS);
950
- }
951
1056
  }
1057
+ if (reconnect) scheduleReconnect(context, owner);
952
1058
  }
953
1059
  }
954
1060
 
955
- private void handleIncomingFrame(Context context, JCoreWireCodec.DecodedFrame frame) {
1061
+ private void handleIncomingFrame(Context context, JCoreWireCodec.DecodedFrame frame, Attempt owner, Socket socket) {
956
1062
  if (frame.command == JCoreWireCodec.CMD_PUSH_MESSAGE) {
957
1063
  JCoreWireCodec.PushEnvelope envelope = JCoreWireCodec.parsePushMessage(frame);
958
1064
  if (envelope != null) {
@@ -965,7 +1071,7 @@ public final class JCoreTcpEngine {
965
1071
  long ackRid = frame.requestId != 0L ? frame.requestId : JCoreStorage.nextRid();
966
1072
  long uid = frame.uid != 0L ? frame.uid : JCoreStorage.getUid(context);
967
1073
  byte[] ackBody = JCoreWireCodec.buildMessageAckPayload(envelope.messageType, envelope.messageId);
968
- sendFrameRaw(
1074
+ sendFrameOwned(owner, socket,
969
1075
  JCoreWireCodec.CMD_MESSAGE_ACK,
970
1076
  JCoreWireCodec.VER_MESSAGE_ACK,
971
1077
  ackRid,
@@ -990,7 +1096,7 @@ public final class JCoreTcpEngine {
990
1096
  long ackRid = frame.requestId != 0L ? frame.requestId : JCoreStorage.nextRid();
991
1097
  long uid = frame.uid != 0L ? frame.uid : JCoreStorage.getUid(context);
992
1098
  byte[] ackBody = JCoreWireCodec.buildCtrlAckPayload(0, ctrl.requestId);
993
- sendFrameRaw(
1099
+ sendFrameOwned(owner, socket,
994
1100
  JCoreWireCodec.CMD_CTRL,
995
1101
  JCoreWireCodec.VER_CTRL,
996
1102
  ackRid,
@@ -1002,29 +1108,21 @@ public final class JCoreTcpEngine {
1002
1108
  return;
1003
1109
  }
1004
1110
 
1005
- ResponseSlot slot = pendingRequests.remove(frame.requestId);
1006
- if (slot != null) {
1111
+ ResponseSlot slot = pendingRequests.get(frame.requestId);
1112
+ if (slot != null && slot.owner == owner && slot.socket == socket && slot.command == frame.command && pendingRequests.remove(frame.requestId, slot)) {
1007
1113
  slot.complete(frame);
1008
1114
  }
1009
1115
  }
1010
1116
 
1117
+ private boolean isBusinessCommand(int command) {
1118
+ return command != JCoreWireCodec.CMD_REGISTER && command != JCoreWireCodec.CMD_LOGIN
1119
+ && command != JCoreWireCodec.CMD_HEARTBEAT && command != JCoreWireCodec.CMD_MESSAGE_ACK
1120
+ && command != JCoreWireCodec.CMD_CTRL && command != JCoreWireCodec.CMD_PUSH_CTRL;
1121
+ }
1122
+
1011
1123
  public boolean sendFrameRaw(int command, int version, long requestId, int sid, long uid, byte[] body) {
1012
- byte[] packet = JCoreWireCodec.encodeFrame(command, version, requestId, sid, uid, body);
1013
- if (packet.length == 0) {
1014
- return false;
1015
- }
1016
- synchronized (socketLock) {
1017
- if (activeOutput == null) {
1018
- return false;
1019
- }
1020
- try {
1021
- activeOutput.write(packet);
1022
- activeOutput.flush();
1023
- return true;
1024
- } catch (Throwable ignored) {
1025
- return false;
1026
- }
1027
- }
1124
+ Attempt owner = activeAttempt;
1125
+ return owner != null && owner.socket != null && sendFrameOwned(owner, owner.socket, command, version, requestId, sid, uid, body);
1028
1126
  }
1029
1127
 
1030
1128
  public JCoreWireCodec.DecodedFrame sendRequest(int command, int version, byte[] body, long timeoutMs) {
@@ -1032,44 +1130,61 @@ public final class JCoreTcpEngine {
1032
1130
  return sendRequestOnSocket(command, version, this.sessionId, uid, body, timeoutMs);
1033
1131
  }
1034
1132
 
1035
- private JCoreWireCodec.DecodedFrame sendRequestOnSocket(
1036
- int command,
1037
- int version,
1038
- int sid,
1039
- long uid,
1040
- byte[] body,
1041
- long timeoutMs
1042
- ) {
1043
- long rid = JCoreStorage.nextRid();
1044
- return sendRequestWithRidOnSocket(command, version, rid, sid, uid, body, timeoutMs);
1045
- }
1046
-
1047
- private JCoreWireCodec.DecodedFrame sendRequestWithRidOnSocket(
1048
- int command,
1049
- int version,
1050
- long rid,
1051
- int sid,
1052
- long uid,
1053
- byte[] body,
1054
- long timeoutMs
1055
- ) {
1056
- ResponseSlot slot = new ResponseSlot();
1133
+ private JCoreWireCodec.DecodedFrame sendRequestOnSocket(int command, int version, int sid, long uid, byte[] body, long timeoutMs) {
1134
+ return sendRequestOnSocket(activeAttempt, command, version, sid, uid, body, timeoutMs);
1135
+ }
1136
+
1137
+ private JCoreWireCodec.DecodedFrame sendRequestOnSocket(Attempt owner, int command, int version, int sid, long uid, byte[] body, long timeoutMs) {
1138
+ return sendRequestWithRidOnSocket(owner, command, version, JCoreStorage.nextRid(), sid, uid, body, timeoutMs,
1139
+ isBusinessCommand(command) ? JCoreStorage.getContentGeneration() : -1L);
1140
+ }
1141
+
1142
+ private JCoreWireCodec.DecodedFrame sendRequestWithRidOnSocket(Attempt owner, int command, int version, long rid,
1143
+ int sid, long uid, byte[] body, long timeoutMs, long policyGeneration) {
1144
+ if (!owns(owner, null) || owner.socket == null) return null;
1145
+ ResponseSlot slot = new ResponseSlot(owner, owner.socket, command);
1057
1146
  pendingRequests.put(rid, slot);
1058
- if (!sendFrameRaw(command, version, rid, sid, uid, body)) {
1059
- pendingRequests.remove(rid);
1147
+ if (!sendFrameOwned(owner, slot.socket, command, version, rid, sid, uid, body, policyGeneration)) {
1148
+ pendingRequests.remove(rid, slot);
1060
1149
  return null;
1061
1150
  }
1062
- try {
1063
- slot.latch.await(timeoutMs, TimeUnit.MILLISECONDS);
1064
- } catch (InterruptedException e) {
1065
- Thread.currentThread().interrupt();
1066
- } finally {
1067
- pendingRequests.remove(rid);
1151
+ try { slot.latch.await(timeoutMs, TimeUnit.MILLISECONDS); }
1152
+ catch (InterruptedException e) { Thread.currentThread().interrupt(); }
1153
+ finally { pendingRequests.remove(rid, slot); }
1154
+ return owns(owner, null) ? slot.response : null;
1155
+ }
1156
+
1157
+ private boolean sendFrameOwned(Attempt owner, Socket socket, int command, int version, long rid,
1158
+ int sid, long uid, byte[] body) {
1159
+ return sendFrameOwned(owner, socket, command, version, rid, sid, uid, body,
1160
+ isBusinessCommand(command) ? JCoreStorage.getContentGeneration() : -1L);
1161
+ }
1162
+
1163
+ private boolean sendFrameOwned(Attempt owner, Socket socket, int command, int version, long rid,
1164
+ int sid, long uid, byte[] body, long policyGeneration) {
1165
+ byte[] packet = JCoreWireCodec.encodeFrame(command, version, rid, sid, uid, body);
1166
+ synchronized (owner.writerLock) {
1167
+ final OutputStream output;
1168
+ synchronized (socketLock) {
1169
+ boolean stopControl = command == JCoreWireCodec.CMD_PUSH_CTRL
1170
+ && policyGeneration == STOP_CONTROL_POLICY && owner.stopScheduled;
1171
+ if (activeAttempt != owner || activeSocket != socket || owner.output == null || packet.length == 0
1172
+ || (!stopControl && (!owns(owner, null)
1173
+ || (policyGeneration >= 0L && policyGeneration != JCoreStorage.getContentGeneration())))
1174
+ || (isBusinessCommand(command) && (!JCoreStorage.isCollectionAuth(null)
1175
+ || JCoreStorage.isPushStopped(null)))) return false;
1176
+ output = owner.output;
1177
+ }
1178
+ try { output.write(packet); output.flush(); return true; }
1179
+ catch (Exception ignored) { return false; }
1068
1180
  }
1069
- return slot.response;
1070
1181
  }
1071
1182
 
1072
1183
  public String[] resolveUdpSisEndpoint(Context context, String sisHost, int sisPort) {
1184
+ return resolveUdpSisEndpoint(context, sisHost, sisPort, null);
1185
+ }
1186
+
1187
+ private String[] resolveUdpSisEndpoint(Context context, String sisHost, int sisPort, Attempt owner) {
1073
1188
  DatagramSocket udpSocket = null;
1074
1189
  try {
1075
1190
  String requestJson = JCoreWireCodec.buildSisRequestJson(
@@ -1097,7 +1212,10 @@ public final class JCoreTcpEngine {
1097
1212
  System.arraycopy(recvBuf, 0, respBytes, 0, respBytes.length);
1098
1213
  String decodedJson = JCoreWireCodec.decodeUdpSisPacket(respBytes);
1099
1214
  if (!decodedJson.isEmpty()) {
1100
- JCoreStorage.saveSisConfig(context, decodedJson);
1215
+ synchronized (this) {
1216
+ if (owner != null && !owns(owner, context)) return null;
1217
+ JCoreStorage.saveSisConfig(context, decodedJson);
1218
+ }
1101
1219
  }
1102
1220
  Set<String> ips = JCoreWireCodec.extractJsonStringArray(decodedJson, "ips");
1103
1221
  if (ips.isEmpty()) {
@@ -1120,6 +1238,10 @@ public final class JCoreTcpEngine {
1120
1238
  }
1121
1239
 
1122
1240
  public String[] resolveSisEndpoint(Context context) {
1241
+ return resolveSisEndpoint(context, null);
1242
+ }
1243
+
1244
+ private String[] resolveSisEndpoint(Context context, Attempt owner) {
1123
1245
  if (customHost != null && !customHost.isEmpty() && customPort > 0) {
1124
1246
  return new String[] { customHost, String.valueOf(customPort) };
1125
1247
  }
@@ -1166,7 +1288,10 @@ public final class JCoreTcpEngine {
1166
1288
  byte[] decrypted = JCoreCrypto.aesCbcDecrypt(buffer.toByteArray(), aesKey, SIS_AES_IV, 32);
1167
1289
  byte[] plain = JCoreCrypto.gzipDecompress(decrypted);
1168
1290
  String plainStr = new String(plain, StandardCharsets.UTF_8);
1169
- JCoreStorage.saveSisConfig(context, plainStr);
1291
+ synchronized (this) {
1292
+ if (owner != null && !owns(owner, context)) return null;
1293
+ JCoreStorage.saveSisConfig(context, plainStr);
1294
+ }
1170
1295
  Set<String> ips = JCoreWireCodec.extractJsonStringArray(plainStr, "ips");
1171
1296
  if (ips.isEmpty()) {
1172
1297
  ips = JCoreWireCodec.extractJsonStringArray(plainStr, "ssl_ips");
@@ -1201,7 +1326,7 @@ public final class JCoreTcpEngine {
1201
1326
  }
1202
1327
  }
1203
1328
  }
1204
- return resolveUdpSisEndpoint(context, defaultSisUdpHost, defaultSisUdpPort);
1329
+ return resolveUdpSisEndpoint(context, defaultSisUdpHost, defaultSisUdpPort, owner);
1205
1330
  }
1206
1331
 
1207
1332
  /**
@@ -1212,6 +1337,8 @@ public final class JCoreTcpEngine {
1212
1337
  * and X-App-Key: appKey.
1213
1338
  */
1214
1339
  public boolean sendHttpReport(Context context, String reportItemJson) {
1340
+ final long policyGeneration = JCoreStorage.getContentGeneration();
1341
+ if (!JCoreStorage.isCollectionAuth(context) || JCoreStorage.isPushStopped(context)) return false;
1215
1342
  OutputStream out = null;
1216
1343
  InputStream in = null;
1217
1344
  try {
@@ -1272,6 +1399,7 @@ public final class JCoreTcpEngine {
1272
1399
  conn.setRequestProperty("X-App-Key", appKey);
1273
1400
  conn.setRequestProperty("Authorization", authHeader);
1274
1401
 
1402
+ if (policyGeneration != JCoreStorage.getContentGeneration() || !JCoreStorage.isCollectionAuth(context) || JCoreStorage.isPushStopped(context)) return false;
1275
1403
  out = conn.getOutputStream();
1276
1404
  out.write(encryptedBody);
1277
1405
  out.flush();