From fe52c3fa23f1e858a012d7328e125e8fe9ed2b11 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 24 Nov 2023 23:58:26 +0530 Subject: [PATCH 01/73] Implemented connectionRecoveryKey --- .../ably/lib/types/ConnectionRecoveryKey.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/src/main/java/io/ably/lib/types/ConnectionRecoveryKey.java diff --git a/lib/src/main/java/io/ably/lib/types/ConnectionRecoveryKey.java b/lib/src/main/java/io/ably/lib/types/ConnectionRecoveryKey.java new file mode 100644 index 000000000..c7be39f72 --- /dev/null +++ b/lib/src/main/java/io/ably/lib/types/ConnectionRecoveryKey.java @@ -0,0 +1,62 @@ +package io.ably.lib.types; + +import com.google.gson.JsonSyntaxException; + +import java.util.HashMap; +import java.util.Map; + +import io.ably.lib.util.Log; +import io.ably.lib.util.Serialisation; + +public class ConnectionRecoveryKey { + private static final String TAG = "RecoveryKey"; + + private final String connectionKey; + private final long msgSerial; + /** + * Key - channel name + *

+ * Value - channelSerial + */ + private final Map serials = new HashMap<>(); + + public ConnectionRecoveryKey(String connectionKey, long msgSerial) { + this.connectionKey = connectionKey; + this.msgSerial = msgSerial; + } + + public String getConnectionKey() { + return connectionKey; + } + + public long getMsgSerial() { + return msgSerial; + } + + public Map getSerials() { + return serials; + } + + public void setSerials(Map serials) { + this.serials.clear(); + this.serials.putAll(serials); + } + + public void addSerial(String channelName, String channelSerial) { + this.serials.put(channelName, channelSerial); + } + + public String asJson() { + return Serialisation.gson.toJson(this); + } + + public static ConnectionRecoveryKey fromJson(String json) { + try { + return Serialisation.gson.fromJson(json, ConnectionRecoveryKey.class); + } catch (JsonSyntaxException e) { + Log.e(TAG, "Cannot create recovery key from json: " + e.getMessage()); + return null; + } + } + +} From c3e75761cc3acfad2296e982bb36a6f5cd893d44 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sat, 25 Nov 2023 00:40:21 +0530 Subject: [PATCH 02/73] Renamed file to recoverykeycontext --- ...onnectionRecoveryKey.java => RecoveryKeyContext.java} | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) rename lib/src/main/java/io/ably/lib/types/{ConnectionRecoveryKey.java => RecoveryKeyContext.java} (83%) diff --git a/lib/src/main/java/io/ably/lib/types/ConnectionRecoveryKey.java b/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java similarity index 83% rename from lib/src/main/java/io/ably/lib/types/ConnectionRecoveryKey.java rename to lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java index c7be39f72..da2398c3b 100644 --- a/lib/src/main/java/io/ably/lib/types/ConnectionRecoveryKey.java +++ b/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java @@ -8,7 +8,7 @@ import io.ably.lib.util.Log; import io.ably.lib.util.Serialisation; -public class ConnectionRecoveryKey { +public class RecoveryKeyContext { private static final String TAG = "RecoveryKey"; private final String connectionKey; @@ -20,7 +20,7 @@ public class ConnectionRecoveryKey { */ private final Map serials = new HashMap<>(); - public ConnectionRecoveryKey(String connectionKey, long msgSerial) { + public RecoveryKeyContext(String connectionKey, long msgSerial) { this.connectionKey = connectionKey; this.msgSerial = msgSerial; } @@ -50,13 +50,12 @@ public String asJson() { return Serialisation.gson.toJson(this); } - public static ConnectionRecoveryKey fromJson(String json) { + public static RecoveryKeyContext fromJson(String json) { try { - return Serialisation.gson.fromJson(json, ConnectionRecoveryKey.class); + return Serialisation.gson.fromJson(json, RecoveryKeyContext.class); } catch (JsonSyntaxException e) { Log.e(TAG, "Cannot create recovery key from json: " + e.getMessage()); return null; } } - } From 254954780ffa014edc18a315246dd201696ceb4b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 27 Nov 2023 17:14:12 +0530 Subject: [PATCH 03/73] Added a test to check for encoded recovery key --- .../io/ably/lib/types/RecoveryKeyContext.java | 20 +++++++------- .../lib/types/RecoveryKeyContextTest.java | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java diff --git a/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java b/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java index da2398c3b..7d7e48522 100644 --- a/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java +++ b/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java @@ -9,7 +9,7 @@ import io.ably.lib.util.Serialisation; public class RecoveryKeyContext { - private static final String TAG = "RecoveryKey"; + private static final String TAG = "RecoveryKeyContext"; private final String connectionKey; private final long msgSerial; @@ -18,7 +18,7 @@ public class RecoveryKeyContext { *

* Value - channelSerial */ - private final Map serials = new HashMap<>(); + private final Map channelSerials = new HashMap<>(); public RecoveryKeyContext(String connectionKey, long msgSerial) { this.connectionKey = connectionKey; @@ -33,24 +33,24 @@ public long getMsgSerial() { return msgSerial; } - public Map getSerials() { - return serials; + public Map getChannelSerials() { + return channelSerials; } - public void setSerials(Map serials) { - this.serials.clear(); - this.serials.putAll(serials); + public void setChannelSerials(Map channelSerials) { + this.channelSerials.clear(); + this.channelSerials.putAll(channelSerials); } public void addSerial(String channelName, String channelSerial) { - this.serials.put(channelName, channelSerial); + this.channelSerials.put(channelName, channelSerial); } - public String asJson() { + public String encode() { return Serialisation.gson.toJson(this); } - public static RecoveryKeyContext fromJson(String json) { + public static RecoveryKeyContext decode(String json) { try { return Serialisation.gson.fromJson(json, RecoveryKeyContext.class); } catch (JsonSyntaxException e) { diff --git a/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java b/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java new file mode 100644 index 000000000..96a1ee2ce --- /dev/null +++ b/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java @@ -0,0 +1,27 @@ +package io.ably.lib.types; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class RecoveryKeyContextTest { + + @Test + public void should_encode_recovery_key_context_object() { + String expectedRecoveryKey = + "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":1,\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}"; + RecoveryKeyContext recoveryKey = new RecoveryKeyContext("uniqueKey", 1); + Map keys = new HashMap<>(); + keys.put("channel1", "1"); + keys.put("channel2", "2"); + keys.put("channel3", "3"); + recoveryKey.setChannelSerials(keys); + String encodedRecoveryKey = recoveryKey.encode(); + assertEquals("should be equal", expectedRecoveryKey, encodedRecoveryKey); + } + + +} From c767de20bf4378d06f82f4f62679906a2a82c4f9 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 27 Nov 2023 17:32:35 +0530 Subject: [PATCH 04/73] Added test for encoding and decoding recovery key --- .../lib/types/RecoveryKeyContextTest.java | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java b/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java index 96a1ee2ce..8684a8264 100644 --- a/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java +++ b/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java @@ -6,9 +6,13 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; public class RecoveryKeyContextTest { + /** + * Spec: RTN16i, RTN16f, RTN16j + */ @Test public void should_encode_recovery_key_context_object() { String expectedRecoveryKey = @@ -20,8 +24,37 @@ public void should_encode_recovery_key_context_object() { keys.put("channel3", "3"); recoveryKey.setChannelSerials(keys); String encodedRecoveryKey = recoveryKey.encode(); - assertEquals("should be equal", expectedRecoveryKey, encodedRecoveryKey); + assertEquals(expectedRecoveryKey, encodedRecoveryKey); } + /** + * Spec: RTN16i, RTN16f, RTN16j + */ + @Test + public void should_decode_recoverykey_to_recoveryKeyContextObject() { + String recoveryKey = + "{\"connectionKey\":\"key2\",\"msgSerial\":5,\"channelSerials\":{\"channel1\":\"98\",\"channel2\":\"32\",\"channel3\":\"09\"}}"; + RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(recoveryKey); + assertEquals("key2", recoveryKeyContext.getConnectionKey()); + assertEquals(5, recoveryKeyContext.getMsgSerial()); + Map expectedChannelSerials = new HashMap() + {{ + put("channel1", "98"); + put("channel2", "32"); + put("channel3", "09"); + }}; + assertEquals(expectedChannelSerials, recoveryKeyContext.getChannelSerials()); + } + + /** + * Spec: RTN16i, RTN16f, RTN16j + */ + @Test + public void should_return_null_recovery_context_while_decoding_faulty_recovery_key() { + String recoveryKey = + "{\"connectionKey\":\"key2\",\"msgSerial\":\"incorrectStringSerial\",\"channelSerials\":{\"channel1\":\"98\",\"channel2\":\"32\",\"channel3\":\"09\"}}"; + RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(recoveryKey); + assertNull(recoveryKeyContext); + } } From 25b9cdcb68c238e82a1cf88317f4fc7a98a37da2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 27 Nov 2023 23:37:04 +0530 Subject: [PATCH 05/73] Added channel serial to channel properties --- .../main/java/io/ably/lib/types/ChannelProperties.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/src/main/java/io/ably/lib/types/ChannelProperties.java b/lib/src/main/java/io/ably/lib/types/ChannelProperties.java index ea3094911..50a1ae989 100644 --- a/lib/src/main/java/io/ably/lib/types/ChannelProperties.java +++ b/lib/src/main/java/io/ably/lib/types/ChannelProperties.java @@ -13,5 +13,13 @@ public class ChannelProperties { */ public String attachSerial; + /** + * ChannelSerial contains the channelSerial from latest ProtocolMessage of action type + * Message/PresenceMessage received on the channel. + *

+ * Spec: CP2b, RTL15b + */ + public String channelSerial; + public ChannelProperties() {} } From a7938e3e383e99f25c8570f1c73db48cc7e545f6 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 27 Nov 2023 23:39:29 +0530 Subject: [PATCH 06/73] Added method to set channelSerials from recover option --- .../main/java/io/ably/lib/realtime/AblyRealtime.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 5419b83f9..f5c048df4 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -272,6 +272,18 @@ public void transferToChannels(List queuedMessa private void clear() { map.clear(); } + + protected void setChannelSerialsFromRecoverOption(HashMap serials) { + for (Map.Entry entry : serials.entrySet()) { + String channelName = entry.getKey(); + String channelSerial = entry.getValue(); + Channel channel = this.get(channelName); + if (channel != null) { + channel.properties.channelSerial = channelSerial; + } + } + } + } /******************** From 2a8bbbdc4dd21588cf192805119491f09ef2225b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 27 Nov 2023 23:47:05 +0530 Subject: [PATCH 07/73] Added method for getting channel serials to Channels class --- lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index f5c048df4..ea77799e3 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -284,6 +284,14 @@ protected void setChannelSerialsFromRecoverOption(HashMap serial } } + protected HashMap getChannelSerials() { + HashMap channelSerials = new HashMap<>(); + for (Channel channel : this.values()) { + channelSerials.put(channel.name, channel.properties.channelSerial); + } + return channelSerials; + } + } /******************** From 6461967656953a5d7c67c5bd05be04d29dc89963 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 28 Nov 2023 17:14:01 +0530 Subject: [PATCH 08/73] Marked recoveryKey field as deprecated --- lib/src/main/java/io/ably/lib/realtime/Connection.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index 473b5df4c..4e216ebf4 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -49,7 +49,9 @@ public class Connection extends EventEmitter * Spec: RTN16b, RTN16c + * @deprecated use createRecoveryKey method instead. */ + @Deprecated public String recoveryKey; /** From 180d68f0419f66e900d9c2e573b97b25ae77c291 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 28 Nov 2023 18:23:26 +0530 Subject: [PATCH 09/73] Added explicit method for creating a recovery key --- .../io/ably/lib/realtime/AblyRealtime.java | 29 +++++++++---------- .../java/io/ably/lib/realtime/Connection.java | 23 +++++++++++++++ 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index ea77799e3..d2ae41cae 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -272,26 +272,25 @@ public void transferToChannels(List queuedMessa private void clear() { map.clear(); } + } - protected void setChannelSerialsFromRecoverOption(HashMap serials) { - for (Map.Entry entry : serials.entrySet()) { - String channelName = entry.getKey(); - String channelSerial = entry.getValue(); - Channel channel = this.get(channelName); - if (channel != null) { - channel.properties.channelSerial = channelSerial; - } + protected void setChannelSerialsFromRecoverOption(HashMap serials) { + for (Map.Entry entry : serials.entrySet()) { + String channelName = entry.getKey(); + String channelSerial = entry.getValue(); + Channel channel = this.channels.get(channelName); + if (channel != null) { + channel.properties.channelSerial = channelSerial; } } + } - protected HashMap getChannelSerials() { - HashMap channelSerials = new HashMap<>(); - for (Channel channel : this.values()) { - channelSerials.put(channel.name, channel.properties.channelSerial); - } - return channelSerials; + protected HashMap getChannelSerials() { + HashMap channelSerials = new HashMap<>(); + for (Channel channel : this.channels.values()) { + channelSerials.put(channel.name, channel.properties.channelSerial); } - + return channelSerials; } /******************** diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index 4e216ebf4..96469e9e2 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -4,6 +4,7 @@ import io.ably.lib.transport.ConnectionManager; import io.ably.lib.types.AblyException; import io.ably.lib.types.ErrorInfo; +import io.ably.lib.types.RecoveryKeyContext; import io.ably.lib.util.EventEmitter; import io.ably.lib.util.Log; import io.ably.lib.util.PlatformAgentProvider; @@ -54,6 +55,28 @@ public class Connection extends EventEmitter From 1509938a3d288e428c7025f789f68f183e2e9a22 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 28 Nov 2023 18:30:04 +0530 Subject: [PATCH 10/73] Simplified recoveryKeyContext class --- .../java/io/ably/lib/realtime/Connection.java | 5 +---- .../io/ably/lib/types/RecoveryKeyContext.java | 17 ++--------------- .../ably/lib/types/RecoveryKeyContextTest.java | 11 +++++------ 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index 96469e9e2..4c4638168 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -71,10 +71,7 @@ public String createRecoveryKey() { return null; } - RecoveryKeyContext recoveryKey = new RecoveryKeyContext(key, serial); - recoveryKey.setChannelSerials(this.ably.getChannelSerials()); - - return recoveryKey.encode(); + return new RecoveryKeyContext(key, serial, ably.getChannelSerials()).encode(); } /** diff --git a/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java b/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java index 7d7e48522..c110c9af3 100644 --- a/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java +++ b/lib/src/main/java/io/ably/lib/types/RecoveryKeyContext.java @@ -13,16 +13,12 @@ public class RecoveryKeyContext { private final String connectionKey; private final long msgSerial; - /** - * Key - channel name - *

- * Value - channelSerial - */ private final Map channelSerials = new HashMap<>(); - public RecoveryKeyContext(String connectionKey, long msgSerial) { + public RecoveryKeyContext(String connectionKey, long msgSerial, Map channelSerials) { this.connectionKey = connectionKey; this.msgSerial = msgSerial; + this.channelSerials.putAll(channelSerials); } public String getConnectionKey() { @@ -37,15 +33,6 @@ public Map getChannelSerials() { return channelSerials; } - public void setChannelSerials(Map channelSerials) { - this.channelSerials.clear(); - this.channelSerials.putAll(channelSerials); - } - - public void addSerial(String channelName, String channelSerial) { - this.channelSerials.put(channelName, channelSerial); - } - public String encode() { return Serialisation.gson.toJson(this); } diff --git a/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java b/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java index 8684a8264..85d9e0127 100644 --- a/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java +++ b/lib/src/test/java/io/ably/lib/types/RecoveryKeyContextTest.java @@ -17,12 +17,11 @@ public class RecoveryKeyContextTest { public void should_encode_recovery_key_context_object() { String expectedRecoveryKey = "{\"connectionKey\":\"uniqueKey\",\"msgSerial\":1,\"channelSerials\":{\"channel1\":\"1\",\"channel2\":\"2\",\"channel3\":\"3\"}}"; - RecoveryKeyContext recoveryKey = new RecoveryKeyContext("uniqueKey", 1); - Map keys = new HashMap<>(); - keys.put("channel1", "1"); - keys.put("channel2", "2"); - keys.put("channel3", "3"); - recoveryKey.setChannelSerials(keys); + Map serials = new HashMap<>(); + serials.put("channel1", "1"); + serials.put("channel2", "2"); + serials.put("channel3", "3"); + RecoveryKeyContext recoveryKey = new RecoveryKeyContext("uniqueKey", 1, serials); String encodedRecoveryKey = recoveryKey.encode(); assertEquals(expectedRecoveryKey, encodedRecoveryKey); } From 169392e21440a28054d5e7f6783d437eddbb9c7f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 28 Nov 2023 21:49:41 +0530 Subject: [PATCH 11/73] Setting recovery key and serials from clientOption --- .../io/ably/lib/realtime/AblyRealtime.java | 21 +++++++++++-------- .../java/io/ably/lib/realtime/Connection.java | 2 +- .../ably/lib/transport/ConnectionManager.java | 2 +- .../io/ably/lib/transport/ITransport.java | 20 +++++------------- 4 files changed, 19 insertions(+), 26 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index d2ae41cae..8e3a77035 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -9,12 +9,7 @@ import io.ably.lib.rest.AblyRest; import io.ably.lib.rest.Auth; import io.ably.lib.transport.ConnectionManager; -import io.ably.lib.types.AblyException; -import io.ably.lib.types.ChannelOptions; -import io.ably.lib.types.ClientOptions; -import io.ably.lib.types.ErrorInfo; -import io.ably.lib.types.ProtocolMessage; -import io.ably.lib.types.ReadOnlyMap; +import io.ably.lib.types.*; import io.ably.lib.util.InternalMap; import io.ably.lib.util.Log; @@ -71,6 +66,14 @@ public void onConnectionStateChanged(ConnectionStateListener.ConnectionStateChan } }); + if (options.recover != null && !options.recover.isEmpty()) { + RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(options.recover); + if (recoveryKeyContext != null) { + setChannelSerialsFromRecoverOption(recoveryKeyContext.getChannelSerials()); + connection.connectionManager.msgSerial = recoveryKeyContext.getMsgSerial(); //RTN16f + } + } + if(options.autoConnect) connection.connect(); } @@ -274,7 +277,7 @@ private void clear() { } } - protected void setChannelSerialsFromRecoverOption(HashMap serials) { + protected void setChannelSerialsFromRecoverOption(Map serials) { for (Map.Entry entry : serials.entrySet()) { String channelName = entry.getKey(); String channelSerial = entry.getValue(); @@ -285,8 +288,8 @@ protected void setChannelSerialsFromRecoverOption(HashMap serial } } - protected HashMap getChannelSerials() { - HashMap channelSerials = new HashMap<>(); + protected Map getChannelSerials() { + Map channelSerials = new HashMap<>(); for (Channel channel : this.channels.values()) { channelSerials.put(channel.name, channel.properties.channelSerial); } diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index 4c4638168..fa342ec48 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -71,7 +71,7 @@ public String createRecoveryKey() { return null; } - return new RecoveryKeyContext(key, serial, ably.getChannelSerials()).encode(); + return new RecoveryKeyContext(key, connectionManager.msgSerial, ably.getChannelSerials()).encode(); } /** diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index 29f33706a..da3cc9116 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -1905,7 +1905,7 @@ private boolean isFatalError(ErrorInfo err) { private boolean suppressRetry; /* for tests only; modified via reflection */ private ITransport transport; private long suspendTime; - private long msgSerial; + public long msgSerial; private long lastActivity; private CMConnectivityListener connectivityListener; private long connectionStateTtl = Defaults.connectionStateTtl; diff --git a/lib/src/main/java/io/ably/lib/transport/ITransport.java b/lib/src/main/java/io/ably/lib/transport/ITransport.java index 93b426f3b..9dadb54a5 100644 --- a/lib/src/main/java/io/ably/lib/transport/ITransport.java +++ b/lib/src/main/java/io/ably/lib/transport/ITransport.java @@ -1,10 +1,6 @@ package io.ably.lib.transport; -import io.ably.lib.types.AblyException; -import io.ably.lib.types.ClientOptions; -import io.ably.lib.types.ErrorInfo; -import io.ably.lib.types.Param; -import io.ably.lib.types.ProtocolMessage; +import io.ably.lib.types.*; import io.ably.lib.util.AgentHeaderCreator; import io.ably.lib.util.Log; import io.ably.lib.util.PlatformAgentProvider; @@ -14,8 +10,6 @@ import java.util.Arrays; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public interface ITransport { @@ -73,15 +67,11 @@ public Param[] getConnectParams(Param[] baseParams) { paramList.add(new Param("resume", connectionKey)); if(connectionSerial != null) paramList.add(new Param("connectionSerial", connectionSerial)); - } else if(options.recover != null) { + } else if(options.recover != null && !options.recover.isEmpty()) { // RTN16k mode = Mode.recover; - Pattern recoverSpec = Pattern.compile("^([\\w\\-\\!]+):(\\-?\\d+)$"); - Matcher match = recoverSpec.matcher(options.recover); - if(match.matches()) { - paramList.add(new Param("recover", match.group(1))); - paramList.add(new Param("connectionSerial", match.group(2))); - } else { - Log.e(TAG, "Invalid recover string specified"); + RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(options.recover); + if (recoveryKeyContext != null) { + paramList.add(new Param("recover", recoveryKeyContext.getConnectionKey())); } } if(options.clientId != null) From c6af891b341ce864c67ad65114b343c6430fbe3b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 28 Nov 2023 22:17:34 +0530 Subject: [PATCH 12/73] Refactored recoverykey to use createRecoveryKey method --- .../main/java/io/ably/lib/transport/ConnectionManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index da3cc9116..7b18e7a9c 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -1196,7 +1196,7 @@ private void onChannelMessage(ProtocolMessage message) { if(message.connectionSerial != null) { connection.serial = message.connectionSerial.longValue(); if (connection.key != null) - connection.recoveryKey = connection.key + ":" + message.connectionSerial; + connection.recoveryKey = connection.createRecoveryKey(); } channels.onMessage(message); } @@ -1243,7 +1243,7 @@ private synchronized void onConnected(ProtocolMessage message) { if(message.connectionSerial != null) { connection.serial = message.connectionSerial; if (connection.key != null) - connection.recoveryKey = connection.key + ":" + message.connectionSerial; + connection.recoveryKey = connection.createRecoveryKey(); } ConnectionDetails connectionDetails = message.connectionDetails; From e7dc410547ecae9eddcc46c10195b21ae9b02f02 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 28 Nov 2023 22:33:40 +0530 Subject: [PATCH 13/73] Removed all connection serial references from the code --- .../java/io/ably/lib/realtime/Connection.java | 10 ---------- .../io/ably/lib/transport/ConnectionManager.java | 16 ++++------------ .../java/io/ably/lib/transport/ITransport.java | 3 --- .../java/io/ably/lib/types/ProtocolMessage.java | 4 ---- 4 files changed, 4 insertions(+), 29 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index fa342ec48..443aef04f 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -81,16 +81,6 @@ public String createRecoveryKey() { */ public String id; - /** - * The serial number of the last message to be received on this connection, - * used automatically by the library when recovering or resuming a connection. - * When recovering a connection explicitly, the recoveryKey is used in the recover - * client options as it contains both the key and the last message serial. - *

- * Spec: RTN10 - */ - public long serial; - /** * Explicitly calling connect() is unnecessary unless the autoConnect attribute of the {@link io.ably.lib.types.ClientOptions} * object is false. diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index 7b18e7a9c..17d7b6489 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -1193,12 +1193,8 @@ public void onMessage(ITransport transport, ProtocolMessage message) throws Ably } private void onChannelMessage(ProtocolMessage message) { - if(message.connectionSerial != null) { - connection.serial = message.connectionSerial.longValue(); - if (connection.key != null) - connection.recoveryKey = connection.createRecoveryKey(); - } channels.onMessage(message); + connection.recoveryKey = connection.createRecoveryKey(); } private synchronized void onConnected(ProtocolMessage message) { @@ -1240,12 +1236,6 @@ private synchronized void onConnected(ProtocolMessage message) { connection.id = message.connectionId; - if(message.connectionSerial != null) { - connection.serial = message.connectionSerial; - if (connection.key != null) - connection.recoveryKey = connection.createRecoveryKey(); - } - ConnectionDetails connectionDetails = message.connectionDetails; /* Get any parameters from connectionDetails. */ connection.key = connectionDetails.connectionKey; //RTN16d @@ -1260,6 +1250,9 @@ private synchronized void onConnected(ProtocolMessage message) { requestState(transport, new StateIndication(ConnectionState.failed, e.errorInfo)); return; } + + connection.recoveryKey = connection.createRecoveryKey(); + /* indicated connected currentState */ final StateIndication stateIndication = new StateIndication(ConnectionState.connected, error, null, null, reattachOnResumeFailure); @@ -1504,7 +1497,6 @@ private class ConnectParams extends TransportParams { ConnectParams(ClientOptions options, PlatformAgentProvider platformAgentProvider) { super(options, platformAgentProvider); this.connectionKey = connection.key; - this.connectionSerial = String.valueOf(connection.serial); this.port = Defaults.getPort(options); } } diff --git a/lib/src/main/java/io/ably/lib/transport/ITransport.java b/lib/src/main/java/io/ably/lib/transport/ITransport.java index 9dadb54a5..ad424a64e 100644 --- a/lib/src/main/java/io/ably/lib/transport/ITransport.java +++ b/lib/src/main/java/io/ably/lib/transport/ITransport.java @@ -33,7 +33,6 @@ class TransportParams { protected String host; protected int port; protected String connectionKey; - protected String connectionSerial; protected Mode mode; protected boolean heartbeats; private final PlatformAgentProvider platformAgentProvider; @@ -65,8 +64,6 @@ public Param[] getConnectParams(Param[] baseParams) { if(connectionKey != null) { mode = Mode.resume; paramList.add(new Param("resume", connectionKey)); - if(connectionSerial != null) - paramList.add(new Param("connectionSerial", connectionSerial)); } else if(options.recover != null && !options.recover.isEmpty()) { // RTN16k mode = Mode.recover; RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(options.recover); diff --git a/lib/src/main/java/io/ably/lib/types/ProtocolMessage.java b/lib/src/main/java/io/ably/lib/types/ProtocolMessage.java index 1a9d42629..1d1d3bc69 100644 --- a/lib/src/main/java/io/ably/lib/types/ProtocolMessage.java +++ b/lib/src/main/java/io/ably/lib/types/ProtocolMessage.java @@ -98,7 +98,6 @@ public ProtocolMessage(Action action, String channel) { public String channel; public String channelSerial; public String connectionId; - public Long connectionSerial; public Long msgSerial; public long timestamp; public Message[] messages; @@ -198,9 +197,6 @@ ProtocolMessage readMsgpack(MessageUnpacker unpacker) throws IOException { case "connectionId": connectionId = unpacker.unpackString(); break; - case "connectionSerial": - connectionSerial = Long.valueOf(unpacker.unpackLong()); - break; case "msgSerial": msgSerial = Long.valueOf(unpacker.unpackLong()); break; From 130305b540571be2e387db45bde1a8e8072c5b06 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 28 Nov 2023 22:58:52 +0530 Subject: [PATCH 14/73] Added explicit null checks for recoveryKey --- lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java | 3 ++- .../main/java/io/ably/lib/transport/ConnectionManager.java | 5 +++++ lib/src/main/java/io/ably/lib/transport/ITransport.java | 5 +++-- lib/src/main/java/io/ably/lib/util/StringUtils.java | 5 +++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 8e3a77035..8ac553c33 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -12,6 +12,7 @@ import io.ably.lib.types.*; import io.ably.lib.util.InternalMap; import io.ably.lib.util.Log; +import io.ably.lib.util.StringUtils; /** * A client that extends the functionality of the {@link AblyRest} and provides additional realtime-specific features. @@ -66,7 +67,7 @@ public void onConnectionStateChanged(ConnectionStateListener.ConnectionStateChan } }); - if (options.recover != null && !options.recover.isEmpty()) { + if (!StringUtils.isNullOrEmpty(options.recover)) { RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(options.recover); if (recoveryKeyContext != null) { setChannelSerialsFromRecoverOption(recoveryKeyContext.getChannelSerials()); diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index 17d7b6489..905e8728f 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -34,6 +34,7 @@ import io.ably.lib.util.Log; import io.ably.lib.util.PlatformAgentProvider; import io.ably.lib.util.ReconnectionStrategy; +import io.ably.lib.util.StringUtils; public class ConnectionManager implements ConnectListener { final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); @@ -1202,6 +1203,10 @@ private synchronized void onConnected(ProtocolMessage message) { boolean reattachOnResumeFailure = false; // this will indicate that channel must reattach when connected // event is received + boolean isConnectionResumeOrRecoverAttempt = !StringUtils.isNullOrEmpty(connection.key) || + !StringUtils.isNullOrEmpty(ably.options.recover); + ably.options.recover = null; // RTN16k, explicitly setting null, so it won't be used for subsequent connection requests + connection.reason = error; if (connection.id != null) { // there was a previous connection, so this is a resume and RTN15c applies Log.d(TAG, "There was a connection resume"); diff --git a/lib/src/main/java/io/ably/lib/transport/ITransport.java b/lib/src/main/java/io/ably/lib/transport/ITransport.java index ad424a64e..9f077bf95 100644 --- a/lib/src/main/java/io/ably/lib/transport/ITransport.java +++ b/lib/src/main/java/io/ably/lib/transport/ITransport.java @@ -4,6 +4,7 @@ import io.ably.lib.util.AgentHeaderCreator; import io.ably.lib.util.Log; import io.ably.lib.util.PlatformAgentProvider; +import io.ably.lib.util.StringUtils; import java.io.IOException; import java.util.ArrayList; @@ -61,10 +62,10 @@ public Param[] getConnectParams(Param[] baseParams) { paramList.add(new Param("format", (options.useBinaryProtocol ? "msgpack" : "json"))); if(!options.echoMessages) paramList.add(new Param("echo", "false")); - if(connectionKey != null) { + if(!StringUtils.isNullOrEmpty(connectionKey)) { mode = Mode.resume; paramList.add(new Param("resume", connectionKey)); - } else if(options.recover != null && !options.recover.isEmpty()) { // RTN16k + } else if(!StringUtils.isNullOrEmpty(options.recover)) { // RTN16k mode = Mode.recover; RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(options.recover); if (recoveryKeyContext != null) { diff --git a/lib/src/main/java/io/ably/lib/util/StringUtils.java b/lib/src/main/java/io/ably/lib/util/StringUtils.java index 97f876b4a..d527fa105 100644 --- a/lib/src/main/java/io/ably/lib/util/StringUtils.java +++ b/lib/src/main/java/io/ably/lib/util/StringUtils.java @@ -4,6 +4,11 @@ import io.ably.lib.http.HttpCore; public class StringUtils { + + public static boolean isNullOrEmpty(String value) { + return value == null || value.isEmpty(); + } + public static Serialisation.FromJsonElement fromJsonElement = new Serialisation.FromJsonElement() { @Override public String fromJsonElement(JsonElement e) { From 3c0452381a0c18d9d8261eeb2e8e55b130da21ed Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 29 Nov 2023 17:55:30 +0530 Subject: [PATCH 15/73] Implemented channel serial for message reeived --- .../java/io/ably/lib/realtime/ChannelBase.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 5042d1c9a..732484d3e 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -37,6 +37,7 @@ import io.ably.lib.util.EventEmitter; import io.ably.lib.util.Log; import io.ably.lib.util.ReconnectionStrategy; +import io.ably.lib.util.StringUtils; /** * Enables messages to be published and subscribed to. @@ -248,8 +249,9 @@ private void attachImpl(final boolean forceReattach, final CompletionListener li } } if(this.decodeFailureRecoveryInProgress) { - attachMessage.channelSerial = this.lastPayloadProtocolMessageChannelSerial; + Log.v(TAG, "attach(); message decode recovery in progress."); } + attachMessage.channelSerial = properties.channelSerial; try { if (listener != null) { on(new ChannelStateCompletionListener(listener, ChannelState.attached, ChannelState.failed)); @@ -850,7 +852,6 @@ private void onMessage(final ProtocolMessage protocolMessage) { } lastPayloadMessageId = lastMessage.id; - lastPayloadProtocolMessageChannelSerial = protocolMessage.channelSerial; for (final Message msg : messages) { this.listeners.onMessage(msg); @@ -1264,6 +1265,15 @@ else if(stateChange.current.equals(failureState)) { } void onChannelMessage(ProtocolMessage msg) { + // RTL15b + if (!StringUtils.isNullOrEmpty(msg.channelSerial) && (msg.action == Action.message || + msg.action == Action.presence || msg.action == Action.attached)) { + Log.v(TAG, String.format( + Locale.ROOT, "Setting channel serial for channelName - %s, previous - %s, current - %s", + name, properties.channelSerial, msg.channelSerial)); + properties.channelSerial = msg.channelSerial; + } + switch(msg.action) { case attached: setAttached(msg); @@ -1369,7 +1379,6 @@ public void once(ChannelState state, ChannelStateListener listener) { */ private Set modes; private String lastPayloadMessageId; - private String lastPayloadProtocolMessageChannelSerial; private boolean decodeFailureRecoveryInProgress; private final DecodingContext decodingContext; } From 0efffdfe151d00c0592e64b730d0c3ff074b7a63 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 29 Nov 2023 18:14:44 +0530 Subject: [PATCH 16/73] Added missing implementation for channel detach when attached msg received --- .../main/java/io/ably/lib/realtime/ChannelBase.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 732484d3e..effa5dfee 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -401,7 +401,15 @@ private void setAttached(ProtocolMessage message) { Log.v(TAG, String.format(Locale.ROOT, "Server initiated attach for channel %s", name)); /* emit UPDATE event according to RTL12 */ emitUpdate(null, resumed); - } else { + } else if (state == ChannelState.detaching || state == ChannelState.detached) { //RTL5k + Log.v(TAG, "setAttached(): channel is in detaching state so no need to attach it!"); + try { + detach(); + } catch (AblyException e) { + Log.e(TAG, e.getMessage(), e); + } + } + else { this.attachResume = true; setState(ChannelState.attached, message.error, resumed); presence.setAttached(message.hasFlag(Flag.has_presence), this.ably.connection.id); From 3ebeed700346cb7d6292f1246e07b9ff87d9815d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 29 Nov 2023 18:29:37 +0530 Subject: [PATCH 17/73] Updated code to send explicit detach message when attached received in detach state --- .../main/java/io/ably/lib/realtime/ChannelBase.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index effa5dfee..7d0816377 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -327,7 +327,10 @@ private void detachImpl(CompletionListener listener) throws AblyException { if(!connectionManager.isActive()) throw AblyException.fromErrorInfo(connectionManager.getStateErrorInfo()); - /* send detach request */ + sendDetachMessage(listener); + } + + private void sendDetachMessage(CompletionListener listener) throws AblyException { ProtocolMessage detachMessage = new ProtocolMessage(Action.detach, this.name); try { if (listener != null) { @@ -340,7 +343,7 @@ private void detachImpl(CompletionListener listener) throws AblyException { } else { setState(ChannelState.detaching, null); } - connectionManager.send(detachMessage, true, null); + ably.connection.connectionManager.send(detachMessage, true, null); } catch(AblyException e) { throw e; } @@ -402,9 +405,9 @@ private void setAttached(ProtocolMessage message) { /* emit UPDATE event according to RTL12 */ emitUpdate(null, resumed); } else if (state == ChannelState.detaching || state == ChannelState.detached) { //RTL5k - Log.v(TAG, "setAttached(): channel is in detaching state so no need to attach it!"); + Log.v(TAG, "setAttached(): channel is in detaching state, as per RTL5k sending detach message!"); try { - detach(); + sendDetachMessage(null); } catch (AblyException e) { Log.e(TAG, e.getMessage(), e); } From 21661c426a7c1ddacaffe94e85afc3e102e3bbcf Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 29 Nov 2023 22:42:32 +0530 Subject: [PATCH 18/73] Created internal presencemap for internal presence --- .../java/io/ably/lib/realtime/Presence.java | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index aeaf2a8b7..19686f587 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -1050,7 +1050,7 @@ synchronized Collection get(Param[] params) throws AblyExceptio * false if the message is already superseded */ synchronized boolean put(PresenceMessage item) { - String key = item.memberKey(); + String key = memberKey(item); /* we've seen this member, so do not remove it at the end of sync */ if(residualMembers != null) residualMembers.remove(key); @@ -1145,7 +1145,7 @@ synchronized Collection values(boolean wait) throws AblyExcepti * @return */ synchronized boolean remove(PresenceMessage item) { - String key = item.memberKey(); + String key = memberKey(item); if (hasNewerItem(key, item)) return false; PresenceMessage existingItem = members.remove(key); @@ -1228,13 +1228,36 @@ synchronized void replaceMembersIfNeeded(String connectionId) { } + /** + * Combines clientId and connectionId to ensure that multiple connected clients with an identical clientId are uniquely identifiable. + * A string function that returns the combined clientId and connectionId. + *

+ * Spec: TP3h + * @return A combination of clientId and connectionId. + */ + public String memberKey(PresenceMessage item) { + return item.memberKey(); + } + private boolean syncInProgress; private Collection residualMembers; private final HashMap members = new HashMap(); } + private class InternalPresenceMap extends PresenceMap { + /** + * Get the member key for the internal PresenceMessage. + * Spec: RTP17h + * @return key of the presence message + */ + @Override + public String memberKey(PresenceMessage item) { + return item.clientId; + } + } + private final PresenceMap presence = new PresenceMap(); - private final PresenceMap internalPresence = new PresenceMap(); + private final PresenceMap internalPresence = new InternalPresenceMap(); /************************************ * general From d4654891eccec9660ad13b7a767fe0db1a7c8e27 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 30 Nov 2023 17:56:23 +0530 Subject: [PATCH 19/73] Clearing channel serial as per RTP5a1 --- lib/src/main/java/io/ably/lib/realtime/ChannelBase.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 7d0816377..d38a74df1 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -132,6 +132,11 @@ private void setState(ChannelState newState, ErrorInfo reason, boolean resumed, this.retryCount = 0; } + // RTP5a1 + if (newState == ChannelState.detached || newState == ChannelState.suspended || newState == ChannelState.failed) { + properties.channelSerial = null; + } + if(notifyStateChange) { /* broadcast state change */ emit(newState, stateChange); From b42ff87ad98c55eeefb13d6f372e843fd5511cf2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 1 Dec 2023 16:39:47 +0530 Subject: [PATCH 20/73] resetting message serial on failed connection resume or recover --- .../main/java/io/ably/lib/transport/ConnectionManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index 905e8728f..34d006932 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -1205,6 +1205,10 @@ private synchronized void onConnected(ProtocolMessage message) { boolean isConnectionResumeOrRecoverAttempt = !StringUtils.isNullOrEmpty(connection.key) || !StringUtils.isNullOrEmpty(ably.options.recover); + boolean failedResumeOrRecover = !message.connectionId.equals(connection.id) && message.error != null; // RTN15c7, RTN16d + if (isConnectionResumeOrRecoverAttempt && failedResumeOrRecover) { // RTN15c7 + msgSerial = 0; + } ably.options.recover = null; // RTN16k, explicitly setting null, so it won't be used for subsequent connection requests connection.reason = error; From 5b8ab5efa9e3da8b85d048ed05b59a7f62d7fae4 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 1 Dec 2023 16:44:39 +0530 Subject: [PATCH 21/73] Fixed AblyRealtime as class imports --- lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 8ac553c33..c9b9a9d4d 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -9,7 +9,13 @@ import io.ably.lib.rest.AblyRest; import io.ably.lib.rest.Auth; import io.ably.lib.transport.ConnectionManager; -import io.ably.lib.types.*; +import io.ably.lib.types.AblyException; +import io.ably.lib.types.ChannelOptions; +import io.ably.lib.types.ClientOptions; +import io.ably.lib.types.ErrorInfo; +import io.ably.lib.types.ProtocolMessage; +import io.ably.lib.types.ReadOnlyMap; +import io.ably.lib.types.RecoveryKeyContext; import io.ably.lib.util.InternalMap; import io.ably.lib.util.Log; import io.ably.lib.util.StringUtils; From bb42aea29baf964de55957e6fb3f3221e73f681f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 1 Dec 2023 17:43:03 +0530 Subject: [PATCH 22/73] refactored ably protocol and agent headers in accordance with version id --- .../main/java/io/ably/lib/http/HttpCore.java | 2 +- .../java/io/ably/lib/transport/Defaults.java | 20 +++++++------------ .../io/ably/lib/transport/ITransport.java | 2 +- .../io/ably/lib/types/ChannelProperties.java | 3 +++ .../java/io/ably/lib/types/ClientOptions.java | 2 +- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/http/HttpCore.java b/lib/src/main/java/io/ably/lib/http/HttpCore.java index 8277fe3d9..dc1255bc9 100644 --- a/lib/src/main/java/io/ably/lib/http/HttpCore.java +++ b/lib/src/main/java/io/ably/lib/http/HttpCore.java @@ -209,7 +209,7 @@ T httpExecute(HttpURLConnection conn, String method, Param[] headers, Reques if(!acceptSet) { conn.setRequestProperty(HttpConstants.Headers.ACCEPT, HttpConstants.ContentTypes.JSON); } /* pass required headers */ - conn.setRequestProperty(Defaults.ABLY_VERSION_HEADER, Defaults.ABLY_VERSION); + conn.setRequestProperty(Defaults.ABLY_PROTOCOL_VERSION_HEADER, Defaults.ABLY_PROTOCOL_VERSION); conn.setRequestProperty(Defaults.ABLY_AGENT_HEADER, AgentHeaderCreator.create(options.agents, platformAgentProvider)); /* prepare request body */ diff --git a/lib/src/main/java/io/ably/lib/transport/Defaults.java b/lib/src/main/java/io/ably/lib/transport/Defaults.java index 9d274e572..ba06838da 100644 --- a/lib/src/main/java/io/ably/lib/transport/Defaults.java +++ b/lib/src/main/java/io/ably/lib/transport/Defaults.java @@ -3,28 +3,22 @@ import io.ably.lib.BuildConfig; import io.ably.lib.types.ClientOptions; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.Locale; - public class Defaults { - public static final float ABLY_VERSION_NUMBER = 1.0f; - /** * The level of compatibility with the Ably service that this SDK supports, also referred to as the 'wire protocol version'. * This value is presented as a string, as specified in G4a. */ - public static final String ABLY_VERSION = new DecimalFormat("0.0", new DecimalFormatSymbols(Locale.ENGLISH)).format(ABLY_VERSION_NUMBER); + public static final String ABLY_PROTOCOL_VERSION = "2"; public static final String ABLY_AGENT_VERSION = String.format("%s/%s", "ably-java", BuildConfig.VERSION); - /* params */ - public static final String ABLY_VERSION_PARAM = "v"; - public static final String ABLY_AGENT_PARAM = "agent"; + /* realtime params */ + public static final String ABLY_PROTOCOL_VERSION_PARAM = "v"; + public static final String ABLY_AGENT_PARAM = "agent"; - /* Headers */ - public static final String ABLY_VERSION_HEADER = "X-Ably-Version"; - public static final String ABLY_AGENT_HEADER = "Ably-Agent"; + /* http headers */ + public static final String ABLY_PROTOCOL_VERSION_HEADER = "X-Ably-Version"; + public static final String ABLY_AGENT_HEADER = "Ably-Agent"; /* Hosts */ public static final String[] HOST_FALLBACKS = { "A.ably-realtime.com", "B.ably-realtime.com", "C.ably-realtime.com", "D.ably-realtime.com", "E.ably-realtime.com" }; diff --git a/lib/src/main/java/io/ably/lib/transport/ITransport.java b/lib/src/main/java/io/ably/lib/transport/ITransport.java index 9f077bf95..6e188f3d9 100644 --- a/lib/src/main/java/io/ably/lib/transport/ITransport.java +++ b/lib/src/main/java/io/ably/lib/transport/ITransport.java @@ -58,7 +58,7 @@ public ClientOptions getClientOptions() { public Param[] getConnectParams(Param[] baseParams) { List paramList = new ArrayList(Arrays.asList(baseParams)); - paramList.add(new Param(Defaults.ABLY_VERSION_PARAM, Defaults.ABLY_VERSION)); + paramList.add(new Param(Defaults.ABLY_PROTOCOL_VERSION_PARAM, Defaults.ABLY_PROTOCOL_VERSION)); paramList.add(new Param("format", (options.useBinaryProtocol ? "msgpack" : "json"))); if(!options.echoMessages) paramList.add(new Param("echo", "false")); diff --git a/lib/src/main/java/io/ably/lib/types/ChannelProperties.java b/lib/src/main/java/io/ably/lib/types/ChannelProperties.java index 50a1ae989..482528d18 100644 --- a/lib/src/main/java/io/ably/lib/types/ChannelProperties.java +++ b/lib/src/main/java/io/ably/lib/types/ChannelProperties.java @@ -2,6 +2,9 @@ /** * Describes the properties of the channel state. + *

+ * Spec: CP2 + *

*/ public class ChannelProperties { /** diff --git a/lib/src/main/java/io/ably/lib/types/ClientOptions.java b/lib/src/main/java/io/ably/lib/types/ClientOptions.java index 06a2fae02..7a480b5f5 100644 --- a/lib/src/main/java/io/ably/lib/types/ClientOptions.java +++ b/lib/src/main/java/io/ably/lib/types/ClientOptions.java @@ -147,7 +147,7 @@ public ClientOptions(String key) throws AblyException { * when the connection is recoverable. The callback is then responsible for confirming whether the connection * should be recovered or not. See connection state recovery for further information. *

- * Spec: RTC1c, TO3i + * Spec: RTC1c, TO3i, RTN16i */ public String recover; From 5b8e5b7422b3255e813852441964c2f177d85270 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 1 Dec 2023 17:43:16 +0530 Subject: [PATCH 23/73] Updated test for protocol version --- lib/src/test/java/io/ably/lib/transport/DefaultsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/transport/DefaultsTest.java b/lib/src/test/java/io/ably/lib/transport/DefaultsTest.java index 021387da4..cdeec5dce 100644 --- a/lib/src/test/java/io/ably/lib/transport/DefaultsTest.java +++ b/lib/src/test/java/io/ably/lib/transport/DefaultsTest.java @@ -8,8 +8,8 @@ public class DefaultsTest { @Test - public void versions() { - assertThat(Defaults.ABLY_VERSION, is("1.0")); + public void protocol_version_CSV2() { + assertThat(Defaults.ABLY_PROTOCOL_VERSION, is("2")); } @Test From 232d87b077612dcec49e11f828aff377e6d7ba27 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 1 Dec 2023 21:10:49 +0530 Subject: [PATCH 24/73] Added suspended state as per RTL13a for server initiated detached --- lib/src/main/java/io/ably/lib/realtime/ChannelBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index d38a74df1..70caf1eb9 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -1299,6 +1299,7 @@ void onChannelMessage(ProtocolMessage msg) { ChannelState oldState = state; switch(oldState) { case attached: + case suspended: //RTL13a /* Unexpected detach, reattach when possible */ setDetached((msg.error != null) ? msg.error : REASON_NOT_ATTACHED); Log.v(TAG, String.format(Locale.ROOT, "Server initiated detach for channel %s; attempting reattach", name)); @@ -1320,7 +1321,6 @@ void onChannelMessage(ProtocolMessage msg) { setDetached((msg.error != null) ? msg.error : REASON_NOT_ATTACHED); break; case detached: - case suspended: case failed: default: /* do nothing */ From 2a47f48a0cd5337f3ea7e2fbdeaa7ba04523cbf5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 4 Dec 2023 16:44:31 +0530 Subject: [PATCH 25/73] Added waitForSync unblocking mechanism when channel is attached --- lib/src/main/java/io/ably/lib/realtime/Presence.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 19686f587..9ddc02840 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -913,6 +913,11 @@ private void failQueuedMessages(ErrorInfo reason) { ************************************/ void setAttached(boolean hasPresence, String connectionId) { + /* Interrupt get() call => by unblocking presence.waitForSync()*/ + synchronized (presence) { + presence.notifyAll(); + } + /* Start sync, if hasPresence is not set end sync immediately dropping all the current presence members */ if (hasPresence){ internalPresence.replaceMembersIfNeeded(connectionId); From d003348f05ed1120e8481aaf37aa2219ee3ca4ac Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 5 Dec 2023 17:20:06 +0530 Subject: [PATCH 26/73] refactored presence file, removed unnecessary spec for RTP5c2 --- .../java/io/ably/lib/realtime/Presence.java | 63 ++----------------- 1 file changed, 4 insertions(+), 59 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 9ddc02840..d59725d5b 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -316,7 +316,7 @@ private void implicitAttachOnSubscribe(CompletionListener completionListener) th } /* End sync and emit leave messages for residual members */ - private void endSyncAndEmitLeaves() { + private void endSync() { currentSyncChannelSerial = null; List residualMembers = presence.endSync(); for (PresenceMessage member: residualMembers) { @@ -330,61 +330,6 @@ private void endSyncAndEmitLeaves() { member.timestamp = System.currentTimeMillis(); } broadcastPresence(residualMembers.toArray(new PresenceMessage[residualMembers.size()])); - - /** - * (RTP5c2) If a SYNC is initiated as part of the attach, then once the SYNC is complete, - * all members not present in the PresenceMap but present in the internal PresenceMap must - * be re-entered automatically by the client using the clientId and data attributes from - * each. The members re-entered automatically must be removed from the internal PresenceMap - * ensuring that members present on the channel are constructed from presence events sent - * from Ably since the channel became ATTACHED - */ - if (syncAsResultOfAttach) { - syncAsResultOfAttach = false; - for (PresenceMessage item: internalPresence.values()) { - if (presence.put(item)) { - /* Message is new to presence map, send it */ - final String clientId = item.clientId; - try { - /** - * (RTP17d) [...] publishing a PresenceMessage with an ENTER action using the - * clientId and data attributes from that member [...] - */ - PresenceMessage itemToSend = new PresenceMessage(); - itemToSend.clientId = item.clientId; - itemToSend.data = item.data; - itemToSend.action = PresenceMessage.Action.enter; - updatePresence(itemToSend, new CompletionListener() { - @Override - public void onSuccess() { - } - - @Override - public void onError(ErrorInfo reason) { - /* - * (RTP5c3) If any of the automatic ENTER presence messages published - * in RTP5c2 fail, then an UPDATE event should be emitted on the channel - * with resumed set to true and reason set to an ErrorInfo object with error - * code value 91004 and the error message string containing the message - * received from Ably (if applicable), the code received from Ably - * (if applicable) and the explicit or implicit client_id of the PresenceMessage - */ - String errorString = String.format(Locale.ROOT, "Cannot automatically re-enter %s on channel %s (%s)", - clientId, channel.name, reason.message); - Log.e(TAG, errorString); - channel.emitUpdate(new ErrorInfo(errorString, 91004), true); - } - }); - } catch(AblyException e) { - String errorString = String.format(Locale.ROOT, "Cannot automatically re-enter %s on channel %s (%s)", - clientId, channel.name, e.errorInfo.message); - Log.e(TAG, errorString); - channel.emitUpdate(new ErrorInfo(errorString, 91004), true); - } - } - } - internalPresence.clear(); - } } void setPresence(PresenceMessage[] messages, boolean broadcast, String syncChannelSerial) { @@ -395,7 +340,7 @@ void setPresence(PresenceMessage[] messages, boolean broadcast, String syncChann String serial = colonPos >= 0 ? syncChannelSerial.substring(0, colonPos) : syncChannelSerial; /* Discard incomplete sync if serial has changed */ if (presence.syncInProgress && currentSyncChannelSerial != null && !currentSyncChannelSerial.equals(serial)) - endSyncAndEmitLeaves(); + endSync(); syncCursor = syncChannelSerial.substring(colonPos); if(syncCursor.length() > 1) { presence.startSync(); @@ -435,7 +380,7 @@ void setPresence(PresenceMessage[] messages, boolean broadcast, String syncChann /* if this is the last message in a sequence of sync updates, end the sync */ if(syncChannelSerial == null || syncCursor.length() <= 1) { - endSyncAndEmitLeaves(); + endSync(); } } @@ -929,7 +874,7 @@ void setAttached(boolean hasPresence, String connectionId) { * RTP19a If the PresenceMap has existing members when an ATTACHED message is received without a * HAS_PRESENCE flag, the client library should emit a LEAVE event for each existing member ... */ - endSyncAndEmitLeaves(); + endSync(); } sendQueuedMessages(); } From 0cab20fab5f65da574eabd4f32985a3183e3aca0 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 5 Dec 2023 18:33:06 +0530 Subject: [PATCH 27/73] Refactored/simplified onSync and onPresence implementation for presence --- .../io/ably/lib/realtime/ChannelBase.java | 49 +------ .../java/io/ably/lib/realtime/Presence.java | 124 +++++++++++------- 2 files changed, 82 insertions(+), 91 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 7d0816377..2ce2da236 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -349,26 +349,6 @@ private void sendDetachMessage(CompletionListener listener) throws AblyException } } - public void sync() throws AblyException { - Log.v(TAG, "sync(); channel = " + name); - /* check preconditions */ - switch(state) { - case initialized: - case detaching: - case detached: - throw AblyException.fromErrorInfo(new ErrorInfo("Unable to sync to channel; not attached", 40000)); - default: - } - ConnectionManager connectionManager = ably.connection.connectionManager; - if(!connectionManager.isActive()) - throw AblyException.fromErrorInfo(connectionManager.getStateErrorInfo()); - - /* send sync request */ - ProtocolMessage syncMessage = new ProtocolMessage(Action.sync, this.name); - syncMessage.channelSerial = syncChannelSerial; - connectionManager.send(syncMessage, true, null); - } - /*** * internal * @@ -888,30 +868,6 @@ public void onError(ErrorInfo reason) { }); } - private void onPresence(ProtocolMessage message, String syncChannelSerial) { - Log.v(TAG, "onPresence(); channel = " + name + "; syncChannelSerial = " + syncChannelSerial); - PresenceMessage[] messages = message.presence; - for(int i = 0; i < messages.length; i++) { - PresenceMessage msg = messages[i]; - try { - msg.decode(options); - } catch (MessageDecodeException e) { - Log.e(TAG, String.format(Locale.ROOT, "%s on channel %s", e.errorInfo.message, name)); - } - /* populate fields derived from protocol message */ - if(msg.connectionId == null) msg.connectionId = message.connectionId; - if(msg.timestamp == 0) msg.timestamp = message.timestamp; - if(msg.id == null) msg.id = message.id + ':' + i; - } - presence.setPresence(messages, true, syncChannelSerial); - } - - private void onSync(ProtocolMessage message) { - Log.v(TAG, "onSync(); channel = " + name); - if(message.presence != null) - onPresence(message, (syncChannelSerial = message.channelSerial)); - } - private MessageMulticaster listeners = new MessageMulticaster(); private HashMap eventListeners = new HashMap(); @@ -1337,10 +1293,10 @@ void onChannelMessage(ProtocolMessage msg) { } break; case presence: - onPresence(msg, null); + presence.onPresence(msg); break; case sync: - onSync(msg); + presence.onSync(msg); break; case error: setFailed(msg.error); @@ -1375,7 +1331,6 @@ public void once(ChannelState state, ChannelStateListener listener) { final AblyRealtime ably; final String basePath; ChannelOptions options; - String syncChannelSerial; /** * Optional channel parameters * that configure the behavior of the channel. diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index d59725d5b..b73fb5325 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -8,12 +8,15 @@ import io.ably.lib.types.AsyncPaginatedResult; import io.ably.lib.types.Callback; import io.ably.lib.types.ErrorInfo; +import io.ably.lib.types.MessageDecodeException; import io.ably.lib.types.PaginatedResult; import io.ably.lib.types.Param; import io.ably.lib.types.PresenceMessage; import io.ably.lib.types.PresenceSerializer; import io.ably.lib.types.ProtocolMessage; import io.ably.lib.util.Log; +import io.ably.lib.util.StringUtils; + import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; @@ -329,62 +332,95 @@ private void endSync() { member.id = null; member.timestamp = System.currentTimeMillis(); } - broadcastPresence(residualMembers.toArray(new PresenceMessage[residualMembers.size()])); + broadcastPresence(residualMembers); } - void setPresence(PresenceMessage[] messages, boolean broadcast, String syncChannelSerial) { - Log.v(TAG, "setPresence(); channel = " + channel.name + "; broadcast = " + broadcast + "; syncChannelSerial = " + syncChannelSerial); - String syncCursor = null; - if(syncChannelSerial != null) { - int colonPos = syncChannelSerial.indexOf(':'); - String serial = colonPos >= 0 ? syncChannelSerial.substring(0, colonPos) : syncChannelSerial; - /* Discard incomplete sync if serial has changed */ - if (presence.syncInProgress && currentSyncChannelSerial != null && !currentSyncChannelSerial.equals(serial)) - endSync(); - syncCursor = syncChannelSerial.substring(colonPos); - if(syncCursor.length() > 1) { - presence.startSync(); - currentSyncChannelSerial = serial; + private void updateInnerMessageFields(ProtocolMessage message) { + for(int i = 0; i < message.presence.length; i++) { + PresenceMessage msg = message.presence[i]; + try { + msg.decode(channel.options); + } catch (MessageDecodeException e) { + Log.e(TAG, String.format(Locale.ROOT, "%s on channel %s", e.errorInfo.message, channel.name)); } + /* populate fields derived from protocol message */ + if(msg.connectionId == null) msg.connectionId = message.connectionId; + if(msg.timestamp == 0) msg.timestamp = message.timestamp; + if(msg.id == null) msg.id = message.id + ':' + i; } - for(PresenceMessage update : messages) { - boolean updateInternalPresence = update.connectionId.equals(channel.ably.connection.id); - boolean broadcastThisUpdate = broadcast; - PresenceMessage originalUpdate = update; - - switch(update.action) { - case enter: - case update: - update = (PresenceMessage)update.clone(); - update.action = PresenceMessage.Action.present; - case present: - broadcastThisUpdate &= presence.put(update); - if(updateInternalPresence) - internalPresence.put(update); - break; - case leave: - broadcastThisUpdate &= presence.remove(update); - if(updateInternalPresence) - internalPresence.remove(update); - break; - case absent: + } + + void onSync(ProtocolMessage protocolMessage) { + String syncCursor = null; + String syncChannelSerial = protocolMessage.channelSerial; + // RTP18a + if(!StringUtils.isNullOrEmpty(syncChannelSerial)) { + String[] serials = syncChannelSerial.split(":"); + String syncSequenceId = serials[0]; + syncCursor = serials.length > 1 ? serials[1] : ""; + + /* If a new sequence identifier is sent from Ably, then the client library + * must consider that to be the start of a new sync sequence + * and any previous in-flight sync should be discarded. (part of RTP18)*/ + if (presence.syncInProgress && !StringUtils.isNullOrEmpty(currentSyncChannelSerial) + && !currentSyncChannelSerial.equals(syncSequenceId)) { + endSync(); } - /* - * RTP2g: Any incoming presence message that passes the newness check should be emitted on the - * Presence object, with an event name set to its original action. - */ - if (broadcastThisUpdate) - broadcastPresence(new PresenceMessage[]{originalUpdate}); + presence.startSync(); + + if (!StringUtils.isNullOrEmpty(syncCursor)) + { + currentSyncChannelSerial = syncSequenceId; + } } - /* if this is the last message in a sequence of sync updates, end the sync */ - if(syncChannelSerial == null || syncCursor.length() <= 1) { + onPresence(protocolMessage); + + // RTP18b, RTP18c + if (StringUtils.isNullOrEmpty(syncChannelSerial) || StringUtils.isNullOrEmpty(syncCursor)) + { endSync(); + currentSyncChannelSerial = null; } } - private void broadcastPresence(PresenceMessage[] messages) { + void onPresence(ProtocolMessage protocolMessage) { + updateInnerMessageFields(protocolMessage); + List updatedPresenceMessages = new ArrayList<>(); + for(PresenceMessage presenceMessage : protocolMessage.presence) { + boolean updateInternalPresence = presenceMessage.connectionId.equals(channel.ably.connection.id); + boolean memberUpdated = false; + + switch(presenceMessage.action) { + case enter: + case update: + case present: + PresenceMessage shallowClone = (PresenceMessage)presenceMessage.clone(); + shallowClone.action = PresenceMessage.Action.present; + memberUpdated = presence.put(shallowClone); + if(updateInternalPresence) + internalPresence.put(presenceMessage); + break; + case leave: + memberUpdated = presence.remove(presenceMessage); + if(updateInternalPresence) + internalPresence.remove(presenceMessage); + break; + case absent: + } + if (memberUpdated) { + updatedPresenceMessages.add(presenceMessage); + } + } + /* + * RTP2g: Any incoming presence message that passes the newness check should be emitted on the + * Presence object, with an event name set to its original action. + */ + broadcastPresence(updatedPresenceMessages); + } + + private void broadcastPresence(List messages) { for(PresenceMessage message : messages) { listeners.onPresenceMessage(message); From 6fb868634e5da1e67829b08a10aeffd232d9db70 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 5 Dec 2023 18:48:56 +0530 Subject: [PATCH 28/73] Refactored presence file for handling on Presence --- lib/src/main/java/io/ably/lib/realtime/Presence.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index b73fb5325..9f3d3a50e 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -335,7 +335,7 @@ private void endSync() { broadcastPresence(residualMembers); } - private void updateInnerMessageFields(ProtocolMessage message) { + private void updateInnerPresenceMessageFields(ProtocolMessage message) { for(int i = 0; i < message.presence.length; i++) { PresenceMessage msg = message.presence[i]; try { @@ -386,7 +386,7 @@ void onSync(ProtocolMessage protocolMessage) { } void onPresence(ProtocolMessage protocolMessage) { - updateInnerMessageFields(protocolMessage); + updateInnerPresenceMessageFields(protocolMessage); List updatedPresenceMessages = new ArrayList<>(); for(PresenceMessage presenceMessage : protocolMessage.presence) { boolean updateInternalPresence = presenceMessage.connectionId.equals(channel.ably.connection.id); @@ -396,9 +396,9 @@ void onPresence(ProtocolMessage protocolMessage) { case enter: case update: case present: - PresenceMessage shallowClone = (PresenceMessage)presenceMessage.clone(); - shallowClone.action = PresenceMessage.Action.present; - memberUpdated = presence.put(shallowClone); + PresenceMessage shallowPresenceCopy = (PresenceMessage)presenceMessage.clone(); + shallowPresenceCopy.action = PresenceMessage.Action.present; + memberUpdated = presence.put(shallowPresenceCopy); if(updateInternalPresence) internalPresence.put(presenceMessage); break; From 834738b48ade0a0e1e8e6db6b37d8d949f6e670f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 6 Dec 2023 16:38:48 +0530 Subject: [PATCH 29/73] Refactored presence file, removed unnecessary flag --- lib/src/main/java/io/ably/lib/realtime/Presence.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 9f3d3a50e..513431ccc 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -904,7 +904,6 @@ void setAttached(boolean hasPresence, String connectionId) { internalPresence.replaceMembersIfNeeded(connectionId); } presence.startSync(); - syncAsResultOfAttach = true; if (!hasPresence) { /* * RTP19a If the PresenceMap has existing members when an ATTACHED message is received without a @@ -1259,9 +1258,6 @@ public String memberKey(PresenceMessage item) { /* channel serial if sync is in progress */ private String currentSyncChannelSerial; - /* Sync in progress is a result of attach operation */ - private boolean syncAsResultOfAttach; - /** * Indicates whether the presence set synchronization between Ably and the clients on the channel has been completed. * Set to true when the sync is complete. From 941230ff9231b44a21c37a342a44e022e0398dd1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 7 Dec 2023 12:27:27 +0530 Subject: [PATCH 30/73] Refactored presence.java, added code to enter internal members --- .../io/ably/lib/realtime/ChannelBase.java | 2 +- .../java/io/ably/lib/realtime/Presence.java | 57 ++++++++++--------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 2ce2da236..ae90c6b84 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -395,7 +395,7 @@ private void setAttached(ProtocolMessage message) { else { this.attachResume = true; setState(ChannelState.attached, message.error, resumed); - presence.setAttached(message.hasFlag(Flag.has_presence), this.ably.connection.id); + presence.setAttached(message.hasFlag(Flag.has_presence)); } } diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 513431ccc..925e1438e 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -893,25 +893,45 @@ private void failQueuedMessages(ErrorInfo reason) { * attach / detach ************************************/ - void setAttached(boolean hasPresence, String connectionId) { + void setAttached(boolean hasPresence) { /* Interrupt get() call => by unblocking presence.waitForSync()*/ synchronized (presence) { presence.notifyAll(); } - /* Start sync, if hasPresence is not set end sync immediately dropping all the current presence members */ - if (hasPresence){ - internalPresence.replaceMembersIfNeeded(connectionId); - } presence.startSync(); - if (!hasPresence) { - /* - * RTP19a If the PresenceMap has existing members when an ATTACHED message is received without a - * HAS_PRESENCE flag, the client library should emit a LEAVE event for each existing member ... - */ + if (!hasPresence) { // RTP19a endSync(); } - sendQueuedMessages(); + sendQueuedMessages(); // RTP5b + } + + /** + * Spec: RTP17g + */ + synchronized void enterInternalMembers() { + for (final PresenceMessage item: internalPresence.values()) { + try { + enterClient(item.clientId, item.data, new CompletionListener() { + @Override + public void onSuccess() { + } + + @Override + public void onError(ErrorInfo reason) { + String errorString = String.format(Locale.ROOT, "Cannot automatically re-enter %s on channel %s (%s)", + item.clientId, channel.name, reason.message); + Log.e(TAG, errorString); + channel.emitUpdate(new ErrorInfo(errorString, 91004), true); + } + }); + } catch(AblyException e) { + String errorString = String.format(Locale.ROOT, "Cannot automatically re-enter %s on channel %s (%s)", + item.clientId, channel.name, e.errorInfo.message); + Log.e(TAG, errorString); + channel.emitUpdate(new ErrorInfo(errorString, 91004), true); + } + } } void setDetached(ErrorInfo reason) { @@ -1198,21 +1218,6 @@ synchronized void clear() { residualMembers.clear(); } - /* - Old internal members are stuck with old member ids, we need to replace them with new one - * */ - synchronized void replaceMembersIfNeeded(String connectionId) { - for (Map.Entry entry : members.entrySet()) { - final String key = entry.getKey(); - if (!key.contains(connectionId)) { //connection has changed - replace key - PresenceMessage presenceMessage = internalPresence.members.get(key); - presenceMessage.connectionId = connectionId; - internalPresence.members.put(key, presenceMessage); - } - } - } - - /** * Combines clientId and connectionId to ensure that multiple connected clients with an identical clientId are uniquely identifiable. * A string function that returns the combined clientId and connectionId. From 77e06fa038c86bff9cc70a5d6a86d4a503a4a0d1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 7 Dec 2023 16:03:57 +0530 Subject: [PATCH 31/73] Added explicit call for entering presence members --- lib/src/main/java/io/ably/lib/realtime/ChannelBase.java | 2 +- lib/src/main/java/io/ably/lib/realtime/Presence.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index ae90c6b84..06201b310 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -395,7 +395,7 @@ private void setAttached(ProtocolMessage message) { else { this.attachResume = true; setState(ChannelState.attached, message.error, resumed); - presence.setAttached(message.hasFlag(Flag.has_presence)); + presence.setAttached(message.hasFlag(Flag.has_presence), true); } } diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 925e1438e..93dd67f71 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -893,7 +893,7 @@ private void failQueuedMessages(ErrorInfo reason) { * attach / detach ************************************/ - void setAttached(boolean hasPresence) { + void setAttached(boolean hasPresence, boolean enterInternalPresenceMembers) { /* Interrupt get() call => by unblocking presence.waitForSync()*/ synchronized (presence) { presence.notifyAll(); @@ -904,6 +904,10 @@ void setAttached(boolean hasPresence) { endSync(); } sendQueuedMessages(); // RTP5b + + if (enterInternalPresenceMembers) { + enterInternalMembers(); + } } /** From da26dab29e461ce905ce6bd91796fcc4ad46c627 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 7 Dec 2023 20:48:17 +0530 Subject: [PATCH 32/73] updated setAttached implementation for channelbase --- .../io/ably/lib/realtime/AblyRealtime.java | 8 ++++++- .../io/ably/lib/realtime/ChannelBase.java | 21 +++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 8ac553c33..c9b9a9d4d 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -9,7 +9,13 @@ import io.ably.lib.rest.AblyRest; import io.ably.lib.rest.Auth; import io.ably.lib.transport.ConnectionManager; -import io.ably.lib.types.*; +import io.ably.lib.types.AblyException; +import io.ably.lib.types.ChannelOptions; +import io.ably.lib.types.ClientOptions; +import io.ably.lib.types.ErrorInfo; +import io.ably.lib.types.ProtocolMessage; +import io.ably.lib.types.ReadOnlyMap; +import io.ably.lib.types.RecoveryKeyContext; import io.ably.lib.util.InternalMap; import io.ably.lib.util.Log; import io.ably.lib.util.StringUtils; diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 06201b310..6da52b59d 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -375,27 +375,30 @@ private static void callCompletionListenerError(CompletionListener listener, Err private void setAttached(ProtocolMessage message) { clearAttachTimers(); - boolean resumed = message.hasFlag(Flag.resumed); - Log.v(TAG, "setAttached(); channel = " + name + ", resumed = " + resumed); properties.attachSerial = message.channelSerial; params = message.params; modes = ChannelMode.toSet(message.flags); - if(state == ChannelState.attached) { - Log.v(TAG, String.format(Locale.ROOT, "Server initiated attach for channel %s", name)); - /* emit UPDATE event according to RTL12 */ - emitUpdate(null, resumed); - } else if (state == ChannelState.detaching || state == ChannelState.detached) { //RTL5k + this.attachResume = true; + + if (state == ChannelState.detaching || state == ChannelState.detached) { //RTL5k Log.v(TAG, "setAttached(): channel is in detaching state, as per RTL5k sending detach message!"); try { sendDetachMessage(null); } catch (AblyException e) { Log.e(TAG, e.getMessage(), e); } + return; + } + if(state == ChannelState.attached) { + Log.v(TAG, String.format(Locale.ROOT, "Server initiated attach for channel %s", name)); + if (!message.hasFlag(Flag.resumed)) { // RTL12 + presence.setAttached(message.hasFlag(Flag.has_presence), true); + emitUpdate(message.error, false); + } } else { - this.attachResume = true; - setState(ChannelState.attached, message.error, resumed); presence.setAttached(message.hasFlag(Flag.has_presence), true); + setState(ChannelState.attached, message.error, message.hasFlag(Flag.resumed)); } } From 19fe085e02d0b6ff275fe9d6f3e26c9816094bf8 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 8 Dec 2023 15:12:35 +0530 Subject: [PATCH 33/73] Refactored presence code for channel detached and failed state --- .../java/io/ably/lib/realtime/ChannelBase.java | 6 +++--- .../java/io/ably/lib/realtime/Presence.java | 17 +++++------------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 6da52b59d..dfe499af4 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -405,14 +405,14 @@ private void setAttached(ProtocolMessage message) { private void setDetached(ErrorInfo reason) { clearAttachTimers(); Log.v(TAG, "setDetached(); channel = " + name); - presence.setDetached(reason); + presence.onChannelDetachedOrFailed(reason); setState(ChannelState.detached, reason); } private void setFailed(ErrorInfo reason) { clearAttachTimers(); Log.v(TAG, "setFailed(); channel = " + name); - presence.setDetached(reason); + presence.onChannelDetachedOrFailed(reason); this.attachResume = false; setState(ChannelState.failed, reason); } @@ -653,7 +653,7 @@ public synchronized void setSuspended(ErrorInfo reason, boolean notifyStateChang clearAttachTimers(); if (state == ChannelState.attached || state == ChannelState.attaching) { Log.v(TAG, "setSuspended(); channel = " + name); - presence.setSuspended(reason); + presence.onChannelSuspended(reason); setState(ChannelState.suspended, reason, false, notifyStateChange); } } diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 93dd67f71..3206b39d9 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -938,32 +938,25 @@ public void onError(ErrorInfo reason) { } } - void setDetached(ErrorInfo reason) { + // RTP5a + void onChannelDetachedOrFailed(ErrorInfo reason) { /* Interrupt get() call if needed */ synchronized (presence) { presence.notifyAll(); } - /** - * (RTP5a) If the channel enters the DETACHED or FAILED state then all queued presence - * messages will fail immediately, and the PresenceMap and internal PresenceMap is cleared. - * The latter ensures members are not automatically re-entered if the Channel later becomes attached - */ - failQueuedMessages(reason); presence.clear(); internalPresence.clear(); + failQueuedMessages(reason); } - void setSuspended(ErrorInfo reason) { + // RTP5f, RTP16b + void onChannelSuspended(ErrorInfo reason) { /* Interrupt get() call if needed */ synchronized (presence) { presence.notifyAll(); } - /* - * (RTP5f) If the channel enters the SUSPENDED state then all queued presence messages will fail - * immediately, and the PresenceMap is maintained - */ failQueuedMessages(reason); } From 768762d2bc3ca732712ece38f93d9e0ad606f2d4 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 8 Dec 2023 15:22:49 +0530 Subject: [PATCH 34/73] Refactored endSync and onAttached channel method --- .../main/java/io/ably/lib/realtime/ChannelBase.java | 4 ++-- lib/src/main/java/io/ably/lib/realtime/Presence.java | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index dfe499af4..682ad259f 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -392,12 +392,12 @@ private void setAttached(ProtocolMessage message) { if(state == ChannelState.attached) { Log.v(TAG, String.format(Locale.ROOT, "Server initiated attach for channel %s", name)); if (!message.hasFlag(Flag.resumed)) { // RTL12 - presence.setAttached(message.hasFlag(Flag.has_presence), true); + presence.onAttached(message.hasFlag(Flag.has_presence), true); emitUpdate(message.error, false); } } else { - presence.setAttached(message.hasFlag(Flag.has_presence), true); + presence.onAttached(message.hasFlag(Flag.has_presence), true); setState(ChannelState.attached, message.error, message.hasFlag(Flag.resumed)); } } diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 3206b39d9..281270006 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -320,14 +320,8 @@ private void implicitAttachOnSubscribe(CompletionListener completionListener) th /* End sync and emit leave messages for residual members */ private void endSync() { - currentSyncChannelSerial = null; List residualMembers = presence.endSync(); - for (PresenceMessage member: residualMembers) { - /* - * RTP19: ... The PresenceMessage published should contain the original attributes of the presence - * member with the action set to LEAVE, PresenceMessage#id set to null, and the timestamp set - * to the current time ... - */ + for (PresenceMessage member: residualMembers) { // RTP19 member.action = PresenceMessage.Action.leave; member.id = null; member.timestamp = System.currentTimeMillis(); @@ -893,7 +887,7 @@ private void failQueuedMessages(ErrorInfo reason) { * attach / detach ************************************/ - void setAttached(boolean hasPresence, boolean enterInternalPresenceMembers) { + void onAttached(boolean hasPresence, boolean enterInternalPresenceMembers) { /* Interrupt get() call => by unblocking presence.waitForSync()*/ synchronized (presence) { presence.notifyAll(); From da67abcb01870eb87b7fc0647f0e886d6b0d71ae Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 8 Dec 2023 15:43:45 +0530 Subject: [PATCH 35/73] Added a method for reattaching channels in AblyRealtime class --- lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java | 9 +++++++++ lib/src/main/java/io/ably/lib/realtime/ChannelBase.java | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index c9b9a9d4d..2f139a3c5 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -295,6 +295,15 @@ protected void setChannelSerialsFromRecoverOption(Map serials) { } } + protected void reattachChannels() { + for (Channel channel : this.channels.values()) { + if (channel.state == ChannelState.attaching || channel.state == ChannelState.attached || channel.state == ChannelState.suspended) { + Log.d(TAG, "reAttach(); channel = " + channel.name); + channel.attach(true, null); + } + } + } + protected Map getChannelSerials() { Map channelSerials = new HashMap<>(); for (Channel channel : this.channels.values()) { diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 682ad259f..098fc0170 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -1295,12 +1295,12 @@ void onChannelMessage(ProtocolMessage msg) { } } break; - case presence: - presence.onPresence(msg); - break; case sync: presence.onSync(msg); break; + case presence: + presence.onPresence(msg); + break; case error: setFailed(msg.error); break; From 68a9f0afe08af157c38698b8540833fa3497889b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 8 Dec 2023 16:07:12 +0530 Subject: [PATCH 36/73] Refactored attach channels with right spec --- .../io/ably/lib/realtime/AblyRealtime.java | 19 ++++++++++--------- .../java/io/ably/lib/realtime/Connection.java | 3 +++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 2f139a3c5..a0758c7da 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -284,6 +284,16 @@ private void clear() { } } + // RTN19b + protected void reattachChannels() { + for (Channel channel : this.channels.values()) { + if (channel.state == ChannelState.attaching || channel.state == ChannelState.attached || channel.state == ChannelState.suspended) { + Log.d(TAG, "reAttach(); channel = " + channel.name); + channel.attach(true, null); + } + } + } + protected void setChannelSerialsFromRecoverOption(Map serials) { for (Map.Entry entry : serials.entrySet()) { String channelName = entry.getKey(); @@ -295,15 +305,6 @@ protected void setChannelSerialsFromRecoverOption(Map serials) { } } - protected void reattachChannels() { - for (Channel channel : this.channels.values()) { - if (channel.state == ChannelState.attaching || channel.state == ChannelState.attached || channel.state == ChannelState.suspended) { - Log.d(TAG, "reAttach(); channel = " + channel.name); - channel.attach(true, null); - } - } - } - protected Map getChannelSerials() { Map channelSerials = new HashMap<>(); for (Channel channel : this.channels.values()) { diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index 443aef04f..dd5b8b0a6 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -130,6 +130,9 @@ public void close() { public void onConnectionStateChange(ConnectionStateChange stateChange) { state = stateChange.current; reason = stateChange.reason; + if (state == ConnectionState.connected) { // RTN19b + ably.reattachChannels(); + } emit(state, stateChange); } From 249b40e9e5b46c5e477b6cf272dbd5090dc87621 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 11 Dec 2023 15:53:33 +0530 Subject: [PATCH 37/73] Removed unnecessary explicit code to reattach channels --- .../main/java/io/ably/lib/realtime/AblyRealtime.java | 10 ---------- .../main/java/io/ably/lib/realtime/ChannelBase.java | 8 +------- .../main/java/io/ably/lib/realtime/ChannelState.java | 1 + lib/src/main/java/io/ably/lib/realtime/Connection.java | 3 --- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index a0758c7da..c9b9a9d4d 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -284,16 +284,6 @@ private void clear() { } } - // RTN19b - protected void reattachChannels() { - for (Channel channel : this.channels.values()) { - if (channel.state == ChannelState.attaching || channel.state == ChannelState.attached || channel.state == ChannelState.suspended) { - Log.d(TAG, "reAttach(); channel = " + channel.name); - channel.attach(true, null); - } - } - } - protected void setChannelSerialsFromRecoverOption(Map serials) { for (Map.Entry entry : serials.entrySet()) { String channelName = entry.getKey(); diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 098fc0170..1a8b6134b 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -605,16 +605,10 @@ public void run() { } /* State changes provoked by ConnectionManager state changes. */ - public void setConnected(boolean reattachOnResumeFailure) { if (reattachOnResumeFailure && state.isReattachable()){ attach(true,null); - } else if (state == ChannelState.suspended) { - /* (RTL3d) If the connection state enters the CONNECTED state, then - * a SUSPENDED channel will initiate an attach operation. If the - * attach operation for the channel times out and the channel - * returns to the SUSPENDED state (see #RTL4f) - */ + } else if (state == ChannelState.suspended) { // RTL3d try { attachWithTimeout(null); } catch (AblyException e) { diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelState.java b/lib/src/main/java/io/ably/lib/realtime/ChannelState.java index bb7b7b58c..ccebb0116 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelState.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelState.java @@ -48,6 +48,7 @@ public ChannelEvent getChannelEvent() { return event; } + // RTN19b public boolean isReattachable() { return this == ChannelState.attaching || this == ChannelState.attached || this == ChannelState.suspended; } diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index dd5b8b0a6..443aef04f 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -130,9 +130,6 @@ public void close() { public void onConnectionStateChange(ConnectionStateChange stateChange) { state = stateChange.current; reason = stateChange.reason; - if (state == ConnectionState.connected) { // RTN19b - ably.reattachChannels(); - } emit(state, stateChange); } From e69884c735a7f1f90a80f82139a3fc0a03e208ab Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 11 Dec 2023 17:26:50 +0530 Subject: [PATCH 38/73] Refactored code to attach channels on reconnection --- .../io/ably/lib/realtime/AblyRealtime.java | 2 +- .../io/ably/lib/realtime/ChannelBase.java | 10 +---- .../io/ably/lib/realtime/ChannelState.java | 2 +- .../ably/lib/transport/ConnectionManager.java | 42 ++++--------------- 4 files changed, 12 insertions(+), 44 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index c9b9a9d4d..3d7b08d79 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -255,7 +255,7 @@ public void suspendAll(ErrorInfo error, boolean notifyStateChange) { * @param queuedMessages Queued messages transferred from ConnectionManager */ @Override - public void transferToChannels(List queuedMessages) { + public void transferToChannelQueue(List queuedMessages) { final Map> channelQueueMap = new HashMap<>(); for (ConnectionManager.QueuedMessage queuedMessage : queuedMessages) { final String channelName = queuedMessage.msg.channel; diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 1a8b6134b..681be4ff4 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -605,15 +605,9 @@ public void run() { } /* State changes provoked by ConnectionManager state changes. */ - public void setConnected(boolean reattachOnResumeFailure) { - if (reattachOnResumeFailure && state.isReattachable()){ + public void setConnected() { + if (state.isReattachable()){ attach(true,null); - } else if (state == ChannelState.suspended) { // RTL3d - try { - attachWithTimeout(null); - } catch (AblyException e) { - Log.e(TAG, "setConnected(): Unable to initiate attach; channel = " + name, e); - } } } diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelState.java b/lib/src/main/java/io/ably/lib/realtime/ChannelState.java index ccebb0116..308f2956c 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelState.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelState.java @@ -48,7 +48,7 @@ public ChannelEvent getChannelEvent() { return event; } - // RTN19b + // RTN15c6, RTN15c7, RTL3d public boolean isReattachable() { return this == ChannelState.attaching || this == ChannelState.attached || this == ChannelState.suspended; } diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index 905e8728f..f6384d8a5 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -34,7 +34,6 @@ import io.ably.lib.util.Log; import io.ably.lib.util.PlatformAgentProvider; import io.ably.lib.util.ReconnectionStrategy; -import io.ably.lib.util.StringUtils; public class ConnectionManager implements ConnectListener { final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); @@ -81,7 +80,7 @@ public interface Channels { void suspendAll(ErrorInfo error, boolean notifyStateChange); Iterable values(); - void transferToChannels(List queuedMessages); + void transferToChannelQueue(List queuedMessages); } /*********************************** @@ -95,7 +94,6 @@ public static class StateIndication { final ErrorInfo reason; final String fallback; final String currentHost; - final boolean reattachOnResumeFailure; StateIndication(ConnectionState state) { this(state, null); @@ -110,16 +108,6 @@ public StateIndication(ConnectionState state, ErrorInfo reason) { this.reason = reason; this.fallback = fallback; this.currentHost = currentHost; - this.reattachOnResumeFailure = false; - } - - StateIndication(ConnectionState state, ErrorInfo reason, String fallback, String currentHost, - boolean reattachOnResumeFailure) { - this.state = state; - this.reason = reason; - this.fallback = fallback; - this.currentHost = currentHost; - this.reattachOnResumeFailure = reattachOnResumeFailure; } } @@ -264,7 +252,7 @@ StateIndication validateTransition(StateIndication target) { @Override void enactForChannel(StateIndication stateIndication, ConnectionStateChange change, Channel channel) { - channel.setConnected(stateIndication.reattachOnResumeFailure); + channel.setConnected(); } @Override @@ -1200,42 +1188,29 @@ private void onChannelMessage(ProtocolMessage message) { private synchronized void onConnected(ProtocolMessage message) { final ErrorInfo error = message.error; - boolean reattachOnResumeFailure = false; // this will indicate that channel must reattach when connected - // event is received - boolean isConnectionResumeOrRecoverAttempt = !StringUtils.isNullOrEmpty(connection.key) || - !StringUtils.isNullOrEmpty(ably.options.recover); ably.options.recover = null; // RTN16k, explicitly setting null, so it won't be used for subsequent connection requests - connection.reason = error; + if (connection.id != null) { // there was a previous connection, so this is a resume and RTN15c applies Log.d(TAG, "There was a connection resume"); - if(message.connectionId.equals(connection.id)) { - // resume succeeded + if(message.connectionId.equals(connection.id)) { // RTN15c6 - resume success if(message.error == null) { - // RTN15c1: no action required wrt channel state Log.d(TAG, "connection has reconnected and resumed successfully"); } else { - // RTN15c2: no action required wrt channel state Log.d(TAG, "connection resume success with non-fatal error: " + error.message); } - // Add pending messages to the front of queued messages to be sent later addPendingMessagesToQueuedMessages(false); - } else { - // RTN15c3: resume failed - if (error != null){ + } else { // RTN15c7, RTN16d - resume failure + if (error != null) { Log.d(TAG, "connection resume failed with error: " + error.message); }else { // This shouldn't happen but, putting it here for safety Log.d(TAG, "connection resume failed without error" ); } - //we are going to add pending messages and update pending queue state addPendingMessagesToQueuedMessages(true); - - //We are going to transfer presence messages as they need to be sent when the channel is attached final List queuedPresenceMessages = removeAndGetQueuedPresenceMessages(); - channels.transferToChannels(queuedPresenceMessages); - reattachOnResumeFailure = true; + channels.transferToChannelQueue(queuedPresenceMessages); } } @@ -1259,8 +1234,7 @@ private synchronized void onConnected(ProtocolMessage message) { connection.recoveryKey = connection.createRecoveryKey(); /* indicated connected currentState */ - final StateIndication stateIndication = new StateIndication(ConnectionState.connected, error, null, null, - reattachOnResumeFailure); + final StateIndication stateIndication = new StateIndication(ConnectionState.connected, error, null, null); requestState(stateIndication); } From e4ebfb2ae4febeefb7587419b4bb3730a9b24804 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 11 Dec 2023 17:32:17 +0530 Subject: [PATCH 39/73] Simplified channel iteration loop --- lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 3d7b08d79..d7ef25d53 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -243,9 +243,8 @@ public void onMessage(ProtocolMessage msg) { @Override public void suspendAll(ErrorInfo error, boolean notifyStateChange) { - for(Iterator> it = map.entrySet().iterator(); it.hasNext(); ) { - Map.Entry entry = it.next(); - entry.getValue().setSuspended(error, notifyStateChange); + for (Channel channel : map.values()) { + channel.setSuspended(error, notifyStateChange); } } @@ -265,11 +264,9 @@ public void transferToChannelQueue(List queuedM channelQueueMap.get(channelName).add(queuedMessage); } - for (Map.Entry channelEntry : map.entrySet()) { - Channel channel = channelEntry.getValue(); + for (Channel channel : map.values()) { if (channel.state.isReattachable()) { Log.d(TAG, "reAttach(); channel = " + channel.name); - if (channelQueueMap.containsKey(channel.name)){ channel.transferQueuedPresenceMessages(channelQueueMap.get(channel.name)); }else { From 11d1994cb345988dc0aaa517b530262a40e3a6a2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 11 Dec 2023 17:44:24 +0530 Subject: [PATCH 40/73] refactored connection manager for connected message --- .../ably/lib/transport/ConnectionManager.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index f6384d8a5..bc98e171d 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -1187,10 +1187,8 @@ private void onChannelMessage(ProtocolMessage message) { } private synchronized void onConnected(ProtocolMessage message) { - final ErrorInfo error = message.error; - ably.options.recover = null; // RTN16k, explicitly setting null, so it won't be used for subsequent connection requests - connection.reason = error; + connection.reason = message.error; if (connection.id != null) { // there was a previous connection, so this is a resume and RTN15c applies Log.d(TAG, "There was a connection resume"); @@ -1198,19 +1196,18 @@ private synchronized void onConnected(ProtocolMessage message) { if(message.error == null) { Log.d(TAG, "connection has reconnected and resumed successfully"); } else { - Log.d(TAG, "connection resume success with non-fatal error: " + error.message); + Log.d(TAG, "connection resume success with non-fatal error: " + message.error.message); } addPendingMessagesToQueuedMessages(false); } else { // RTN15c7, RTN16d - resume failure - if (error != null) { - Log.d(TAG, "connection resume failed with error: " + error.message); + if (message.error != null) { + Log.d(TAG, "connection resume failed with error: " + message.error.message); }else { // This shouldn't happen but, putting it here for safety Log.d(TAG, "connection resume failed without error" ); } addPendingMessagesToQueuedMessages(true); - final List queuedPresenceMessages = removeAndGetQueuedPresenceMessages(); - channels.transferToChannelQueue(queuedPresenceMessages); + channels.transferToChannelQueue(extractConnectionQueuePresenceMessages()); } } @@ -1234,7 +1231,7 @@ private synchronized void onConnected(ProtocolMessage message) { connection.recoveryKey = connection.createRecoveryKey(); /* indicated connected currentState */ - final StateIndication stateIndication = new StateIndication(ConnectionState.connected, error, null, null); + final StateIndication stateIndication = new StateIndication(ConnectionState.connected, message.error, null, null); requestState(stateIndication); } @@ -1243,7 +1240,7 @@ private synchronized void onConnected(ProtocolMessage message) { list and returns them. We can't yet use Java 8's stream and predicates for this purpose as we support below Android v24. * */ - private synchronized List removeAndGetQueuedPresenceMessages() { + private synchronized List extractConnectionQueuePresenceMessages() { final Iterator queuedIterator = queuedMessages.iterator(); final List queuedPresenceMessages = new ArrayList<>(); while (queuedIterator.hasNext()){ From 943f75792feccb87066da24ef31e2ed400bdd5ee Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 11 Dec 2023 23:31:20 +0530 Subject: [PATCH 41/73] Updated protocol version for ably-java --- lib/src/main/java/io/ably/lib/transport/Defaults.java | 6 +++++- .../test/java/io/ably/lib/test/rest/HttpHeaderTest.java | 7 ++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/transport/Defaults.java b/lib/src/main/java/io/ably/lib/transport/Defaults.java index ba06838da..6cdc32d27 100644 --- a/lib/src/main/java/io/ably/lib/transport/Defaults.java +++ b/lib/src/main/java/io/ably/lib/transport/Defaults.java @@ -5,8 +5,12 @@ public class Defaults { /** - * The level of compatibility with the Ably service that this SDK supports, also referred to as the 'wire protocol version'. + * The level of compatibility with the Ably service that this SDK supports. + * Also referred to as the 'wire protocol version'. * This value is presented as a string, as specified in G4a. + *

+ * spec: G4 + *

*/ public static final String ABLY_PROTOCOL_VERSION = "2"; diff --git a/lib/src/test/java/io/ably/lib/test/rest/HttpHeaderTest.java b/lib/src/test/java/io/ably/lib/test/rest/HttpHeaderTest.java index d83d04a13..eab5cc724 100644 --- a/lib/src/test/java/io/ably/lib/test/rest/HttpHeaderTest.java +++ b/lib/src/test/java/io/ably/lib/test/rest/HttpHeaderTest.java @@ -48,13 +48,10 @@ public static void tearDown() { /** * The header Ably-Agent: [lib]/[version] * should be included in all REST requests to the Ably endpoint - * see {@link io.ably.lib.http.HttpUtils#ABLY_AGENT_VERSION} + * see {@link io.ably.lib.transport.Defaults#ABLY_AGENT_PARAM} *

* Spec: RSC7d, G4 *

- * - * Spec: RSC7a: Must have the header X-Ably-Version: 1.0 (or whatever the - * spec version is). */ @Test public void header_lib_channel_publish() { @@ -84,7 +81,7 @@ public void header_lib_channel_publish() { * from those values. */ Assert.assertNotNull("Expected headers", headers); - Assert.assertEquals(headers.get("x-ably-version"), "1.0"); + Assert.assertEquals(headers.get("x-ably-version"), "2"); Assert.assertEquals(headers.get("ably-agent"), expectedAblyAgentHeader); } catch (AblyException e) { e.printStackTrace(); From 4a72ad657a16cf384058acde8ba03bc078127958 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 11 Dec 2023 23:46:31 +0530 Subject: [PATCH 42/73] Updated connection id and key to be cleared as per connection states --- lib/src/main/java/io/ably/lib/realtime/Connection.java | 10 ++++++---- .../java/io/ably/lib/transport/ConnectionManager.java | 7 +++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index 443aef04f..c6202990c 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -49,17 +49,19 @@ public class Connection extends EventEmitterconnection state recover options * for more information. *

- * Spec: RTN16b, RTN16c + * Spec: RTN16m * @deprecated use createRecoveryKey method instead. */ @Deprecated public String recoveryKey; /** + * createRecoveryKey is a method that returns a json string which incorporates the @connectionKey@, the + * current @msgSerial@, and a collection of pairs of channel @name@ and current @channelSerial@ for every + * currently attached channel. + *

* Spec: RTN16g - * - * @return a json string which incorporates the @connectionKey@, the current @msgSerial@, - * and a collection of pairs of channel @name@ and current @channelSerial@ for every currently attached channel. + *

*/ public String createRecoveryKey() { if (key == null || key.isEmpty() || this.state == ConnectionState.closing || diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index bc98e171d..34ece7345 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -834,6 +834,13 @@ private synchronized ConnectionStateChange setState(ITransport transport, StateI ReconnectionStrategy.getRetryTime(ably.options.disconnectedRetryTimeout, ++disconnectedRetryAttempt); } + // RTN8c, RTN9c + if (stateIndication.state == ConnectionState.closing || stateIndication.state == ConnectionState.closed + || stateIndication.state == ConnectionState.suspended || stateIndication.state == ConnectionState.failed) { + connection.id = null; + connection.key = null; + } + /* update currentState */ ConnectionState newConnectionState = validatedStateIndication.state; State newState = states.get(newConnectionState); From b704dfba37283ef4e4475b6bca58077df4546c28 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 12 Dec 2023 15:56:09 +0530 Subject: [PATCH 43/73] Annotated implementation with no-connection-serial spec --- lib/src/main/java/io/ably/lib/realtime/ChannelBase.java | 4 ++-- lib/src/main/java/io/ably/lib/realtime/Presence.java | 2 +- .../main/java/io/ably/lib/transport/ConnectionManager.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index fa321cf1d..818027a09 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -256,7 +256,7 @@ private void attachImpl(final boolean forceReattach, final CompletionListener li if(this.decodeFailureRecoveryInProgress) { Log.v(TAG, "attach(); message decode recovery in progress."); } - attachMessage.channelSerial = properties.channelSerial; + attachMessage.channelSerial = properties.channelSerial; // RTL4c1 try { if (listener != null) { on(new ChannelStateCompletionListener(listener, ChannelState.attached, ChannelState.failed)); @@ -612,7 +612,7 @@ public void run() { /* State changes provoked by ConnectionManager state changes. */ public void setConnected() { if (state.isReattachable()){ - attach(true,null); + attach(true,null); // RTN15c6, RTN15c7 } } diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 281270006..3de130109 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -899,7 +899,7 @@ void onAttached(boolean hasPresence, boolean enterInternalPresenceMembers) { } sendQueuedMessages(); // RTP5b - if (enterInternalPresenceMembers) { + if (enterInternalPresenceMembers) { // RTP17f enterInternalMembers(); } } diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index 34ece7345..62d90d85d 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -1272,7 +1272,7 @@ private void addPendingMessagesToQueuedMessages(boolean resetMessageSerial) { queuedMessages.addAll(0, pendingMessages.queue); if (resetMessageSerial){ // failed resume, so all new published messages start with msgSerial = 0 - msgSerial = 0; //msgSerial will increase in sendImpl when messages are sent + msgSerial = 0; //msgSerial will increase in sendImpl when messages are sent, RTN15c7 pendingMessages.resetStartSerial(0); } else if(!pendingMessages.queue.isEmpty()) { // pendingMessages needs to expect next msgSerial to be the earliest previously unacknowledged message msgSerial = pendingMessages.queue.get(0).msg.msgSerial; From 088f96b1eef8e1e9c8ddadaef95040d1e1ca93f1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 19 Dec 2023 23:45:44 +0530 Subject: [PATCH 44/73] Refactored code to transfer queued messages --- lib/src/main/java/io/ably/lib/http/HttpCore.java | 2 +- lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java | 6 +----- lib/src/main/java/io/ably/lib/realtime/ChannelState.java | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/http/HttpCore.java b/lib/src/main/java/io/ably/lib/http/HttpCore.java index dc1255bc9..6410849ca 100644 --- a/lib/src/main/java/io/ably/lib/http/HttpCore.java +++ b/lib/src/main/java/io/ably/lib/http/HttpCore.java @@ -209,7 +209,7 @@ T httpExecute(HttpURLConnection conn, String method, Param[] headers, Reques if(!acceptSet) { conn.setRequestProperty(HttpConstants.Headers.ACCEPT, HttpConstants.ContentTypes.JSON); } /* pass required headers */ - conn.setRequestProperty(Defaults.ABLY_PROTOCOL_VERSION_HEADER, Defaults.ABLY_PROTOCOL_VERSION); + conn.setRequestProperty(Defaults.ABLY_PROTOCOL_VERSION_HEADER, Defaults.ABLY_PROTOCOL_VERSION); // RSC7a conn.setRequestProperty(Defaults.ABLY_AGENT_HEADER, AgentHeaderCreator.create(options.agents, platformAgentProvider)); /* prepare request body */ diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index d7ef25d53..a00745d8c 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -267,11 +267,7 @@ public void transferToChannelQueue(List queuedM for (Channel channel : map.values()) { if (channel.state.isReattachable()) { Log.d(TAG, "reAttach(); channel = " + channel.name); - if (channelQueueMap.containsKey(channel.name)){ - channel.transferQueuedPresenceMessages(channelQueueMap.get(channel.name)); - }else { - channel.transferQueuedPresenceMessages(null); - } + channel.transferQueuedPresenceMessages(channelQueueMap.getOrDefault(channel.name, null)); } } } diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelState.java b/lib/src/main/java/io/ably/lib/realtime/ChannelState.java index 308f2956c..202659e12 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelState.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelState.java @@ -48,7 +48,7 @@ public ChannelEvent getChannelEvent() { return event; } - // RTN15c6, RTN15c7, RTL3d + // RTN15c6, RTN15c7, RTL3d, RTN15g3 public boolean isReattachable() { return this == ChannelState.attaching || this == ChannelState.attached || this == ChannelState.suspended; } From dcc93ac84a8a894cec5be39e1645755831b0d7cd Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 20 Dec 2023 16:27:33 +0530 Subject: [PATCH 45/73] Annotated missing spec implementation for the presence code --- lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java | 2 +- lib/src/main/java/io/ably/lib/realtime/Connection.java | 5 ++--- lib/src/main/java/io/ably/lib/realtime/Presence.java | 2 +- .../main/java/io/ably/lib/transport/ConnectionManager.java | 6 +++--- lib/src/main/java/io/ably/lib/transport/ITransport.java | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index a00745d8c..585f5bb31 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -76,7 +76,7 @@ public void onConnectionStateChanged(ConnectionStateListener.ConnectionStateChan if (!StringUtils.isNullOrEmpty(options.recover)) { RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(options.recover); if (recoveryKeyContext != null) { - setChannelSerialsFromRecoverOption(recoveryKeyContext.getChannelSerials()); + setChannelSerialsFromRecoverOption(recoveryKeyContext.getChannelSerials()); // RTN16j connection.connectionManager.msgSerial = recoveryKeyContext.getMsgSerial(); //RTN16f } } diff --git a/lib/src/main/java/io/ably/lib/realtime/Connection.java b/lib/src/main/java/io/ably/lib/realtime/Connection.java index c6202990c..c1ca65c70 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Connection.java +++ b/lib/src/main/java/io/ably/lib/realtime/Connection.java @@ -60,7 +60,7 @@ public class Connection extends EventEmitter - * Spec: RTN16g + * Spec: RTN16g, RTN16c *

*/ public String createRecoveryKey() { @@ -69,8 +69,7 @@ public String createRecoveryKey() { this.state == ConnectionState.failed || this.state == ConnectionState.suspended ) { - //RTN16h - return null; + return null; // RTN16g2 } return new RecoveryKeyContext(key, connectionManager.msgSerial, ably.getChannelSerials()).encode(); diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index 3de130109..b1c1dcb7d 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -1238,7 +1238,7 @@ public String memberKey(PresenceMessage item) { } private final PresenceMap presence = new PresenceMap(); - private final PresenceMap internalPresence = new InternalPresenceMap(); + private final PresenceMap internalPresence = new InternalPresenceMap(); // RTP17 /************************************ * general diff --git a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java index 62d90d85d..dc6c753eb 100644 --- a/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/lib/transport/ConnectionManager.java @@ -1206,10 +1206,10 @@ private synchronized void onConnected(ProtocolMessage message) { Log.d(TAG, "connection resume success with non-fatal error: " + message.error.message); } addPendingMessagesToQueuedMessages(false); - } else { // RTN15c7, RTN16d - resume failure + } else { // RTN15c7, RTN16d - resume failure if (message.error != null) { Log.d(TAG, "connection resume failed with error: " + message.error.message); - }else { // This shouldn't happen but, putting it here for safety + } else { // This shouldn't happen but, putting it here for safety Log.d(TAG, "connection resume failed without error" ); } @@ -1262,7 +1262,7 @@ private synchronized List extractConnectionQueuePresenceMessages( /** * Add all pending queued messages to the front of QueuedMessages for them to be sent later - * Spec: RTN19a + * Spec: RTN19a, RTN19a1, RTN19a2 * @param resetMessageSerial whether to reset message serial, this will determine whether to reset message serials * on pending queue, for example when a connection resume failed */ diff --git a/lib/src/main/java/io/ably/lib/transport/ITransport.java b/lib/src/main/java/io/ably/lib/transport/ITransport.java index 6e188f3d9..8d53d7ef0 100644 --- a/lib/src/main/java/io/ably/lib/transport/ITransport.java +++ b/lib/src/main/java/io/ably/lib/transport/ITransport.java @@ -64,7 +64,7 @@ public Param[] getConnectParams(Param[] baseParams) { paramList.add(new Param("echo", "false")); if(!StringUtils.isNullOrEmpty(connectionKey)) { mode = Mode.resume; - paramList.add(new Param("resume", connectionKey)); + paramList.add(new Param("resume", connectionKey)); // RTN15b1 } else if(!StringUtils.isNullOrEmpty(options.recover)) { // RTN16k mode = Mode.recover; RecoveryKeyContext recoveryKeyContext = RecoveryKeyContext.decode(options.recover); From 2f252eb1715689bbd487f25ae7a0d0dc2f4449c9 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 20 Dec 2023 16:43:41 +0530 Subject: [PATCH 46/73] Fixed checkstyle import issues for ITransport --- lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java | 1 - lib/src/main/java/io/ably/lib/transport/ITransport.java | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 585f5bb31..74129ff9d 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; diff --git a/lib/src/main/java/io/ably/lib/transport/ITransport.java b/lib/src/main/java/io/ably/lib/transport/ITransport.java index 8d53d7ef0..cdcbf92b5 100644 --- a/lib/src/main/java/io/ably/lib/transport/ITransport.java +++ b/lib/src/main/java/io/ably/lib/transport/ITransport.java @@ -1,6 +1,11 @@ package io.ably.lib.transport; -import io.ably.lib.types.*; +import io.ably.lib.types.AblyException; +import io.ably.lib.types.ClientOptions; +import io.ably.lib.types.ErrorInfo; +import io.ably.lib.types.Param; +import io.ably.lib.types.ProtocolMessage; +import io.ably.lib.types.RecoveryKeyContext; import io.ably.lib.util.AgentHeaderCreator; import io.ably.lib.util.Log; import io.ably.lib.util.PlatformAgentProvider; From 32e06878cdff4488a84d7849fb6244adb2fe23b9 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 20 Dec 2023 18:19:48 +0530 Subject: [PATCH 47/73] Fixed failing tests for recovery key --- .../ably/lib/test/realtime/RealtimeConnectFailTest.java | 9 +++++---- .../ably/lib/test/realtime/RealtimeHttpHeaderTest.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java index 55f95d814..eff2e652d 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java @@ -342,15 +342,16 @@ public void connect_unknown_recover_fail() { AblyRealtime ably = null; try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - String recoverConnectionId = "0123456789abcdef-99"; - opts.recover = recoverConnectionId + ":0"; + String recoveryKey = + "{\"connectionKey\":\"0123456789abcdef-99\",\"msgSerial\":5,\"channelSerials\":{\"channel1\":\"98\",\"channel2\":\"32\",\"channel3\":\"09\"}}"; + opts.recover = recoveryKey; ably = new AblyRealtime(opts); ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); ErrorInfo connectedError = connectionWaiter.waitFor(ConnectionState.connected); assertEquals("Verify connected state is reached", ConnectionState.connected, ably.connection.state); assertNotNull("Verify error is returned", connectedError); - assertEquals("Verify correct error code is given", 80008, connectedError.code); - assertFalse("Verify new connection id is assigned", recoverConnectionId.equals(ably.connection.key)); + assertEquals("Verify correct error code is given", 80018, connectedError.code); + assertFalse("Verify new connection id is assigned", "0123456789abcdef-99".equals(ably.connection.key)); } catch (AblyException e) { e.printStackTrace(); fail("init0: Unexpected exception instantiating library"); diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeHttpHeaderTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeHttpHeaderTest.java index 07e945629..ac90c7ac3 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeHttpHeaderTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeHttpHeaderTest.java @@ -81,7 +81,7 @@ public void realtime_websocket_param_test() { * Defaults.ABLY_VERSION_PARAM, as ultimately the request param has been derived from those values. */ assertEquals("Verify correct version", requestParameters.get("v"), - Collections.singletonList("1.0")); + Collections.singletonList("2")); /* Spec RSC7d3 * This test should not directly validate version against Defaults.ABLY_AGENT_VERSION, nor From 6ea5c2b6ed679bba2c63a9f03436d296b9e3761c Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 8 Jan 2024 18:36:31 +0530 Subject: [PATCH 48/73] Refactored test file for no connection serial --- .../lib/test/realtime/RealtimeResumeTest.java | 112 ++++++++---------- 1 file changed, 47 insertions(+), 65 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java index 356dcf210..2895dc841 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java @@ -86,7 +86,7 @@ public void resume_none() { /* wait */ System.out.println("Got reconnection; waiting 2s"); - try { Thread.sleep(2000L); } catch(InterruptedException e) {} + try { Thread.sleep(2000L); } catch(InterruptedException ignored) {} /* Check the channel is still attached. */ assertEquals("Verify channel still attached", channel.state, ChannelState.attached); @@ -140,12 +140,12 @@ public void resume_simple() { CompletionSet msgComplete1 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { channelTx.publish("test_event", "Test message (resume_simple) " + i, msgComplete1.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ ErrorInfo[] errors = msgComplete1.waitFor(); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* wait for the subscription callback to be called */ messageWaiter.waitFor(messageCount); @@ -159,7 +159,7 @@ public void resume_simple() { ablyRx.connection.connectionManager.requestState(ConnectionState.disconnected); /* wait */ - try { Thread.sleep(2000L); } catch(InterruptedException e) {} + try { Thread.sleep(2000L); } catch(InterruptedException ignored) {} /* reconnect the rx connection */ ablyRx.connection.connect(); @@ -168,12 +168,12 @@ public void resume_simple() { CompletionSet msgComplete2 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { channelTx.publish("test_event", "Test message (resume_simple) " + i, msgComplete2.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ errors = msgComplete2.waitFor(); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* wait for the subscription callback to be called */ messageWaiter.waitFor(messageCount); @@ -231,12 +231,12 @@ public void resume_disconnected() { CompletionSet msgComplete1 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { channelTx.publish("test_event", "Test message (resume_disconnected) " + i, msgComplete1.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ ErrorInfo[] errors = msgComplete1.waitFor(); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* wait for the subscription callback to be called */ messageWaiter.waitFor(messageCount); @@ -250,18 +250,18 @@ public void resume_disconnected() { ablyRx.connection.connectionManager.requestState(ConnectionState.disconnected); /* wait */ - try { Thread.sleep(2000L); } catch(InterruptedException e) {} + try { Thread.sleep(2000L); } catch(InterruptedException ignored) {} /* publish next messages to the channel */ CompletionSet msgComplete2 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { channelTx.publish("test_event", "Test message (resume_disconnected) " + i, msgComplete2.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ errors = msgComplete2.waitFor(); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* reconnect the rx connection, and expect the messages to be delivered */ ablyRx.connection.connect(); @@ -327,12 +327,12 @@ public void resume_multiple_channel() { for(int i = 0; i < messageCount; i++) { channelTx1.publish("test_event1", "Test message (resume_multiple_channel) " + i, msgComplete1.add()); channelTx2.publish("test_event2", "Test message (resume_multiple_channel) " + i, msgComplete1.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ ErrorInfo[] errors = msgComplete1.waitFor(); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* wait for the subscription callback to be called */ messageWaiter1.waitFor(messageCount); @@ -349,19 +349,19 @@ public void resume_multiple_channel() { ablyRx.connection.connectionManager.requestState(ConnectionState.disconnected); /* wait */ - try { Thread.sleep(2000L); } catch(InterruptedException e) {} + try { Thread.sleep(2000L); } catch(InterruptedException ignored) {} /* publish next messages to the channel */ CompletionSet msgComplete2 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { channelTx1.publish("test_event1", "Test message (resume_multiple_channel) " + i, msgComplete2.add()); channelTx2.publish("test_event2", "Test message (resume_multiple_channel) " + i, msgComplete2.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ errors = msgComplete2.waitFor(); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* reconnect the rx connection, and expect the messages to be delivered */ ablyRx.connection.connect(); @@ -420,12 +420,12 @@ public void resume_multiple_interval() { CompletionSet msgComplete1 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { channelTx.publish("test_event", "Test message (resume_multiple_interval) " + i, msgComplete1.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ ErrorInfo[] errors = msgComplete1.waitFor(); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* wait for the subscription callback to be called */ messageWaiter.waitFor(messageCount); @@ -439,18 +439,18 @@ public void resume_multiple_interval() { ablyRx.connection.connectionManager.requestState(ConnectionState.disconnected); /* wait */ - try { Thread.sleep(20000L); } catch(InterruptedException e) {} + try { Thread.sleep(20000L); } catch(InterruptedException ignored) {} /* publish next messages to the channel */ CompletionSet msgComplete2 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { channelTx.publish("test_event", "Test message (resume_multiple_interval) " + i, msgComplete2.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ errors = msgComplete2.waitFor(); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* reconnect the rx connection, and expect the messages to be delivered */ ablyRx.connection.connect(); @@ -509,12 +509,12 @@ public void resume_verify_publish() { CompletionSet msgComplete1 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { channelTx.publish("test_event", "Test message (resume_simple) " + i, msgComplete1.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ ErrorInfo[] errors = msgComplete1.waitFor(); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* wait for the subscription callback to be called */ messageWaiter.waitFor(messageCount); @@ -536,7 +536,7 @@ public void resume_verify_publish() { } /* wait */ - try { Thread.sleep(2000L); } catch(InterruptedException e) {} + try { Thread.sleep(2000L); } catch(InterruptedException ignored) {} /* reconnect the tx connection */ System.out.println("*** about to reconnect tx connection"); @@ -547,7 +547,7 @@ public void resume_verify_publish() { CompletionSet msgComplete2 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { channelTx.publish("test_event", "Test message (resume_simple) " + i, msgComplete2.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called. This never finishes if @@ -556,7 +556,7 @@ public void resume_verify_publish() { System.out.println("*** published. About to wait for callbacks"); errors = msgComplete2.waitFor(); System.out.println("*** done"); - assertTrue("Verify success from all message callbacks", errors.length == 0); + assertEquals("Verify success from all message callbacks", 0, errors.length); /* wait for the subscription callback to be called */ messageWaiter.waitFor(messageCount); @@ -619,14 +619,12 @@ public void resume_publish_queue() { CompletionSet msgComplete1 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { senderChannel.publish("test_event", "Test message (resume_publish_queue) " + i, msgComplete1.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* wait for the publish callback to be called */ ErrorInfo[] errors = msgComplete1.waitFor(); - assertTrue( - "First round of messages has errors", errors.length == 0 - ); + assertEquals("First round of messages has errors", 0, errors.length); /* wait for the subscription callback to be called */ messageWaiter.waitFor(messageCount); @@ -643,7 +641,7 @@ public void resume_publish_queue() { sender.connection.connectionManager.requestState(ConnectionState.disconnected); /* wait */ - try { Thread.sleep(2000L); } catch(InterruptedException e) {} + try { Thread.sleep(2000L); } catch(InterruptedException ignored) {} /* * publish further messages to the channel, which should be queued @@ -652,7 +650,7 @@ public void resume_publish_queue() { CompletionSet msgComplete2 = new CompletionSet(); for(int i = 0; i < messageCount; i++) { senderChannel.publish("queued_message_" + i, "Test queued message (resume_publish_queue) " + i, msgComplete2.add()); - try { Thread.sleep(delay); } catch(InterruptedException e){} + try { Thread.sleep(delay); } catch(InterruptedException ignored){} } /* reconnect the sender */ @@ -662,10 +660,7 @@ public void resume_publish_queue() { /* wait for the publish callback to be called.*/ errors = msgComplete2.waitFor(); - assertTrue( - "Second round of messages (queued) has errors", - errors.length == 0 - ); + assertEquals("Second round of messages (queued) has errors", 0, errors.length); /* wait for the subscription callback to be called */ messageWaiter.waitFor(messageCount); @@ -731,10 +726,7 @@ public void resume_publish_resend_pending_messages_when_resume_is_successful() { /* wait for the publish callback to be called.*/ ErrorInfo[] errors = senderCompletion.waitFor(); - assertTrue( - "First completion has errors", - errors.length == 0 - ); + assertEquals("First completion has errors", 0, errors.length); //assert that messages sent till now are sent with correct size and serials assertEquals("First round of messages has incorrect size", 3, transport.getPublishedMessages().size()); @@ -797,10 +789,7 @@ public void resume_publish_resend_pending_messages_when_resume_is_successful() { (new ChannelWaiter(senderChannel)).waitFor(ChannelState.attached); /* wait for the publish callback to be called.*/ ErrorInfo[] senderErrors = senderCompletion.waitFor(); - assertTrue( - "Second round of send has errors", - senderErrors.length == 0 - ); + assertEquals("Second round of send has errors", 0, senderErrors.length); assertEquals("Second round of messages has incorrect size", 6, transport.getPublishedMessages().size()); //make sure they were sent with correct serials @@ -917,7 +906,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { /* Wait for the connection to go stale, then reconnect */ try { Thread.sleep(waitInDisconnectedState); - } catch (InterruptedException e) { + } catch (InterruptedException ignored) { } ably.connection.connect(); connectionWaiter.waitFor(ConnectionState.connected); @@ -933,10 +922,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { assertEquals("Connection has the same id", ChannelState.attached, senderChannel.state); ErrorInfo[] resendErrors = senderCompletion.waitFor(); - assertTrue( - "Second round of messages (queued) has errors", - resendErrors.length == 0 - ); + assertEquals("Second round of messages (queued) has errors", 0, resendErrors.length); assertEquals("Second round of messages has incorrect size", 6, transport.getPublishedMessages().size()); //make sure they were sent with reset serials @@ -956,8 +942,9 @@ public void resume_publish_reenter_when_resume_failed() throws AblyException { final String channelName = "sender_channel"; final MockWebsocketFactory mockWebsocketFactory = new MockWebsocketFactory(); final DebugOptions options = createOptions(testVars.keys[0].keyStr); - final String[] clients = new String[]{"client1","client2","client3", - "client4","client5","client6","client7","client8","client9"}; + final String[] clients = new String[]{"client1", "client2", "client3", "client4", "client5", + "client6", "client7", "client8", "client9"}; + options.logLevel = Log.VERBOSE; options.realtimeRequestTimeout = 2000L; @@ -972,10 +959,12 @@ public void resume_publish_reenter_when_resume_failed() throws AblyException { @Override public void onConnectionStateChanged(ConnectionStateChange state) { try { - Field connectionStateField = ably.connection.connectionManager.getClass().getDeclaredField("connectionStateTtl"); + Field connectionStateField = ably.connection.connectionManager.getClass(). + getDeclaredField("connectionStateTtl"); connectionStateField.setAccessible(true); connectionStateField.setLong(ably.connection.connectionManager, newTtl); - Field maxIdleField = ably.connection.connectionManager.getClass().getDeclaredField("maxIdleInterval"); + Field maxIdleField = ably.connection.connectionManager.getClass(). + getDeclaredField("maxIdleInterval"); maxIdleField.setAccessible(true); maxIdleField.setLong(ably.connection.connectionManager, newIdleInterval); } catch (NoSuchFieldException | IllegalAccessException e) { @@ -990,10 +979,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { final Channel senderChannel = ably.channels.get(channelName); senderChannel.attach(); (new ChannelWaiter(senderChannel)).waitFor(ChannelState.attached); - assertEquals( - "The sender's channel should be attached", - senderChannel.state, ChannelState.attached - ); + assertEquals("The sender's channel should be attached", senderChannel.state, ChannelState.attached); MockWebsocketFactory.MockWebsocketTransport transport = mockWebsocketFactory.getCreatedTransport(); CompletionSet presenceCompletion = new CompletionSet(); @@ -1043,7 +1029,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { /* Wait for the connection to go stale, then reconnect */ try { Thread.sleep(waitInDisconnectedState); - } catch (InterruptedException e) { + } catch (InterruptedException ignored) { } //now let's unblock the ack nacks and reconnect @@ -1070,13 +1056,9 @@ public void onConnectionStateChanged(ConnectionStateChange state) { for (ErrorInfo resendError : resendErrors) { System.out.println("presence_resume_test: error "+resendError.message); } - assertTrue( - "Second round of messages (queued) has errors", - resendErrors.length == 0 - ); + assertEquals("Second round of messages (queued) has errors", 0, resendErrors.length); - for (PresenceMessage presenceMessage: - transport.getSentPresenceMessages()) { + for (PresenceMessage presenceMessage: transport.getSentPresenceMessages()) { System.out.println("presence_resume_test: sent message with client: "+presenceMessage.clientId +" " + " action:"+presenceMessage.action); } @@ -1087,7 +1069,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { sentPresenceMap.put(presenceMessage.clientId, presenceMessage); } for (String client : clients) { - assertTrue("Client id isn't there:"+client, sentPresenceMap.containsKey(client)); + assertTrue("Client id isn't there:" + client, sentPresenceMap.containsKey(client)); } } } From cf567ec18552a9854676d8a73111a9f4dd39ac6f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 23 Jan 2024 12:04:43 +0530 Subject: [PATCH 49/73] Getting channel serial only when channel state is attached for recovery key --- lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java index 74129ff9d..f22789c78 100644 --- a/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java +++ b/lib/src/main/java/io/ably/lib/realtime/AblyRealtime.java @@ -290,7 +290,9 @@ protected void setChannelSerialsFromRecoverOption(Map serials) { protected Map getChannelSerials() { Map channelSerials = new HashMap<>(); for (Channel channel : this.channels.values()) { - channelSerials.put(channel.name, channel.properties.channelSerial); + if (channel.state == ChannelState.attached) { + channelSerials.put(channel.name, channel.properties.channelSerial); + } } return channelSerials; } From 564a96af8f6ddffdace2c50836faa27afaa6cbd1 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 23 Jan 2024 12:40:24 +0530 Subject: [PATCH 50/73] refactored presence impl for entering internal members --- .../io/ably/lib/realtime/ChannelBase.java | 4 ++-- .../java/io/ably/lib/realtime/Presence.java | 24 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 818027a09..4ae7b353a 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -397,12 +397,12 @@ private void setAttached(ProtocolMessage message) { if(state == ChannelState.attached) { Log.v(TAG, String.format(Locale.ROOT, "Server initiated attach for channel %s", name)); if (!message.hasFlag(Flag.resumed)) { // RTL12 - presence.onAttached(message.hasFlag(Flag.has_presence), true); + presence.onAttached(message.hasFlag(Flag.has_presence)); emitUpdate(message.error, false); } } else { - presence.onAttached(message.hasFlag(Flag.has_presence), true); + presence.onAttached(message.hasFlag(Flag.has_presence)); setState(ChannelState.attached, message.error, message.hasFlag(Flag.resumed)); } } diff --git a/lib/src/main/java/io/ably/lib/realtime/Presence.java b/lib/src/main/java/io/ably/lib/realtime/Presence.java index b1c1dcb7d..bbfb3baf1 100644 --- a/lib/src/main/java/io/ably/lib/realtime/Presence.java +++ b/lib/src/main/java/io/ably/lib/realtime/Presence.java @@ -589,6 +589,21 @@ public void enterClient(String clientId, Object data, CompletionListener listene updatePresence(new PresenceMessage(PresenceMessage.Action.enter, clientId, data), listener); } + private void enterClientWithId(String id, String clientId, Object data, CompletionListener listener) throws AblyException { + if(clientId == null) { + String errorMessage = String.format(Locale.ROOT, "Channel %s: unable to enter presence channel (null clientId specified)", channel.name); + Log.v(TAG, errorMessage); + if(listener != null) { + listener.onError(new ErrorInfo(errorMessage, 40000)); + return; + } + } + PresenceMessage presenceMsg = new PresenceMessage(PresenceMessage.Action.enter, clientId, data); + presenceMsg.id = id; + Log.v(TAG, "enterClient(); channel = " + channel.name + "; clientId = " + clientId); + updatePresence(presenceMsg, listener); + } + /** * Updates the data payload for a presence member using a given clientId. * Enables a single client to update presence on behalf of any number of clients using a single connection. @@ -887,7 +902,7 @@ private void failQueuedMessages(ErrorInfo reason) { * attach / detach ************************************/ - void onAttached(boolean hasPresence, boolean enterInternalPresenceMembers) { + void onAttached(boolean hasPresence) { /* Interrupt get() call => by unblocking presence.waitForSync()*/ synchronized (presence) { presence.notifyAll(); @@ -898,10 +913,7 @@ void onAttached(boolean hasPresence, boolean enterInternalPresenceMembers) { endSync(); } sendQueuedMessages(); // RTP5b - - if (enterInternalPresenceMembers) { // RTP17f - enterInternalMembers(); - } + enterInternalMembers(); // RTP17f } /** @@ -910,7 +922,7 @@ void onAttached(boolean hasPresence, boolean enterInternalPresenceMembers) { synchronized void enterInternalMembers() { for (final PresenceMessage item: internalPresence.values()) { try { - enterClient(item.clientId, item.data, new CompletionListener() { + enterClientWithId(item.id, item.clientId, item.data, new CompletionListener() { @Override public void onSuccess() { } From 75a8032457da6169c26e95d485ee2a0c1038255e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 23 Jan 2024 19:03:26 +0530 Subject: [PATCH 51/73] updated realtime channel test for resume flag --- .../test/realtime/RealtimeChannelTest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index 181dbc49c..111a9b269 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -1479,8 +1479,8 @@ public void channel_server_initiated_attached_detached() throws AblyException { channel.attach(); channelWaiter.waitFor(ChannelState.attached); - final int[] updateEventsEmitted = new int[]{0}; - final boolean[] resumedFlag = new boolean[]{true}; + final int[] updateEventsEmitted = {0}; + final boolean[] resumedFlag = {false}; channel.on(ChannelEvent.update, new ChannelStateListener() { @Override public void onChannelStateChanged(ChannelStateChange stateChange) { @@ -1497,19 +1497,19 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { }}; ably.connection.connectionManager.onMessage(null, attachedMessage); - /* Inject detached message as if from the server */ - ProtocolMessage detachedMessage = new ProtocolMessage() {{ - action = Action.detached; - channel = channelName; - }}; - ably.connection.connectionManager.onMessage(null, detachedMessage); +// /* Inject detached message as if from the server */ +// ProtocolMessage detachedMessage = new ProtocolMessage() {{ +// action = Action.detached; +// channel = channelName; +// }}; +// ably.connection.connectionManager.onMessage(null, detachedMessage); /* Channel should transition to attaching, then to attached */ - channelWaiter.waitFor(ChannelState.attaching); - channelWaiter.waitFor(ChannelState.attached); +// channelWaiter.waitFor(ChannelState.attaching); +// channelWaiter.waitFor(ChannelState.attached); /* Verify received UPDATE message on channel */ - assertEquals("Verify exactly one UPDATE event was emitted on the channel", updateEventsEmitted[0], 1); + assertEquals("Verify exactly one UPDATE event was emitted on the channel",1, updateEventsEmitted[0]); assertTrue("Verify resumed flag set in UPDATE event", resumedFlag[0]); } finally { if (ably != null) From ea65bffcc1332799609136b3e12a61bd93e773dc Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 24 Jan 2024 18:08:10 +0530 Subject: [PATCH 52/73] Added todo that checks for exact error --- lib/src/main/java/io/ably/lib/realtime/ChannelBase.java | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 4ae7b353a..e8b61383b 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -611,6 +611,7 @@ public void run() { /* State changes provoked by ConnectionManager state changes. */ public void setConnected() { + // TODO - seems test is failing because of explicit attach after connect if (state.isReattachable()){ attach(true,null); // RTN15c6, RTN15c7 } From cd355b6d83cbf082c713feb5b2b3cbc147bbbe72 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Thu, 25 Jan 2024 18:02:04 +0530 Subject: [PATCH 53/73] Fixed test for server injected attach --- .../io/ably/lib/realtime/ChannelBase.java | 10 +-- .../test/realtime/RealtimeChannelTest.java | 74 ++++++++++++++++--- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index e8b61383b..7538789ad 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -82,15 +82,13 @@ public abstract class ChannelBase extends EventEmitter updateEventsEmitted = new ArrayList<>(); + channel.on(new ChannelStateListener() { + @Override + public void onChannelStateChanged(ChannelStateChange stateChange) { + updateEventsEmitted.add(stateChange); + } + }); + + /* Inject attached message as if received from the server */ + ProtocolMessage attachedMessage = new ProtocolMessage() {{ + action = Action.attached; + channel = channelName; + }}; + + ably.connection.connectionManager.onMessage(null, attachedMessage); + assertEquals(1, updateEventsEmitted.size()); + assertEquals(ChannelEvent.update, updateEventsEmitted.get(0).event); + assertFalse(updateEventsEmitted.get(0).resumed); + + } finally { + if (ably != null) + ably.close(); + Defaults.realtimeRequestTimeout = oldRealtimeTimeout; + } + } + /* * Establish connection, attach channel, simulate sending attached and detached messages * from the server, test correct behaviour @@ -1472,6 +1523,7 @@ public void channel_server_initiated_attached_detached() throws AblyException { opts.channelRetryTimeout = 1000; ably = new AblyRealtime(opts); + new ConnectionWaiter(ably.connection).waitFor(ConnectionState.connected); Channel channel = ably.channels.get(channelName); ChannelWaiter channelWaiter = new ChannelWaiter(channel); @@ -1490,12 +1542,12 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { }); /* Inject attached message as if received from the server */ - ProtocolMessage attachedMessage = new ProtocolMessage() {{ - action = Action.attached; - channel = channelName; - flags |= Flag.resumed.getMask(); - }}; - ably.connection.connectionManager.onMessage(null, attachedMessage); +// ProtocolMessage attachedMessage = new ProtocolMessage() {{ +// action = Action.attached; +// channel = channelName; +// flags |= Flag.resumed.getMask(); +// }}; +// ably.connection.connectionManager.onMessage(null, attachedMessage); // /* Inject detached message as if from the server */ // ProtocolMessage detachedMessage = new ProtocolMessage() {{ From 969d1d563d765f6b4d3cf32a9add85b6f580eea8 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 26 Jan 2024 11:16:47 +0530 Subject: [PATCH 54/73] Refactored helper method to wait channel event --- .../java/io/ably/lib/test/common/Helpers.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/common/Helpers.java b/lib/src/test/java/io/ably/lib/test/common/Helpers.java index 8a9a7e38b..b2af77cb8 100644 --- a/lib/src/test/java/io/ably/lib/test/common/Helpers.java +++ b/lib/src/test/java/io/ably/lib/test/common/Helpers.java @@ -28,14 +28,8 @@ import io.ably.lib.debug.DebugOptions.RawProtocolListener; import io.ably.lib.http.HttpCore; import io.ably.lib.http.HttpUtils; -import io.ably.lib.realtime.Channel; +import io.ably.lib.realtime.*; import io.ably.lib.realtime.Channel.MessageListener; -import io.ably.lib.realtime.ChannelState; -import io.ably.lib.realtime.ChannelStateListener; -import io.ably.lib.realtime.CompletionListener; -import io.ably.lib.realtime.Connection; -import io.ably.lib.realtime.ConnectionState; -import io.ably.lib.realtime.ConnectionStateListener; import io.ably.lib.realtime.Presence.PresenceListener; import io.ably.lib.transport.ConnectionManager; import io.ably.lib.types.AblyException; @@ -586,28 +580,42 @@ public ChannelWaiter(Channel channel) { /** * Wait for a given state to be reached. - * @param state */ public synchronized ErrorInfo waitFor(ChannelState state) { Log.d(TAG, "waitFor(" + state + ")"); while(channel.state != state) - try { wait(); } catch(InterruptedException e) {} + try { wait(); } catch(InterruptedException ignored) {} Log.d(TAG, "waitFor done: " + channel.state + ", " + channel.reason + ")"); return channel.reason; } + /** + * Wait for a given ChannelEvent to be reached. + */ + public synchronized ChannelStateChange waitFor(ChannelEvent channelEvent) { + Log.d(TAG, "waitFor(" + channelEvent + ")"); + while(this.channelStateChange.event != channelEvent) + try { wait(); } catch(InterruptedException ignored) {} + Log.d(TAG, "waitFor done: " + channel.state + ", " + channel.reason + ")"); + return this.channelStateChange; + } + /** * ChannelStateListener interface */ @Override public void onChannelStateChanged(ChannelStateListener.ChannelStateChange stateChange) { - synchronized(this) { notify(); } + synchronized(this) { + this.channelStateChange = stateChange; + notify(); + } } /** * Internal */ - private Channel channel; + private final Channel channel; + private ChannelStateChange channelStateChange; } /** From f84e07adddb985cb3fab25d290609c68a33417eb Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 26 Jan 2024 11:17:06 +0530 Subject: [PATCH 55/73] Added test for server initiated detached --- .../test/realtime/RealtimeChannelTest.java | 71 +++++-------------- 1 file changed, 19 insertions(+), 52 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index be7a5b18a..56c8441a1 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -37,14 +37,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; public class RealtimeChannelTest extends ParameterizedTest { @@ -1477,24 +1470,19 @@ public void channel_server_initiated_attached() throws AblyException { channel.attach(); channelWaiter.waitFor(ChannelState.attached); - List updateEventsEmitted = new ArrayList<>(); - channel.on(new ChannelStateListener() { - @Override - public void onChannelStateChanged(ChannelStateChange stateChange) { - updateEventsEmitted.add(stateChange); - } - }); - /* Inject attached message as if received from the server */ ProtocolMessage attachedMessage = new ProtocolMessage() {{ action = Action.attached; channel = channelName; }}; - ably.connection.connectionManager.onMessage(null, attachedMessage); - assertEquals(1, updateEventsEmitted.size()); - assertEquals(ChannelEvent.update, updateEventsEmitted.get(0).event); - assertFalse(updateEventsEmitted.get(0).resumed); + + ChannelStateListener.ChannelStateChange channelUpdateEvent = channelWaiter.waitFor(ChannelEvent.update); + assertEquals(ChannelEvent.update, channelUpdateEvent.event); + assertEquals(ChannelState.attached, channelUpdateEvent.previous); + assertEquals(ChannelState.attached, channelUpdateEvent.current); + assertFalse(channelUpdateEvent.resumed); + assertNull(channelUpdateEvent.reason); } finally { if (ably != null) @@ -1504,13 +1492,13 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { } /* - * Establish connection, attach channel, simulate sending attached and detached messages + * Establish connection, attach channel, simulate sending detached messages * from the server, test correct behaviour * - * Tests RTL12, RTL13a + * Tests RTL13a */ @Test - public void channel_server_initiated_attached_detached() throws AblyException { + public void channel_server_initiated_detached() throws AblyException { AblyRealtime ably = null; long oldRealtimeTimeout = Defaults.realtimeRequestTimeout; final String channelName = "channel_server_initiated_attach_detach"; @@ -1531,38 +1519,17 @@ public void channel_server_initiated_attached_detached() throws AblyException { channel.attach(); channelWaiter.waitFor(ChannelState.attached); - final int[] updateEventsEmitted = {0}; - final boolean[] resumedFlag = {false}; - channel.on(ChannelEvent.update, new ChannelStateListener() { - @Override - public void onChannelStateChanged(ChannelStateChange stateChange) { - updateEventsEmitted[0]++; - resumedFlag[0] = stateChange.resumed; - } - }); - - /* Inject attached message as if received from the server */ -// ProtocolMessage attachedMessage = new ProtocolMessage() {{ -// action = Action.attached; -// channel = channelName; -// flags |= Flag.resumed.getMask(); -// }}; -// ably.connection.connectionManager.onMessage(null, attachedMessage); - -// /* Inject detached message as if from the server */ -// ProtocolMessage detachedMessage = new ProtocolMessage() {{ -// action = Action.detached; -// channel = channelName; -// }}; -// ably.connection.connectionManager.onMessage(null, detachedMessage); + /* Inject detached message as if from the server */ + ProtocolMessage detachedMessage = new ProtocolMessage() {{ + action = Action.detached; + channel = channelName; + }}; + ably.connection.connectionManager.onMessage(null, detachedMessage); /* Channel should transition to attaching, then to attached */ -// channelWaiter.waitFor(ChannelState.attaching); -// channelWaiter.waitFor(ChannelState.attached); + channelWaiter.waitFor(ChannelState.attaching); + channelWaiter.waitFor(ChannelState.attached); - /* Verify received UPDATE message on channel */ - assertEquals("Verify exactly one UPDATE event was emitted on the channel",1, updateEventsEmitted[0]); - assertTrue("Verify resumed flag set in UPDATE event", resumedFlag[0]); } finally { if (ably != null) ably.close(); From da747626c0a343d946e2a3994e4d5445c36dbb4c Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 26 Jan 2024 13:33:48 +0530 Subject: [PATCH 56/73] Fixed connect reauth failure test --- .../realtime/RealtimeConnectFailTest.java | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java index eff2e652d..021d4ec22 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java @@ -418,13 +418,11 @@ public void onError(ErrorInfo reason) { */ @Test public void connect_reauth_failure_state_flow_test() { - try { - AblyRest ablyRest = null; ClientOptions opts = createOptions(testVars.keys[0].keyStr); - ablyRest = new AblyRest(opts); - final TokenDetails tokenDetails = ablyRest.auth.requestToken(new TokenParams() {{ ttl = 8000L; }}, null); + AblyRest ablyRest = new AblyRest(opts); + final TokenDetails tokenDetails = ablyRest.auth.requestToken(new TokenParams() {{ ttl = 2000L; }}, null); assertNotNull("Expected token value", tokenDetails.token); final ArrayList stateHistory = new ArrayList<>(); @@ -433,31 +431,14 @@ public void connect_reauth_failure_state_flow_test() { optsForRealtime.authCallback = new TokenCallback() { @Override public Object getTokenRequest(TokenParams params) throws AblyException { - // return already expired token + // always return same token return tokenDetails; } }; optsForRealtime.tokenDetails = tokenDetails; final AblyRealtime ablyRealtime = new AblyRealtime(optsForRealtime); - ablyRealtime.connection.on(ConnectionState.connected, new ConnectionStateListener() { - @Override - public void onConnectionStateChanged(ConnectionStateChange state) { - /* To go quicker into a disconnected state we use a - * smaller value for maxIdleInterval - */ - try { - Field field = ablyRealtime.connection.connectionManager.getClass().getDeclaredField("maxIdleInterval"); - field.setAccessible(true); - field.setLong(ablyRealtime.connection.connectionManager, 5000L); - } catch (NoSuchFieldException|IllegalAccessException e) { - fail("Unexpected exception in checking connectionStateTtl"); - } - } - }); - (new ConnectionWaiter(ablyRealtime.connection)).waitFor(ConnectionState.connected); - // TODO: improve by collecting and testing also auth attempts final List correctHistory = Arrays.asList( ConnectionState.disconnected, ConnectionState.connecting, @@ -475,7 +456,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { if (state.current == ConnectionState.disconnected) { disconnections++; if (disconnections == maxDisconnections) { - assertTrue("Verifying state change history", stateHistory.equals(correctHistory)); + assertEquals(correctHistory, stateHistory); ablyRealtime.close(); } } From 5297b404f2bcd8db3e4b13b2ffbe027a6b3b2796 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 26 Jan 2024 17:33:20 +0530 Subject: [PATCH 57/73] Added a test for valid/invalid resume channel attach --- .../test/realtime/RealtimeChannelTest.java | 124 +++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index 56c8441a1..12a606df1 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -1541,10 +1541,130 @@ public void channel_server_initiated_detached() throws AblyException { * Establish connection, attach channel, disconnection and failed resume * verify that subsequent attaches are performed, and give rise to update events * - * Tests RTN15c3 + * Tests RTN15c6 */ @Test - public void channel_resume_lost_continuity() throws AblyException { + public void channel_valid_resume_reattach_channels() throws AblyException { + AblyRealtime ably = null; + final String attachedChannelName = "channel_resume_lost_continuity_attached"; + final String suspendedChannelName = "channel_resume_lost_continuity_suspended"; + + try { + ClientOptions opts = createOptions(testVars.keys[0].keyStr); + ably = new AblyRealtime(opts); + + /* prepare channels */ + Channel attachedChannel = ably.channels.get(attachedChannelName); + ChannelWaiter attachedChannelWaiter = new ChannelWaiter(attachedChannel); + attachedChannel.attach(); + attachedChannelWaiter.waitFor(ChannelState.attached); + + Channel suspendedChannel = ably.channels.get(suspendedChannelName); + suspendedChannel.state = ChannelState.suspended; + ChannelWaiter suspendedChannelWaiter = new ChannelWaiter(suspendedChannel); + + final boolean[] suspendedStateReached = new boolean[2]; + final boolean[] attachingStateReached = new boolean[2]; + final boolean[] attachedStateReached = new boolean[2]; + final boolean[] resumedFlag = new boolean[]{true, true}; + attachedChannel.on(new ChannelStateListener() { + @Override + public void onChannelStateChanged(ChannelStateChange stateChange) { + switch(stateChange.current) { + case suspended: + suspendedStateReached[0] = true; + break; + case attaching: + attachingStateReached[0] = true; + break; + case attached: + attachedStateReached[0] = true; + resumedFlag[0] = stateChange.resumed; + break; + default: + break; + } + } + }); + suspendedChannel.on(new ChannelStateListener() { + @Override + public void onChannelStateChanged(ChannelStateChange stateChange) { + switch(stateChange.current) { + case attaching: + attachingStateReached[1] = true; + break; + case attached: + attachedStateReached[1] = true; + resumedFlag[1] = stateChange.resumed; + break; + default: + break; + } + } + }); + + /* disconnect, and sabotage the resume */ + String originalConnectionId = ably.connection.id; + ably.connection.key = "_____!ably___test_fake-key____"; + ably.connection.id = "ably___tes"; + ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); + + /* suppress automatic retries by the connection manager */ + try { + Method method = ably.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); + method.setAccessible(true); + method.invoke(ably.connection.connectionManager); + } catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException e) { + fail("Unexpected exception in suppressing retries"); + } + + connectionWaiter.waitFor(ConnectionState.disconnected); + assertEquals("Verify disconnected state is reached", ConnectionState.disconnected, ably.connection.state); + + /* wait */ + try { Thread.sleep(2000L); } catch(InterruptedException e) {} + + /* wait for connection to be reestablished */ + System.out.println("channel_resume_lost_continuity: initiating reconnection (resume)"); + ably.connection.connect(); + connectionWaiter.waitFor(ConnectionState.connected); + + /* verify a new connection was assigned */ + assertNotEquals("A new connection was created", originalConnectionId, ably.connection.id); + + /* previously suspended channel should transition to attaching, then to attached */ + suspendedChannelWaiter.waitFor(ChannelState.attached); + + /* previously attached channel should remain attached */ + attachedChannelWaiter.waitFor(ChannelState.attached); + + /* + * Verify each channel undergoes relevant events: + * - previously attached channel does attaching, attached, without visiting suspended; + * - previously suspended channel does attaching, attached + */ + assertEquals("Verify channel was not suspended", suspendedStateReached[0], false); + assertEquals("Verify channel was attaching", attachingStateReached[0], true); + assertEquals("Verify channel was attached", attachedStateReached[0], true); + assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[0]); + + assertEquals("Verify channel was attaching", attachingStateReached[1], true); + assertEquals("Verify channel was attached", attachedStateReached[1], true); + assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[1]); + } finally { + if (ably != null) + ably.close(); + } + } + + /* + * Establish connection, attach channel, disconnection and failed resume + * verify that subsequent attaches are performed, and give rise to update events + * + * Tests RTN15c7 + */ + @Test + public void channel_invalid_resume_reattach_channels() throws AblyException { AblyRealtime ably = null; final String attachedChannelName = "channel_resume_lost_continuity_attached"; final String suspendedChannelName = "channel_resume_lost_continuity_suspended"; From e9790c02b6fd4e5143d1306b17ff70e15914ef7a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 26 Jan 2024 17:34:52 +0530 Subject: [PATCH 58/73] Simplified channel attach/detach assertions --- .../test/realtime/RealtimeChannelTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index 12a606df1..58a862a3b 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -1643,13 +1643,13 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { * - previously attached channel does attaching, attached, without visiting suspended; * - previously suspended channel does attaching, attached */ - assertEquals("Verify channel was not suspended", suspendedStateReached[0], false); - assertEquals("Verify channel was attaching", attachingStateReached[0], true); - assertEquals("Verify channel was attached", attachedStateReached[0], true); + assertFalse("Verify channel was not suspended", suspendedStateReached[0]); + assertTrue("Verify channel was attaching", attachingStateReached[0]); + assertTrue("Verify channel was attached", attachedStateReached[0]); assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[0]); - assertEquals("Verify channel was attaching", attachingStateReached[1], true); - assertEquals("Verify channel was attached", attachedStateReached[1], true); + assertTrue("Verify channel was attaching", attachingStateReached[1]); + assertTrue("Verify channel was attached", attachedStateReached[1]); assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[1]); } finally { if (ably != null) @@ -1763,13 +1763,13 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { * - previously attached channel does attaching, attached, without visiting suspended; * - previously suspended channel does attaching, attached */ - assertEquals("Verify channel was not suspended", suspendedStateReached[0], false); - assertEquals("Verify channel was attaching", attachingStateReached[0], true); - assertEquals("Verify channel was attached", attachedStateReached[0], true); + assertFalse("Verify channel was not suspended", suspendedStateReached[0]); + assertTrue("Verify channel was attaching", attachingStateReached[0]); + assertTrue("Verify channel was attached", attachedStateReached[0]); assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[0]); - assertEquals("Verify channel was attaching", attachingStateReached[1], true); - assertEquals("Verify channel was attached", attachedStateReached[1], true); + assertTrue("Verify channel was attaching", attachingStateReached[1]); + assertTrue("Verify channel was attached", attachedStateReached[1]); assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[1]); } finally { if (ably != null) From f7c5437bbcc581f53df4546c2c7ce044ae28d9ba Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 29 Jan 2024 18:41:52 +0530 Subject: [PATCH 59/73] Added channelStateChange specific helpers with recorders --- .../java/io/ably/lib/test/common/Helpers.java | 64 +++++++++++++++---- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/common/Helpers.java b/lib/src/test/java/io/ably/lib/test/common/Helpers.java index b2af77cb8..729c80c2c 100644 --- a/lib/src/test/java/io/ably/lib/test/common/Helpers.java +++ b/lib/src/test/java/io/ably/lib/test/common/Helpers.java @@ -18,6 +18,7 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.regex.Pattern; +import java.util.stream.Collectors; import com.google.gson.Gson; import com.google.gson.JsonArray; @@ -551,14 +552,14 @@ public ConnectionManagerWaiter(ConnectionManager connectionManager) { */ public synchronized ErrorInfo waitFor(ConnectionState state) { while(connectionManager.getConnectionState().state != state) - try { wait(INTERVAL_POLLING); } catch(InterruptedException e) {} + try { wait(INTERVAL_POLLING); } catch(InterruptedException ignored) {} return connectionManager.getConnectionState().defaultErrorInfo; } /** * Internal */ - private ConnectionManager connectionManager; + private final ConnectionManager connectionManager; } /** @@ -571,7 +572,6 @@ public static class ChannelWaiter implements ChannelStateListener { /** * Public API - * @param channel */ public ChannelWaiter(Channel channel) { this.channel = channel; @@ -581,11 +581,13 @@ public ChannelWaiter(Channel channel) { /** * Wait for a given state to be reached. */ - public synchronized ErrorInfo waitFor(ChannelState state) { - Log.d(TAG, "waitFor(" + state + ")"); - while(channel.state != state) - try { wait(); } catch(InterruptedException ignored) {} - Log.d(TAG, "waitFor done: " + channel.state + ", " + channel.reason + ")"); + public synchronized ErrorInfo waitFor(ChannelState ... states) { + for (ChannelState state : states) { + Log.d(TAG, "waitFor(" + state + ")"); + while(channel.state != state) + try { wait(); } catch(InterruptedException ignored) {} + Log.d(TAG, "waitFor done: " + channel.state + ", " + channel.reason + ")"); + } return channel.reason; } @@ -594,28 +596,64 @@ public synchronized ErrorInfo waitFor(ChannelState state) { */ public synchronized ChannelStateChange waitFor(ChannelEvent channelEvent) { Log.d(TAG, "waitFor(" + channelEvent + ")"); - while(this.channelStateChange.event != channelEvent) + ChannelStateChange lastStateChange = getLastStateChange(); + while(lastStateChange.event != channelEvent) try { wait(); } catch(InterruptedException ignored) {} Log.d(TAG, "waitFor done: " + channel.state + ", " + channel.reason + ")"); - return this.channelStateChange; + return lastStateChange; } /** * ChannelStateListener interface */ @Override - public void onChannelStateChanged(ChannelStateListener.ChannelStateChange stateChange) { + public void onChannelStateChanged(ChannelStateChange stateChange) { synchronized(this) { - this.channelStateChange = stateChange; + recordedStates.add(stateChange); notify(); } } + private final List recordedStates = Collections.synchronizedList(new ArrayList<>()); + + public List getRecordedStates() { + return recordedStates.stream().map(stateChange -> stateChange.current).collect(Collectors.toList()); + } + + public boolean hasFinalStates(ChannelState ... states) { + List rstates = getRecordedStates(); + List vettedList = rstates.subList(rstates.size() - states.length, rstates.size()); + return hasStates(vettedList, states); + } + + public boolean hasStates(ChannelState ... states) { + return hasStates(getRecordedStates(), states); + } + + private static boolean hasStates(List stateList, ChannelState ... states) { + boolean foundStates = false; + int statesCounter = 0; + for (ChannelState recordedState : stateList) { + if (states[statesCounter] != recordedState) { + statesCounter = 0; + } + if (states[statesCounter] == recordedState) { + statesCounter++; + } + if (statesCounter == states.length) { + foundStates = true; + } + } + return foundStates; + } + + public ChannelStateChange getLastStateChange() { + return recordedStates.get(recordedStates.size()-1); + } /** * Internal */ private final Channel channel; - private ChannelStateChange channelStateChange; } /** From a72e99052685ca303e796fadb64bffacb66f1970 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 29 Jan 2024 18:42:21 +0530 Subject: [PATCH 60/73] Fixed tests for re-attaching channels on connection resume success/failure --- .../test/realtime/RealtimeChannelTest.java | 204 ++++++------------ 1 file changed, 67 insertions(+), 137 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index 58a862a3b..8341acd54 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -1546,69 +1546,32 @@ public void channel_server_initiated_detached() throws AblyException { @Test public void channel_valid_resume_reattach_channels() throws AblyException { AblyRealtime ably = null; - final String attachedChannelName = "channel_resume_lost_continuity_attached"; - final String suspendedChannelName = "channel_resume_lost_continuity_suspended"; try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); ably = new AblyRealtime(opts); + ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); + ably.connect(); + connectionWaiter.waitFor(ConnectionState.connected); + String originalConnectionId = ably.connection.id; /* prepare channels */ - Channel attachedChannel = ably.channels.get(attachedChannelName); + Channel attachedChannel = ably.channels.get("attached_channel"); ChannelWaiter attachedChannelWaiter = new ChannelWaiter(attachedChannel); attachedChannel.attach(); attachedChannelWaiter.waitFor(ChannelState.attached); + attachedChannel.publish("chat", "message"); - Channel suspendedChannel = ably.channels.get(suspendedChannelName); - suspendedChannel.state = ChannelState.suspended; + Channel suspendedChannel = ably.channels.get("suspended_channel"); ChannelWaiter suspendedChannelWaiter = new ChannelWaiter(suspendedChannel); + suspendedChannel.attach(); + suspendedChannelWaiter.waitFor(ChannelState.attached); + suspendedChannel.setSuspended(null, true); + suspendedChannelWaiter.waitFor(ChannelState.suspended); - final boolean[] suspendedStateReached = new boolean[2]; - final boolean[] attachingStateReached = new boolean[2]; - final boolean[] attachedStateReached = new boolean[2]; - final boolean[] resumedFlag = new boolean[]{true, true}; - attachedChannel.on(new ChannelStateListener() { - @Override - public void onChannelStateChanged(ChannelStateChange stateChange) { - switch(stateChange.current) { - case suspended: - suspendedStateReached[0] = true; - break; - case attaching: - attachingStateReached[0] = true; - break; - case attached: - attachedStateReached[0] = true; - resumedFlag[0] = stateChange.resumed; - break; - default: - break; - } - } - }); - suspendedChannel.on(new ChannelStateListener() { - @Override - public void onChannelStateChanged(ChannelStateChange stateChange) { - switch(stateChange.current) { - case attaching: - attachingStateReached[1] = true; - break; - case attached: - attachedStateReached[1] = true; - resumedFlag[1] = stateChange.resumed; - break; - default: - break; - } - } - }); + assertEquals(ably.connection.connectionManager.msgSerial, 1); /* disconnect, and sabotage the resume */ - String originalConnectionId = ably.connection.id; - ably.connection.key = "_____!ably___test_fake-key____"; - ably.connection.id = "ably___tes"; - ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); - /* suppress automatic retries by the connection manager */ try { Method method = ably.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); @@ -1621,36 +1584,35 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Verify disconnected state is reached", ConnectionState.disconnected, ably.connection.state); - /* wait */ - try { Thread.sleep(2000L); } catch(InterruptedException e) {} - /* wait for connection to be reestablished */ System.out.println("channel_resume_lost_continuity: initiating reconnection (resume)"); ably.connection.connect(); - connectionWaiter.waitFor(ConnectionState.connected); - /* verify a new connection was assigned */ - assertNotEquals("A new connection was created", originalConnectionId, ably.connection.id); + ErrorInfo resumeError = connectionWaiter.waitFor(ConnectionState.connected); + assertNull(resumeError); + assertNull(ably.connection.connectionManager.getStateErrorInfo()); + assertEquals("Same connection is used", originalConnectionId, ably.connection.id); + assertEquals(ably.connection.connectionManager.msgSerial, 1); - /* previously suspended channel should transition to attaching, then to attached */ + attachedChannelWaiter.waitFor(ChannelState.attaching, ChannelState.attached); suspendedChannelWaiter.waitFor(ChannelState.attached); - /* previously attached channel should remain attached */ - attachedChannelWaiter.waitFor(ChannelState.attached); + assertFalse("Verify channel was not suspended", + attachedChannelWaiter.hasStates(ChannelState.suspended)); + assertTrue("Verify channel was attaching and attached", + attachedChannelWaiter.hasFinalStates(ChannelState.attaching, ChannelState.attached)); + + ChannelStateListener.ChannelStateChange stateChange = attachedChannelWaiter.getLastStateChange(); + assertEquals(ChannelState.attached, stateChange.current); + assertEquals(ChannelState.attaching, stateChange.previous); + + assertTrue("Verify channel was attaching", + suspendedChannelWaiter.hasFinalStates(ChannelState.attaching, ChannelState.attached)); + + stateChange = suspendedChannelWaiter.getLastStateChange(); + assertEquals(ChannelState.attached, stateChange.current); + assertEquals(ChannelState.attaching, stateChange.previous); - /* - * Verify each channel undergoes relevant events: - * - previously attached channel does attaching, attached, without visiting suspended; - * - previously suspended channel does attaching, attached - */ - assertFalse("Verify channel was not suspended", suspendedStateReached[0]); - assertTrue("Verify channel was attaching", attachingStateReached[0]); - assertTrue("Verify channel was attached", attachedStateReached[0]); - assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[0]); - - assertTrue("Verify channel was attaching", attachingStateReached[1]); - assertTrue("Verify channel was attached", attachedStateReached[1]); - assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[1]); } finally { if (ably != null) ably.close(); @@ -1672,62 +1634,26 @@ public void channel_invalid_resume_reattach_channels() throws AblyException { try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); ably = new AblyRealtime(opts); + ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); + ably.connect(); + connectionWaiter.waitFor(ConnectionState.connected); + String originalConnectionId = ably.connection.id; /* prepare channels */ Channel attachedChannel = ably.channels.get(attachedChannelName); ChannelWaiter attachedChannelWaiter = new ChannelWaiter(attachedChannel); attachedChannel.attach(); attachedChannelWaiter.waitFor(ChannelState.attached); + attachedChannel.publish("chat", "message"); Channel suspendedChannel = ably.channels.get(suspendedChannelName); - suspendedChannel.state = ChannelState.suspended; ChannelWaiter suspendedChannelWaiter = new ChannelWaiter(suspendedChannel); + suspendedChannel.attach(); + suspendedChannelWaiter.waitFor(ChannelState.attached); + suspendedChannel.setSuspended(null, true); + suspendedChannelWaiter.waitFor(ChannelState.suspended); - final boolean[] suspendedStateReached = new boolean[2]; - final boolean[] attachingStateReached = new boolean[2]; - final boolean[] attachedStateReached = new boolean[2]; - final boolean[] resumedFlag = new boolean[]{true, true}; - attachedChannel.on(new ChannelStateListener() { - @Override - public void onChannelStateChanged(ChannelStateChange stateChange) { - switch(stateChange.current) { - case suspended: - suspendedStateReached[0] = true; - break; - case attaching: - attachingStateReached[0] = true; - break; - case attached: - attachedStateReached[0] = true; - resumedFlag[0] = stateChange.resumed; - break; - default: - break; - } - } - }); - suspendedChannel.on(new ChannelStateListener() { - @Override - public void onChannelStateChanged(ChannelStateChange stateChange) { - switch(stateChange.current) { - case attaching: - attachingStateReached[1] = true; - break; - case attached: - attachedStateReached[1] = true; - resumedFlag[1] = stateChange.resumed; - break; - default: - break; - } - } - }); - - /* disconnect, and sabotage the resume */ - String originalConnectionId = ably.connection.id; - ably.connection.key = "_____!ably___test_fake-key____"; - ably.connection.id = "ably___tes"; - ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); + assertEquals(ably.connection.connectionManager.msgSerial, 1); /* suppress automatic retries by the connection manager */ try { @@ -1741,36 +1667,40 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Verify disconnected state is reached", ConnectionState.disconnected, ably.connection.state); - /* wait */ - try { Thread.sleep(2000L); } catch(InterruptedException e) {} + /* disconnect, and sabotage the resume */ + ably.connection.key = "_____!ably___test_fake-key____"; /* wait for connection to be reestablished */ System.out.println("channel_resume_lost_continuity: initiating reconnection (resume)"); ably.connection.connect(); - connectionWaiter.waitFor(ConnectionState.connected); - /* verify a new connection was assigned */ + + ErrorInfo resumeError = connectionWaiter.waitFor(ConnectionState.connected); + assertNotNull(resumeError); + assertTrue(resumeError.message.contains("Invalid connection key")); + assertSame(resumeError, ably.connection.connectionManager.getStateErrorInfo()); assertNotEquals("A new connection was created", originalConnectionId, ably.connection.id); + assertEquals(ably.connection.connectionManager.msgSerial, 0); - /* previously suspended channel should transition to attaching, then to attached */ + attachedChannelWaiter.waitFor(ChannelState.attaching, ChannelState.attached); suspendedChannelWaiter.waitFor(ChannelState.attached); - /* previously attached channel should remain attached */ - attachedChannelWaiter.waitFor(ChannelState.attached); + assertFalse("Verify channel was not suspended", + attachedChannelWaiter.hasStates(ChannelState.suspended)); + assertTrue("Verify channel was attaching and attached", + attachedChannelWaiter.hasFinalStates(ChannelState.attaching, ChannelState.attached)); + + ChannelStateListener.ChannelStateChange stateChange = attachedChannelWaiter.getLastStateChange(); + assertEquals(ChannelState.attached, stateChange.current); + assertEquals(ChannelState.attaching, stateChange.previous); + + assertTrue("Verify channel was attaching", + suspendedChannelWaiter.hasFinalStates(ChannelState.attaching, ChannelState.attached)); + + stateChange = suspendedChannelWaiter.getLastStateChange(); + assertEquals(ChannelState.attached, stateChange.current); + assertEquals(ChannelState.attaching, stateChange.previous); - /* - * Verify each channel undergoes relevant events: - * - previously attached channel does attaching, attached, without visiting suspended; - * - previously suspended channel does attaching, attached - */ - assertFalse("Verify channel was not suspended", suspendedStateReached[0]); - assertTrue("Verify channel was attaching", attachingStateReached[0]); - assertTrue("Verify channel was attached", attachedStateReached[0]); - assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[0]); - - assertTrue("Verify channel was attaching", attachingStateReached[1]); - assertTrue("Verify channel was attached", attachedStateReached[1]); - assertFalse("Verify resumed flag set false in ATTACHED event", resumedFlag[1]); } finally { if (ably != null) ably.close(); From d13051f35733896e78dcff98fdcbe1bb538d286f Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 30 Jan 2024 16:59:11 +0530 Subject: [PATCH 61/73] Fixed deltadecode failure recovery test --- lib/src/main/java/io/ably/lib/realtime/ChannelBase.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 7538789ad..616ac98f4 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -251,10 +251,11 @@ private void attachImpl(final boolean forceReattach, final CompletionListener li attachMessage.setFlags(options.getModeFlags()); } } - if(this.decodeFailureRecoveryInProgress) { - Log.v(TAG, "attach(); message decode recovery in progress."); - } attachMessage.channelSerial = properties.channelSerial; // RTL4c1 + if(this.decodeFailureRecoveryInProgress) { // RTL18c + Log.v(TAG, "attach(); message decode recovery in progress, setting last message channelserial"); + attachMessage.channelSerial = this.lastPayloadProtocolMessageChannelSerial; + } try { if (listener != null) { on(new ChannelStateCompletionListener(listener, ChannelState.attached, ChannelState.failed)); @@ -838,6 +839,7 @@ private void onMessage(final ProtocolMessage protocolMessage) { } lastPayloadMessageId = lastMessage.id; + lastPayloadProtocolMessageChannelSerial = protocolMessage.channelSerial; for (final Message msg : messages) { this.listeners.onMessage(msg); @@ -1340,6 +1342,7 @@ public void once(ChannelState state, ChannelStateListener listener) { */ private Set modes; private String lastPayloadMessageId; + private String lastPayloadProtocolMessageChannelSerial; private boolean decodeFailureRecoveryInProgress; private final DecodingContext decodingContext; } From 5449a9b7477e32396b9b8f527dd2bc5b47cc99be Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 31 Jan 2024 01:36:13 +0530 Subject: [PATCH 62/73] Fixed test for resume publish reenter with right message size --- .../io/ably/lib/test/realtime/RealtimePresenceTest.java | 2 +- .../java/io/ably/lib/test/realtime/RealtimeResumeTest.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java index 0bd2e7b57..84591f86f 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java @@ -1717,7 +1717,7 @@ public void onPresenceMessage(PresenceMessage message) { !receivedMessageStack.get(receivedMessageStack.size()-1).data.equals("Dolor sit!")) receivedMessageStack.wait(); } - } catch(InterruptedException e) {} + } catch(InterruptedException ignored) {} /* Validate that, *- we received specific actions diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java index 2895dc841..7a2024a56 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java @@ -29,9 +29,11 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -1062,13 +1064,13 @@ public void onConnectionStateChanged(ConnectionStateChange state) { System.out.println("presence_resume_test: sent message with client: "+presenceMessage.clientId +" " + " action:"+presenceMessage.action); } - assertEquals("Second round of messages has incorrect size", 9, transport.getSentPresenceMessages().size()); + assertEquals("Second round of messages has incorrect size", 6, transport.getSentPresenceMessages().size()); //make sure they were sent with correct client ids final Map sentPresenceMap = new HashMap<>(); for (PresenceMessage presenceMessage: transport.getSentPresenceMessages()){ sentPresenceMap.put(presenceMessage.clientId, presenceMessage); } - for (String client : clients) { + for (String client : Arrays.stream(clients).skip(3).collect(Collectors.toList())) { assertTrue("Client id isn't there:" + client, sentPresenceMap.containsKey(client)); } } From 98e1e8f72c5dfcf90b72df5a2106b536c69a0f13 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 31 Jan 2024 01:40:26 +0530 Subject: [PATCH 63/73] refactored test for resume publish re-enter --- .../ably/lib/test/realtime/RealtimeResumeTest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java index 7a2024a56..39eea60fb 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java @@ -1006,8 +1006,8 @@ public void onConnectionStateChanged(ConnectionStateChange state) { message.action == ProtocolMessage.Action.nack); //enter next 3 clients - for (int i = 0; i < 3; i++) { - senderChannel.presence.enterClient(clients[i+3],null,presenceCompletion.add()); + for (int i = 3; i < 6; i++) { + senderChannel.presence.enterClient(clients[i],null,presenceCompletion.add()); } final String firstConnectionId = ably.connection.id; @@ -1024,8 +1024,8 @@ public void onConnectionStateChanged(ConnectionStateChange state) { assertEquals("Disconnected state was not reached", ConnectionState.disconnected, ably.connection.state); //enter last 3 clients while disconnected - for (int i = 0; i < 3; i++) { - senderChannel.presence.enterClient(clients[i+6],null,presenceCompletion.add()); + for (int i = 6; i < 9; i++) { + senderChannel.presence.enterClient(clients[i],null,presenceCompletion.add()); } /* Wait for the connection to go stale, then reconnect */ @@ -1070,8 +1070,10 @@ public void onConnectionStateChanged(ConnectionStateChange state) { for (PresenceMessage presenceMessage: transport.getSentPresenceMessages()){ sentPresenceMap.put(presenceMessage.clientId, presenceMessage); } - for (String client : Arrays.stream(clients).skip(3).collect(Collectors.toList())) { - assertTrue("Client id isn't there:" + client, sentPresenceMap.containsKey(client)); + + for (int i = 3; i < 9; i++) { + assertTrue("Client id isn't there:" + clients[i], sentPresenceMap.containsKey(clients[i])); + senderChannel.presence.enterClient(clients[i],null,presenceCompletion.add()); } } } From 4fded6928dadcc19a54830a1a56a73e4612bce06 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 31 Jan 2024 02:07:19 +0530 Subject: [PATCH 64/73] refactored code, added a separate class for updating connectionmanager fields --- .../test/realtime/RealtimePresenceTest.java | 1 - .../lib/test/realtime/RealtimeResumeTest.java | 144 +++++++----------- 2 files changed, 58 insertions(+), 87 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java index 84591f86f..a11a8c339 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java @@ -1655,7 +1655,6 @@ public void onPresenceMessage(PresenceMessage message) { * state will have all messages sent once the channel attaches, and all listeners will be called. *

* - * @throws AblyException */ @Test public void realtime_presence_update_multiple_queued_messages() throws AblyException { diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java index 39eea60fb..4e1959874 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java @@ -13,6 +13,7 @@ import io.ably.lib.test.common.Helpers.MessageWaiter; import io.ably.lib.test.common.ParameterizedTest; import io.ably.lib.test.util.MockWebsocketFactory; +import io.ably.lib.transport.ConnectionManager; import io.ably.lib.types.AblyException; import io.ably.lib.types.ClientOptions; import io.ably.lib.types.ErrorInfo; @@ -29,11 +30,9 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -60,6 +59,8 @@ public void resume_none() { try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); ably = new AblyRealtime(opts); + ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); + connectionWaiter.waitFor(ConnectionState.connected); /* create and attach channel */ final Channel channel = ably.channels.get(channelName); @@ -68,21 +69,12 @@ public void resume_none() { (new ChannelWaiter(channel)).waitFor(ChannelState.attached); assertEquals("Verify attached state reached", channel.state, ChannelState.attached); - /* disconnect the connection, without closing, - /* suppressing automatic retries by the connection manager */ - System.out.println("Simulating dropped transport"); - try { - Method method = ably.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(ably.connection.connectionManager); - } catch (NoSuchMethodException|IllegalAccessException| InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } + new MutableConnectionManager(ably).disconnectAndSuppressRetries(); + connectionWaiter.waitFor(ConnectionState.disconnected); /* reconnect the rx connection */ ably.connection.connect(); System.out.println("Waiting for reconnection"); - ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); connectionWaiter.waitFor(ConnectionState.connected); assertEquals("Verify connected state is reached", ConnectionState.connected, ably.connection.state); @@ -528,14 +520,9 @@ public void resume_verify_publish() { * of the library, to simulate a dropped transport without * causing the connection itself to be disposed */ System.out.println("*** about to disconnect tx connection"); - /* suppress automatic retries by the connection manager */ - try { - Method method = ablyTx.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(ablyTx.connection.connectionManager); - } catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } + + new MutableConnectionManager(ablyTx).disconnectAndSuppressRetries(); + (new ConnectionWaiter(ablyTx.connection)).waitFor(ConnectionState.disconnected); /* wait */ try { Thread.sleep(2000L); } catch(InterruptedException ignored) {} @@ -754,15 +741,7 @@ public void resume_publish_resend_pending_messages_when_resume_is_successful() { final String connectionId = sender.connection.id; - /* suppress automatic retries by the connection manager and disconnect */ - try { - Method method = sender.connection.connectionManager.getClass().getDeclaredMethod( - "disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(sender.connection.connectionManager); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } + new MutableConnectionManager(sender).disconnectAndSuppressRetries(); (new ConnectionWaiter(sender.connection)).waitFor(ConnectionState.disconnected); sender.connection.connectionManager.requestState(ConnectionState.disconnected); @@ -828,28 +807,14 @@ public void resume_publish_resend_pending_messages_when_resume_failed() throws A try(AblyRealtime ably = new AblyRealtime(options)) { final long newTtl = 1000L; final long newIdleInterval = 1000L; - /* We want this greater than newTtl + newIdleInterval */ - final long waitInDisconnectedState = 3000L; - - ably.connection.on(ConnectionEvent.connected, new ConnectionStateListener() { - @Override - public void onConnectionStateChanged(ConnectionStateChange state) { - try { - Field connectionStateField = ably.connection.connectionManager.getClass().getDeclaredField("connectionStateTtl"); - connectionStateField.setAccessible(true); - connectionStateField.setLong(ably.connection.connectionManager, newTtl); - Field maxIdleField = ably.connection.connectionManager.getClass().getDeclaredField("maxIdleInterval"); - maxIdleField.setAccessible(true); - maxIdleField.setLong(ably.connection.connectionManager, newIdleInterval); - } catch (NoSuchFieldException | IllegalAccessException e) { - fail("Unexpected exception in checking connectionStateTtl"); - } - } - }); ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); connectionWaiter.waitFor(ConnectionState.connected); + MutableConnectionManager connectionManager = new MutableConnectionManager(ably); + connectionManager.setField("connectionStateTtl", newTtl); + connectionManager.setField("maxIdleInterval", newIdleInterval); + final Channel senderChannel = ably.channels.get(channelName); senderChannel.attach(); (new ChannelWaiter(senderChannel)).waitFor(ChannelState.attached); @@ -887,14 +852,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { final String firstConnectionId = ably.connection.id; - /* suppress automatic retries by the connection manager and disconnect */ - try { - Method method = ably.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(ably.connection.connectionManager); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } + connectionManager.disconnectAndSuppressRetries(); connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Disconnected state was not reached", ConnectionState.disconnected, ably.connection.state); @@ -905,6 +863,9 @@ public void onConnectionStateChanged(ConnectionStateChange state) { } //now let's unblock the ack nacks and reconnect mockWebsocketFactory.blockReceiveProcessing(message -> false); + + /* We want this greater than newTtl + newIdleInterval */ + final long waitInDisconnectedState = 3000L; /* Wait for the connection to go stale, then reconnect */ try { Thread.sleep(waitInDisconnectedState); @@ -935,6 +896,37 @@ public void onConnectionStateChanged(ConnectionStateChange state) { } } + static class MutableConnectionManager { + ConnectionManager connectionManager; + + public MutableConnectionManager(AblyRealtime ablyRealtime) { + this.connectionManager = ablyRealtime.connection.connectionManager; + } + + public void setField(String fieldName, long value) { + Field connectionStateField = null; + try { + connectionStateField = ConnectionManager.class.getDeclaredField(fieldName); + connectionStateField.setAccessible(true); + connectionStateField.setLong(connectionManager, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Unexpected exception in checking connectionStateTtl"); + } + } + + /** + * Suppress automatic retries by the connection manager and disconnect + */ + public void disconnectAndSuppressRetries() { + try { + Method method = ConnectionManager.class.getDeclaredMethod("disconnectAndSuppressRetries"); + method.setAccessible(true); + method.invoke(connectionManager); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + fail("Unexpected exception in suppressing retries"); + } + } + } /** * In case of resume failure verify that presence messages are resent @@ -950,34 +942,19 @@ public void resume_publish_reenter_when_resume_failed() throws AblyException { options.logLevel = Log.VERBOSE; options.realtimeRequestTimeout = 2000L; - /* We want this greater than newTtl + newIdleInterval */ - final long waitInDisconnectedState = 5000L; options.transportFactory = mockWebsocketFactory; try(AblyRealtime ably = new AblyRealtime(options)) { - final long newTtl = 1000L; - final long newIdleInterval = 1000L; - /* We want this greater than newTtl + newIdleInterval */ - ably.connection.on(ConnectionEvent.connected, new ConnectionStateListener() { - @Override - public void onConnectionStateChanged(ConnectionStateChange state) { - try { - Field connectionStateField = ably.connection.connectionManager.getClass(). - getDeclaredField("connectionStateTtl"); - connectionStateField.setAccessible(true); - connectionStateField.setLong(ably.connection.connectionManager, newTtl); - Field maxIdleField = ably.connection.connectionManager.getClass(). - getDeclaredField("maxIdleInterval"); - maxIdleField.setAccessible(true); - maxIdleField.setLong(ably.connection.connectionManager, newIdleInterval); - } catch (NoSuchFieldException | IllegalAccessException e) { - fail("Unexpected exception in checking connectionStateTtl"); - } - } - }); ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); connectionWaiter.waitFor(ConnectionState.connected); + final long newTtl = 1000L; + final long newIdleInterval = 1000L; + + MutableConnectionManager connectionManager = new MutableConnectionManager(ably); + connectionManager.setField("connectionStateTtl", newTtl); + connectionManager.setField("maxIdleInterval", newIdleInterval); + final Channel senderChannel = ably.channels.get(channelName); senderChannel.attach(); (new ChannelWaiter(senderChannel)).waitFor(ChannelState.attached); @@ -1012,14 +989,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { final String firstConnectionId = ably.connection.id; - /* suppress automatic retries by the connection manager and disconnect */ - try { - Method method = ably.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(ably.connection.connectionManager); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } + connectionManager.disconnectAndSuppressRetries(); connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Disconnected state was not reached", ConnectionState.disconnected, ably.connection.state); @@ -1028,6 +998,8 @@ public void onConnectionStateChanged(ConnectionStateChange state) { senderChannel.presence.enterClient(clients[i],null,presenceCompletion.add()); } + /* We want this greater than newTtl + newIdleInterval */ + final long waitInDisconnectedState = 5000L; /* Wait for the connection to go stale, then reconnect */ try { Thread.sleep(waitInDisconnectedState); From c93f89d091aeaa71c27cbc3f771ea28c67add1a4 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 31 Jan 2024 10:20:37 +0530 Subject: [PATCH 65/73] Fixed checkstyle issues for integration tests --- .../java/io/ably/lib/test/common/Helpers.java | 9 ++++++++- .../test/realtime/RealtimeChannelTest.java | 19 +++++++++++++++---- .../realtime/RealtimeConnectFailTest.java | 1 - .../lib/test/realtime/RealtimeResumeTest.java | 5 +---- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/common/Helpers.java b/lib/src/test/java/io/ably/lib/test/common/Helpers.java index 89a0a1bf7..c3862d1d2 100644 --- a/lib/src/test/java/io/ably/lib/test/common/Helpers.java +++ b/lib/src/test/java/io/ably/lib/test/common/Helpers.java @@ -29,8 +29,15 @@ import io.ably.lib.debug.DebugOptions.RawProtocolListener; import io.ably.lib.http.HttpCore; import io.ably.lib.http.HttpUtils; -import io.ably.lib.realtime.*; +import io.ably.lib.realtime.Channel; import io.ably.lib.realtime.Channel.MessageListener; +import io.ably.lib.realtime.ChannelEvent; +import io.ably.lib.realtime.ChannelState; +import io.ably.lib.realtime.ChannelStateListener; +import io.ably.lib.realtime.CompletionListener; +import io.ably.lib.realtime.Connection; +import io.ably.lib.realtime.ConnectionState; +import io.ably.lib.realtime.ConnectionStateListener; import io.ably.lib.realtime.Presence.PresenceListener; import io.ably.lib.transport.ConnectionManager; import io.ably.lib.types.AblyException; diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index 8341acd54..447607571 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -25,19 +25,30 @@ import io.ably.lib.types.Message; import io.ably.lib.types.ProtocolMessage; import io.ably.lib.util.Log; - -import io.ably.lib.util.StringUtils; import org.hamcrest.Matchers; import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class RealtimeChannelTest extends ParameterizedTest { diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java index 021d4ec22..02a1d07d5 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeConnectFailTest.java @@ -27,7 +27,6 @@ import org.junit.Test; import org.junit.rules.Timeout; -import java.lang.reflect.Field; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java index 4e1959874..179106dad 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java @@ -4,9 +4,7 @@ import io.ably.lib.realtime.AblyRealtime; import io.ably.lib.realtime.Channel; import io.ably.lib.realtime.ChannelState; -import io.ably.lib.realtime.ConnectionEvent; import io.ably.lib.realtime.ConnectionState; -import io.ably.lib.realtime.ConnectionStateListener; import io.ably.lib.test.common.Helpers.ChannelWaiter; import io.ably.lib.test.common.Helpers.CompletionSet; import io.ably.lib.test.common.Helpers.ConnectionWaiter; @@ -899,7 +897,7 @@ public void resume_publish_resend_pending_messages_when_resume_failed() throws A static class MutableConnectionManager { ConnectionManager connectionManager; - public MutableConnectionManager(AblyRealtime ablyRealtime) { + MutableConnectionManager(AblyRealtime ablyRealtime) { this.connectionManager = ablyRealtime.connection.connectionManager; } @@ -1045,7 +1043,6 @@ public void resume_publish_reenter_when_resume_failed() throws AblyException { for (int i = 3; i < 9; i++) { assertTrue("Client id isn't there:" + clients[i], sentPresenceMap.containsKey(clients[i])); - senderChannel.presence.enterClient(clients[i],null,presenceCompletion.add()); } } } From e93f0ca9a6b0f7b8bcd3219a08df21214a5767e2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 31 Jan 2024 16:05:23 +0530 Subject: [PATCH 66/73] Moved mutableConnection manager under helpers --- .../java/io/ably/lib/test/common/Helpers.java | 37 +++++++++++++++ .../test/realtime/ConnectionManagerTest.java | 18 +------ .../test/realtime/RealtimeChannelTest.java | 21 +-------- .../lib/test/realtime/RealtimeResumeTest.java | 47 +++---------------- 4 files changed, 47 insertions(+), 76 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/common/Helpers.java b/lib/src/test/java/io/ably/lib/test/common/Helpers.java index c3862d1d2..dd3adf10e 100644 --- a/lib/src/test/java/io/ably/lib/test/common/Helpers.java +++ b/lib/src/test/java/io/ably/lib/test/common/Helpers.java @@ -1,5 +1,8 @@ package io.ably.lib.test.common; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; @@ -29,6 +32,7 @@ import io.ably.lib.debug.DebugOptions.RawProtocolListener; import io.ably.lib.http.HttpCore; import io.ably.lib.http.HttpUtils; +import io.ably.lib.realtime.AblyRealtime; import io.ably.lib.realtime.Channel; import io.ably.lib.realtime.Channel.MessageListener; import io.ably.lib.realtime.ChannelEvent; @@ -62,6 +66,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class Helpers { @@ -403,6 +408,38 @@ public PresenceMessage contains(String clientId, String connectionId, PresenceMe } } + public static class MutableConnectionManager { + ConnectionManager connectionManager; + + public MutableConnectionManager(AblyRealtime ablyRealtime) { + this.connectionManager = ablyRealtime.connection.connectionManager; + } + + public void setField(String fieldName, long value) { + Field connectionStateField = null; + try { + connectionStateField = ConnectionManager.class.getDeclaredField(fieldName); + connectionStateField.setAccessible(true); + connectionStateField.setLong(connectionManager, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Unexpected exception in checking connectionStateTtl"); + } + } + + /** + * Suppress automatic retries by the connection manager and disconnect + */ + public void disconnectAndSuppressRetries() { + try { + Method method = ConnectionManager.class.getDeclaredMethod("disconnectAndSuppressRetries"); + method.setAccessible(true); + method.invoke(connectionManager); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + fail("Unexpected exception in suppressing retries"); + } + } + } + /** * A class that listens for state change events on a connection. * @author paddy diff --git a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java index a164120a0..97910f0e8 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java @@ -618,14 +618,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { connectionWaiter.waitFor(ConnectionState.connected); final String firstConnectionId = ably.connection.id; - /* suppress automatic retries by the connection manager and disconnect */ - try { - Method method = ably.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(ably.connection.connectionManager); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } + new Helpers.MutableConnectionManager(ably).disconnectAndSuppressRetries(); connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Disconnected state was not reached", ConnectionState.disconnected, ably.connection.state); @@ -726,14 +719,7 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { attachedChannel.attach(); attachedChannelWaiter.waitFor(ChannelState.attached); - /* suppress automatic retries by the connection manager and disconnect */ - try { - Method method = ably.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(ably.connection.connectionManager); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } + new Helpers.MutableConnectionManager(ably).disconnectAndSuppressRetries(); connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Disconnected state was not reached", ConnectionState.disconnected, ably.connection.state); diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index 447607571..ae3c08438 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -1582,16 +1582,7 @@ public void channel_valid_resume_reattach_channels() throws AblyException { assertEquals(ably.connection.connectionManager.msgSerial, 1); - /* disconnect, and sabotage the resume */ - /* suppress automatic retries by the connection manager */ - try { - Method method = ably.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(ably.connection.connectionManager); - } catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } - + new Helpers.MutableConnectionManager(ably).disconnectAndSuppressRetries(); connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Verify disconnected state is reached", ConnectionState.disconnected, ably.connection.state); @@ -1666,15 +1657,7 @@ public void channel_invalid_resume_reattach_channels() throws AblyException { assertEquals(ably.connection.connectionManager.msgSerial, 1); - /* suppress automatic retries by the connection manager */ - try { - Method method = ably.connection.connectionManager.getClass().getDeclaredMethod("disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(ably.connection.connectionManager); - } catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } - + new Helpers.MutableConnectionManager(ably).disconnectAndSuppressRetries(); connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Verify disconnected state is reached", ConnectionState.disconnected, ably.connection.state); diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java index 179106dad..3a882cf18 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java @@ -5,13 +5,13 @@ import io.ably.lib.realtime.Channel; import io.ably.lib.realtime.ChannelState; import io.ably.lib.realtime.ConnectionState; +import io.ably.lib.test.common.Helpers; import io.ably.lib.test.common.Helpers.ChannelWaiter; import io.ably.lib.test.common.Helpers.CompletionSet; import io.ably.lib.test.common.Helpers.ConnectionWaiter; import io.ably.lib.test.common.Helpers.MessageWaiter; import io.ably.lib.test.common.ParameterizedTest; import io.ably.lib.test.util.MockWebsocketFactory; -import io.ably.lib.transport.ConnectionManager; import io.ably.lib.types.AblyException; import io.ably.lib.types.ClientOptions; import io.ably.lib.types.ErrorInfo; @@ -25,9 +25,6 @@ import org.junit.Test; import org.junit.rules.Timeout; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -67,7 +64,7 @@ public void resume_none() { (new ChannelWaiter(channel)).waitFor(ChannelState.attached); assertEquals("Verify attached state reached", channel.state, ChannelState.attached); - new MutableConnectionManager(ably).disconnectAndSuppressRetries(); + new Helpers.MutableConnectionManager(ably).disconnectAndSuppressRetries(); connectionWaiter.waitFor(ConnectionState.disconnected); /* reconnect the rx connection */ @@ -519,7 +516,7 @@ public void resume_verify_publish() { * causing the connection itself to be disposed */ System.out.println("*** about to disconnect tx connection"); - new MutableConnectionManager(ablyTx).disconnectAndSuppressRetries(); + new Helpers.MutableConnectionManager(ablyTx).disconnectAndSuppressRetries(); (new ConnectionWaiter(ablyTx.connection)).waitFor(ConnectionState.disconnected); /* wait */ @@ -739,7 +736,7 @@ public void resume_publish_resend_pending_messages_when_resume_is_successful() { final String connectionId = sender.connection.id; - new MutableConnectionManager(sender).disconnectAndSuppressRetries(); + new Helpers.MutableConnectionManager(sender).disconnectAndSuppressRetries(); (new ConnectionWaiter(sender.connection)).waitFor(ConnectionState.disconnected); sender.connection.connectionManager.requestState(ConnectionState.disconnected); @@ -809,7 +806,7 @@ public void resume_publish_resend_pending_messages_when_resume_failed() throws A ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); connectionWaiter.waitFor(ConnectionState.connected); - MutableConnectionManager connectionManager = new MutableConnectionManager(ably); + Helpers.MutableConnectionManager connectionManager = new Helpers.MutableConnectionManager(ably); connectionManager.setField("connectionStateTtl", newTtl); connectionManager.setField("maxIdleInterval", newIdleInterval); @@ -894,38 +891,6 @@ public void resume_publish_resend_pending_messages_when_resume_failed() throws A } } - static class MutableConnectionManager { - ConnectionManager connectionManager; - - MutableConnectionManager(AblyRealtime ablyRealtime) { - this.connectionManager = ablyRealtime.connection.connectionManager; - } - - public void setField(String fieldName, long value) { - Field connectionStateField = null; - try { - connectionStateField = ConnectionManager.class.getDeclaredField(fieldName); - connectionStateField.setAccessible(true); - connectionStateField.setLong(connectionManager, value); - } catch (NoSuchFieldException | IllegalAccessException e) { - fail("Unexpected exception in checking connectionStateTtl"); - } - } - - /** - * Suppress automatic retries by the connection manager and disconnect - */ - public void disconnectAndSuppressRetries() { - try { - Method method = ConnectionManager.class.getDeclaredMethod("disconnectAndSuppressRetries"); - method.setAccessible(true); - method.invoke(connectionManager); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - fail("Unexpected exception in suppressing retries"); - } - } - } - /** * In case of resume failure verify that presence messages are resent * */ @@ -949,7 +914,7 @@ public void resume_publish_reenter_when_resume_failed() throws AblyException { final long newTtl = 1000L; final long newIdleInterval = 1000L; - MutableConnectionManager connectionManager = new MutableConnectionManager(ably); + Helpers.MutableConnectionManager connectionManager = new Helpers.MutableConnectionManager(ably); connectionManager.setField("connectionStateTtl", newTtl); connectionManager.setField("maxIdleInterval", newIdleInterval); From 2436f676b4d094483a5bd11beff30914060544ec Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 31 Jan 2024 17:45:19 +0530 Subject: [PATCH 67/73] Updating complex tests where reflection is used --- .../java/io/ably/lib/test/common/Helpers.java | 16 +++++++-- .../test/realtime/ConnectionManagerTest.java | 36 ++++++++----------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/common/Helpers.java b/lib/src/test/java/io/ably/lib/test/common/Helpers.java index dd3adf10e..e4d24fd37 100644 --- a/lib/src/test/java/io/ably/lib/test/common/Helpers.java +++ b/lib/src/test/java/io/ably/lib/test/common/Helpers.java @@ -416,16 +416,26 @@ public MutableConnectionManager(AblyRealtime ablyRealtime) { } public void setField(String fieldName, long value) { - Field connectionStateField = null; try { - connectionStateField = ConnectionManager.class.getDeclaredField(fieldName); + Field connectionStateField = ConnectionManager.class.getDeclaredField(fieldName); connectionStateField.setAccessible(true); connectionStateField.setLong(connectionManager, value); } catch (NoSuchFieldException | IllegalAccessException e) { - fail("Unexpected exception in checking connectionStateTtl"); + fail("Error updating " + fieldName + " error occurred" + e); } } + public long getField(String fieldName) { + try { + Field connectionStateField = ConnectionManager.class.getDeclaredField(fieldName); + connectionStateField.setAccessible(true); + return connectionStateField.getLong(connectionManager); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error accessing " + fieldName + " error occurred" + e); + } + return 0; + } + /** * Suppress automatic retries by the connection manager and disconnect */ diff --git a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java index 97910f0e8..c1183b69d 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java @@ -529,29 +529,23 @@ public void run() { @Test public void connection_details_has_ttl() throws AblyException { ClientOptions opts = createOptions(testVars.keys[0].keyStr); + opts.autoConnect = false; try (AblyRealtime ably = new AblyRealtime(opts)) { - final boolean[] callbackWasRun = new boolean[1]; - ably.connection.on(ConnectionEvent.connected, new ConnectionStateListener() { - @Override - public void onConnectionStateChanged(ConnectionStateChange state) { - synchronized(callbackWasRun) { - callbackWasRun[0] = true; - try { - Field field = ably.connection.connectionManager.getClass().getDeclaredField("connectionStateTtl"); - field.setAccessible(true); - assertEquals("Verify connectionStateTtl has the default value", field.get(ably.connection.connectionManager), 120000L); - } catch (NoSuchFieldException|IllegalAccessException e) { - fail("Unexpected exception in checking connectionStateTtl"); - } - callbackWasRun.notify(); - } - } - }); + Helpers.MutableConnectionManager connectionManager = new Helpers.MutableConnectionManager(ably); - synchronized (callbackWasRun) { - try { callbackWasRun.wait(); } catch(InterruptedException ie) {} - assertTrue("Connected callback was not run", callbackWasRun[0]); - } + // connStateTtl set to default value + long connStateTtl = connectionManager.getField("connectionStateTtl"); + assertEquals(Defaults.connectionStateTtl, connStateTtl); + + connectionManager.setField("connectionStateTtl", 8000L); + long oldConnStateTtl = connectionManager.getField("connectionStateTtl"); + assertEquals(8000L, oldConnStateTtl); + + ably.connect(); + new ConnectionWaiter(ably.connection).waitFor(ConnectionState.connected); + long newConnStateTtl = connectionManager.getField("connectionStateTtl"); + // connStateTtl set by server to 120s + assertEquals(120000L, newConnStateTtl); } } From e791f8f273a36070130133d8a29a6b3ee388829e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 31 Jan 2024 18:37:15 +0530 Subject: [PATCH 68/73] refactored tests with easier test helper implementation --- .../java/io/ably/lib/test/common/Helpers.java | 4 +- .../test/realtime/ConnectionManagerTest.java | 106 ++++++------------ .../test/realtime/RealtimeChannelTest.java | 2 - 3 files changed, 34 insertions(+), 78 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/common/Helpers.java b/lib/src/test/java/io/ably/lib/test/common/Helpers.java index e4d24fd37..90473b4df 100644 --- a/lib/src/test/java/io/ably/lib/test/common/Helpers.java +++ b/lib/src/test/java/io/ably/lib/test/common/Helpers.java @@ -421,7 +421,7 @@ public void setField(String fieldName, long value) { connectionStateField.setAccessible(true); connectionStateField.setLong(connectionManager, value); } catch (NoSuchFieldException | IllegalAccessException e) { - fail("Error updating " + fieldName + " error occurred" + e); + fail("Failed updating " + fieldName + " with error " + e); } } @@ -431,7 +431,7 @@ public long getField(String fieldName) { connectionStateField.setAccessible(true); return connectionStateField.getLong(connectionManager); } catch (NoSuchFieldException | IllegalAccessException e) { - fail("Error accessing " + fieldName + " error occurred" + e); + fail("Failed accessing " + fieldName + " with error " + e); } return 0; } diff --git a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java index c1183b69d..1a9cf7325 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java @@ -35,10 +35,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -46,6 +43,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; @@ -557,23 +555,17 @@ public void connection_is_closed_after_max_idle_interval() throws AblyException ClientOptions opts = createOptions(testVars.keys[0].keyStr); opts.realtimeRequestTimeout = 2000; try(AblyRealtime ably = new AblyRealtime(opts)) { - final long newIdleInterval = 500L; - - // When we connect, we set the max idle interval to be very small - ably.connection.on(ConnectionEvent.connected, state -> { - try { - Field maxIdleField = ably.connection.connectionManager.getClass().getDeclaredField("maxIdleInterval"); - maxIdleField.setAccessible(true); - maxIdleField.setLong(ably.connection.connectionManager, newIdleInterval); - } catch (NoSuchFieldException | IllegalAccessException e) { - fail("Unexpected exception in checking connectionStateTtl"); - } - }); // The original max idle interval we receive from the server is 15s. // We should wait for this, plus a tiny bit extra (as we set the new idle interval to be very low // after connecting) to make sure that the connection is disconnected ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); + connectionWaiter.waitFor(ConnectionState.connected); + + // When we connect, we set the max idle interval to be very small + Helpers.MutableConnectionManager connectionManager = new Helpers.MutableConnectionManager(ably); + connectionManager.setField("maxIdleInterval", 500L); + assertTrue(connectionWaiter.waitFor(ConnectionState.disconnected, 1, 25000)); } } @@ -587,39 +579,25 @@ public void connection_has_new_id_when_reconnecting_after_statettl_plus_idleinte ClientOptions opts = createOptions(testVars.keys[0].keyStr); opts.realtimeRequestTimeout = 2000L; try(AblyRealtime ably = new AblyRealtime(opts)) { - final long newTtl = 1000L; - final long newIdleInterval = 1000L; /* We want this greater than newTtl + newIdleInterval */ final long waitInDisconnectedState = 3000L; - ably.connection.on(ConnectionEvent.connected, new ConnectionStateListener() { - @Override - public void onConnectionStateChanged(ConnectionStateChange state) { - try { - Field connectionStateField = ably.connection.connectionManager.getClass().getDeclaredField("connectionStateTtl"); - connectionStateField.setAccessible(true); - connectionStateField.setLong(ably.connection.connectionManager, newTtl); - Field maxIdleField = ably.connection.connectionManager.getClass().getDeclaredField("maxIdleInterval"); - maxIdleField.setAccessible(true); - maxIdleField.setLong(ably.connection.connectionManager, newIdleInterval); - } catch (NoSuchFieldException | IllegalAccessException e) { - fail("Unexpected exception in checking connectionStateTtl"); - } - } - }); - ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); connectionWaiter.waitFor(ConnectionState.connected); final String firstConnectionId = ably.connection.id; - new Helpers.MutableConnectionManager(ably).disconnectAndSuppressRetries(); + Helpers.MutableConnectionManager connectionManager = new Helpers.MutableConnectionManager(ably); + connectionManager.setField("connectionStateTtl", 1000L); + connectionManager.setField("maxIdleInterval", 1000L); + + connectionManager.disconnectAndSuppressRetries(); connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Disconnected state was not reached", ConnectionState.disconnected, ably.connection.state); /* Wait for the connection to go stale, then reconnect */ try { Thread.sleep(waitInDisconnectedState); - } catch (InterruptedException e) { + } catch (InterruptedException ignored) { } ably.connection.connect(); connectionWaiter.waitFor(ConnectionState.connected); @@ -662,65 +640,42 @@ public void connection_has_same_id_when_reconnecting_before_statettl_plus_idlein public void channels_are_reattached_after_reconnecting_when_statettl_plus_idleinterval_has_passed() throws AblyException { ClientOptions opts = createOptions(testVars.keys[0].keyStr); try(AblyRealtime ably = new AblyRealtime(opts)) { - final long newTtl = 1000L; - final long newIdleInterval = 1000L; /* We want this greater than newTtl + newIdleInterval */ final long waitInDisconnectedState = 3000L; - final List attachedChannelHistory = new ArrayList(); - final List expectedAttachedChannelHistory = Arrays.asList("attaching", "attached", "attaching", "attached"); - final List suspendedChannelHistory = new ArrayList(); - final List expectedSuspendedChannelHistory = Arrays.asList("attaching", "attached"); - ably.connection.on(ConnectionEvent.connected, new ConnectionStateListener() { - @Override - public void onConnectionStateChanged(ConnectionStateChange state) { - try { - Field connectionStateField = ably.connection.connectionManager.getClass().getDeclaredField("connectionStateTtl"); - connectionStateField.setAccessible(true); - connectionStateField.setLong(ably.connection.connectionManager, newTtl); - Field maxIdleField = ably.connection.connectionManager.getClass().getDeclaredField("maxIdleInterval"); - maxIdleField.setAccessible(true); - maxIdleField.setLong(ably.connection.connectionManager, newIdleInterval); - } catch (NoSuchFieldException | IllegalAccessException e) { - fail("Unexpected exception in checking connectionStateTtl"); - } - } - }); + final ChannelState[] expectedAttachedChannelHistory = new ChannelState[]{ + ChannelState.attaching, ChannelState.attached, ChannelState.attaching, ChannelState.attached}; - ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); + final ChannelState[] expectedSuspendedChannelHistory = new ChannelState[]{ + ChannelState.attaching, ChannelState.attached}; + + ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); connectionWaiter.waitFor(ConnectionState.connected); final String firstConnectionId = ably.connection.id; + Helpers.MutableConnectionManager connectionManager = new Helpers.MutableConnectionManager(ably); + connectionManager.setField("connectionStateTtl", 1000L); + connectionManager.setField("maxIdleInterval", 1000L); + /* Prepare channels */ final Channel attachedChannel = ably.channels.get("test-reattach-after-ttl" + testParams.name); ChannelWaiter attachedChannelWaiter = new Helpers.ChannelWaiter(attachedChannel); - attachedChannel.on(new ChannelStateListener() { - @Override - public void onChannelStateChanged(ChannelStateChange stateChange) { - attachedChannelHistory.add(stateChange.current.name()); - } - }); + final Channel suspendedChannel = ably.channels.get("test-reattach-suspended-after-ttl" + testParams.name); suspendedChannel.state = ChannelState.suspended; - suspendedChannel.on(new ChannelStateListener() { - @Override - public void onChannelStateChanged(ChannelStateChange stateChange) { - suspendedChannelHistory.add(stateChange.current.name()); - } - }); ChannelWaiter suspendedChannelWaiter = new Helpers.ChannelWaiter(suspendedChannel); /* attach first channel and wait for it to be attached */ attachedChannel.attach(); attachedChannelWaiter.waitFor(ChannelState.attached); - new Helpers.MutableConnectionManager(ably).disconnectAndSuppressRetries(); + connectionManager.disconnectAndSuppressRetries(); connectionWaiter.waitFor(ConnectionState.disconnected); assertEquals("Disconnected state was not reached", ConnectionState.disconnected, ably.connection.state); /* Wait for the connection to go stale, then reconnect */ try { Thread.sleep(waitInDisconnectedState); - } catch (InterruptedException e) { + } catch (InterruptedException ignored) { } ably.connection.connect(); connectionWaiter.waitFor(ConnectionState.connected); @@ -734,15 +689,18 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { attachedChannel.once(ChannelEvent.attached, new ChannelStateListener() { @Override public void onChannelStateChanged(ChannelStateChange stateChange) { - assertEquals("Resumed is true and should be false", stateChange.resumed, false); + assertFalse("Resumed is true and should be false", stateChange.resumed); } }); /* Wait for both channels to reattach and verify state histories match the expected ones */ attachedChannelWaiter.waitFor(ChannelState.attached); suspendedChannelWaiter.waitFor(ChannelState.attached); - assertEquals("Attached channel histories do not match", attachedChannelHistory, expectedAttachedChannelHistory); - assertEquals("Suspended channel histories do not match", suspendedChannelHistory, expectedSuspendedChannelHistory); + assertTrue("Attached channel histories do not match", + attachedChannelWaiter.hasFinalStates(expectedAttachedChannelHistory)); + + assertTrue("Suspended channel histories do not match", + suspendedChannelWaiter.hasFinalStates(expectedSuspendedChannelHistory)); } } diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index ae3c08438..a3a7b30cf 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -29,8 +29,6 @@ import org.junit.Ignore; import org.junit.Test; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; From 68a09449f299ce3d6f53af342a2799a113d618d4 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Sun, 4 Feb 2024 20:14:10 +0530 Subject: [PATCH 69/73] Refactored ably-java tests, removed unnecessary callbacks --- .../java/io/ably/lib/test/common/Helpers.java | 24 ++++++---- .../lib/test/realtime/RealtimeAuthTest.java | 48 +++++++++---------- .../realtime/RealtimeChannelHistoryTest.java | 19 +++----- .../lib/test/realtime/RealtimeResumeTest.java | 37 ++++++-------- 4 files changed, 59 insertions(+), 69 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/common/Helpers.java b/lib/src/test/java/io/ably/lib/test/common/Helpers.java index 90473b4df..32033acc4 100644 --- a/lib/src/test/java/io/ably/lib/test/common/Helpers.java +++ b/lib/src/test/java/io/ably/lib/test/common/Helpers.java @@ -477,7 +477,7 @@ public synchronized ErrorInfo waitFor(ConnectionState state) { while (currentState() != state) { try { wait(); - } catch (InterruptedException e) { + } catch (InterruptedException ignored) { } } Log.d(TAG, "waitFor done: state=" + targetStateName + ")"); @@ -493,8 +493,8 @@ public synchronized void waitFor(ConnectionState state, int count) { Log.d(TAG, "waitFor(state=" + state.getConnectionEvent().name() + ", count=" + count + ")"); while(getStateCount(state) < count) - try { wait(); } catch(InterruptedException e) {} - Log.d(TAG, "waitFor done: state=" + latestChange.current.getConnectionEvent().name() + ", count=" + getStateCount(state) + ")"); + try { wait(); } catch(InterruptedException ignored) {} + Log.d(TAG, "waitFor done: state=" + lastStateChange().current.getConnectionEvent().name() + ", count=" + getStateCount(state) + ")"); } /** @@ -511,7 +511,7 @@ public synchronized boolean waitFor(ConnectionState state, int count, long time) long remaining = time; while(getStateCount(state) < count && remaining > 0) { Log.d(TAG, "waitFor(state=" + state.getConnectionEvent().name() + ", waiting for=" + remaining + ")"); - try { wait(remaining); } catch(InterruptedException e) {} + try { wait(remaining); } catch(InterruptedException ignored) {} remaining = targetTime - System.currentTimeMillis(); } int stateCount = getStateCount(state); @@ -552,7 +552,7 @@ public synchronized void reset() { @Override public void onConnectionStateChanged(ConnectionStateListener.ConnectionStateChange state) { synchronized(this) { - latestChange = state; + stateChanges.add(state); reason = state.reason; Counter counter = stateCounts.get(state.current); if(counter == null) stateCounts.put(state.current, (counter = new Counter())); counter.incr(); @@ -573,15 +573,23 @@ private synchronized int getStateCount(ConnectionState state) { } private synchronized ConnectionState currentState() { - return latestChange == null ? connection.state : latestChange.current; + ConnectionStateChange stateChange = lastStateChange(); + return stateChange == null ? connection.state : stateChange.current; + } + + public synchronized ConnectionStateChange lastStateChange() { + if (stateChanges.size() == 0) { + return null; + } + return stateChanges.get(stateChanges.size() -1); } /** * Internal */ - private Connection connection; + private final Connection connection; private ErrorInfo reason; - private ConnectionStateChange latestChange; + private final List stateChanges = new ArrayList<>(); private Map stateCounts; private static final String TAG = ConnectionWaiter.class.getName(); } diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeAuthTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeAuthTest.java index c8950eed9..d50325d0b 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeAuthTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeAuthTest.java @@ -4,7 +4,6 @@ import io.ably.lib.realtime.AblyRealtime; import io.ably.lib.realtime.Channel; import io.ably.lib.realtime.ChannelState; -import io.ably.lib.realtime.ConnectionEvent; import io.ably.lib.realtime.ConnectionState; import io.ably.lib.realtime.ConnectionStateListener; import io.ably.lib.rest.AblyRest; @@ -147,7 +146,8 @@ public void realtime_connection_with_auth_url_in_query_string_connects() { * Spec: RSA4d, RSA4d1 */ @Test - public void auth_client_fails_authorize_server_forbidden() { + public void auth_client_fails() { + AblyRealtime ablyRealtime = null; try { /* init ably for token */ ClientOptions optsForToken = createOptions(testVars.keys[0].keyStr); @@ -163,25 +163,13 @@ public void auth_client_fails_authorize_server_forbidden() { opts.authUrl = "https://echo.ably.io/respondwith"; opts.authParams = new Param[]{ new Param("status", 403)}; - final AblyRealtime ablyRealtime = new AblyRealtime(opts); + ablyRealtime = new AblyRealtime(opts); ablyRealtime.connection.connect(); /* wait for connected state */ Helpers.ConnectionWaiter connectionWaiter = new Helpers.ConnectionWaiter(ablyRealtime.connection); connectionWaiter.waitFor(ConnectionState.connected); - /* create listener for ConnectionEvent.failed */ - ablyRealtime.connection.once(ConnectionEvent.failed, new ConnectionStateListener() { - @Override - public void onConnectionStateChanged(ConnectionStateChange stateChange) { - /* assert that state changes correctly */ - assertEquals(ConnectionState.connected, stateChange.previous); - assertEquals(80019, stateChange.reason.code); - assertEquals(80019, ablyRealtime.connection.reason.code); - assertEquals(403, ablyRealtime.connection.reason.statusCode); - } - }); - try { opts.tokenDetails = null; /* try to authorize */ @@ -194,11 +182,21 @@ public void onConnectionStateChanged(ConnectionStateChange stateChange) { /* wait for failed state */ connectionWaiter.waitFor(ConnectionState.failed); + ConnectionStateListener.ConnectionStateChange lastStateChange = connectionWaiter.lastStateChange(); + assertEquals(ConnectionState.failed, lastStateChange.current); + assertEquals(80019, lastStateChange.reason.code); + assertEquals(403, lastStateChange.reason.statusCode); + assertEquals("Verify connected state has failed", ConnectionState.failed, ablyRealtime.connection.state); assertEquals("Check correct cause error code", 403, ablyRealtime.connection.reason.statusCode); + assertEquals(80019, ablyRealtime.connection.reason.code); + } catch (AblyException e) { e.printStackTrace(); fail(); + } finally { + assert ablyRealtime != null; + ablyRealtime.close(); } } @@ -350,7 +348,7 @@ public void auth_client_match_token_null_clientId() { assertEquals("Verify connected state is reached", ConnectionState.connected, ablyRealtime.connection.state); /* check expected clientId */ - assertEquals("Auth#clientId is expected to be null", null, ablyRealtime.auth.clientId); + assertNull("Auth#clientId is expected to be null", ablyRealtime.auth.clientId); ablyRealtime.close(); } catch (AblyException e) { @@ -383,7 +381,7 @@ public void auth_clientid_null_before_auth() { AblyRealtime ablyRealtime = new AblyRealtime(opts); /* check expected clientId */ - assertEquals("Auth#clientId is expected to be null", null, ablyRealtime.auth.clientId); + assertNull("Auth#clientId is expected to be null", ablyRealtime.auth.clientId); /* wait for connected state */ ablyRealtime.connection.connect(); @@ -688,7 +686,7 @@ public void auth_client_match_tokendetails_clientId_fail() { ClientOptions opts = createOptions(); opts.clientId = "options clientId"; opts.tokenDetails = tokenDetails; - AblyRealtime ablyRealtime = new AblyRealtime(opts); + new AblyRealtime(opts); } catch (AblyException e) { assertEquals("Verify error code indicates clientId mismatch", e.errorInfo.code, 40101); } @@ -773,7 +771,7 @@ public void auth_clientid_publish_implicit() { /* Get sent message */ Message messagePublished = protocolListener.sentMessages.get(0).messages[0]; - assertEquals("Sent message does not contain clientId", messagePublished.clientId, null); + assertNull("Sent message does not contain clientId", messagePublished.clientId); /* wait until message received on transport */ protocolListener.waitForRecv(1); @@ -819,7 +817,7 @@ public void auth_clientid_publish_implicit() { channel.publish(messageToPublish, pubComplete.add()); pubComplete.waitFor(); assertTrue("Verify publish callback called on completion", pubComplete.pending.isEmpty()); - assertTrue("Verify publish callback returns an error", pubComplete.errors.size() == 1); + assertEquals("Verify publish callback returns an error", 1, pubComplete.errors.size()); assertEquals("Verify publish callback error has expected error code", pubComplete.errors.iterator().next().code, 40012); /* verify no message sent or received on transport */ @@ -838,7 +836,7 @@ public void auth_clientid_publish_implicit() { /* Get sent message */ messagePublished = protocolListener.sentMessages.get(0).messages[0]; - assertEquals("Sent message does not contain clientId", messagePublished.clientId, null); + assertNull("Sent message does not contain clientId", messagePublished.clientId); /* wait until message received on transport */ protocolListener.waitForRecv(1); @@ -927,7 +925,7 @@ public void auth_clientid_publish_explicit_before_identified() { /* Get sent message */ messagePublished = protocolListener.sentMessages.get(0).messages[0]; - assertEquals("Sent message does not contain clientId", messagePublished.clientId, null); + assertNull("Sent message does not contain clientId", messagePublished.clientId); /* wait until message received on transport */ protocolListener.waitForRecv(1); @@ -996,7 +994,7 @@ public Object getTokenRequest(Auth.TokenParams params) { ably.connect(); try { opts.wait(); - } catch(InterruptedException ie) {} + } catch(InterruptedException ignored) {} ably.auth.renew(); } @@ -1066,7 +1064,7 @@ public Object getTokenRequest(Auth.TokenParams params) { ably.connect(); try { opts.wait(); - } catch(InterruptedException ie) {} + } catch(InterruptedException ignored) {} ably.auth.renewAuth((success, tokenDetails1, errorInfo) -> { //Ignore completion handling @@ -1183,7 +1181,7 @@ public void auth_expired_token_expire_before_connect_renew() { assertNotNull("Expected token value", tokenDetails.token); /* allow to expire */ - try { Thread.sleep(200L); } catch(InterruptedException ie) {} + try { Thread.sleep(200L); } catch(InterruptedException ignored) {} /* create Ably realtime instance with token and authCallback */ ClientOptions opts = createOptions(); diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelHistoryTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelHistoryTest.java index 17dbe8b24..b11439ff4 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelHistoryTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelHistoryTest.java @@ -355,7 +355,7 @@ public void channelhistory_wait_b() { /* wait for the history to be persisted */ try { Thread.sleep(16000); - } catch(InterruptedException ie) {} + } catch(InterruptedException ignored) {} /* get the history for this channel */ PaginatedResult messages = channel.history(null); @@ -455,7 +455,7 @@ public void channelhistory_mixed_b() { /* wait for the history to be persisted */ try { Thread.sleep(16000); - } catch(InterruptedException ie) {} + } catch(InterruptedException ignored) {} /* publish to the channel */ msgComplete = new CompletionWaiter(); @@ -517,7 +517,7 @@ public void channelhistory_mixed_f() { /* wait for the history to be persisted */ try { Thread.sleep(16000); - } catch(InterruptedException ie) {} + } catch(InterruptedException ignored) {} /* publish to the channel */ msgComplete = new CompletionWaiter(); @@ -654,7 +654,6 @@ public void channelhistory_limit_b() { } catch (AblyException e) { e.printStackTrace(); fail("channelhistory_limit_b: Unexpected exception"); - return; } finally { if(ably != null) ably.close(); @@ -720,10 +719,7 @@ public void channelhistory_time_f() { for(int i = 20; i < 40; i++) expectedMessageHistory[i - 20] = messageContents.get("history" + i); Assert.assertArrayEquals("Expect messages in forward order", messages.items(), expectedMessageHistory); - } catch (AblyException e) { - e.printStackTrace(); - fail("channelhistory_time_f: Unexpected exception"); - } catch (InterruptedException e) { + } catch (AblyException | InterruptedException e) { e.printStackTrace(); fail("channelhistory_time_f: Unexpected exception"); } finally { @@ -791,10 +787,7 @@ public void channelhistory_time_b() { for(int i = 20; i < 40; i++) expectedMessageHistory[i - 20] = messageContents.get("history" + (59 - i)); Assert.assertArrayEquals("Expect messages in backwards order", messages.items(), expectedMessageHistory); - } catch (AblyException e) { - e.printStackTrace(); - fail("channelhistory_time_b: Unexpected exception"); - } catch (InterruptedException e) { + } catch (AblyException | InterruptedException e) { e.printStackTrace(); fail("channelhistory_time_b: Unexpected exception"); } finally { @@ -1205,7 +1198,7 @@ public void run() { /* wait 2 seconds */ try { Thread.sleep(2000L); - } catch(InterruptedException ie) {} + } catch(InterruptedException ignored) {} /* subscribe; this will trigger the attach */ MessageWaiter messageWaiter = new MessageWaiter(rxChannel); diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java index 3a882cf18..461388f28 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java @@ -583,19 +583,16 @@ public void resume_publish_queue() { final Channel senderChannel = sender.channels.get(channelName); senderChannel.attach(); (new ChannelWaiter(senderChannel)).waitFor(ChannelState.attached); - assertEquals( - "The sender's channel should be attached", - senderChannel.state, ChannelState.attached - ); + assertEquals("The sender's channel should be attached", + senderChannel.state, ChannelState.attached); /* create and attach channel to recv on */ final Channel receiverChannel = receiver.channels.get(channelName); receiverChannel.attach(); (new ChannelWaiter(receiverChannel)).waitFor(ChannelState.attached); - assertEquals( - "The receiver's channel should be attached", - receiverChannel.state, ChannelState.attached - ); + assertEquals("The receiver's channel should be attached", + receiverChannel.state, ChannelState.attached); + /* subscribe */ MessageWaiter messageWaiter = new MessageWaiter(receiverChannel); @@ -612,10 +609,8 @@ public void resume_publish_queue() { /* wait for the subscription callback to be called */ messageWaiter.waitFor(messageCount); - assertEquals( - "Did not receive the entire first round of messages", - messageWaiter.receivedMessages.size(), messageCount - ); + assertEquals("Did not receive the entire first round of messages", + messageWaiter.receivedMessages.size(), messageCount); messageWaiter.reset(); /* disconnect the sender, without closing; @@ -641,7 +636,6 @@ public void resume_publish_queue() { sender.connection.connect(); (new ConnectionWaiter(sender.connection)).waitFor(ConnectionState.connected); - /* wait for the publish callback to be called.*/ errors = msgComplete2.waitFor(); assertEquals("Second round of messages (queued) has errors", 0, errors.length); @@ -655,10 +649,8 @@ public void resume_publish_queue() { received.size(), messageCount ); for(int i=0; i message.action == ProtocolMessage.Action.ack || message.action == ProtocolMessage.Action.nack); From 8e7b2724946a63ee037889c5d4f8a9b11ecaaabb Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 6 Feb 2024 18:03:58 +0530 Subject: [PATCH 70/73] Refactored channel resume tests --- .../io/ably/lib/realtime/ChannelStateListener.java | 2 +- .../lib/test/realtime/ConnectionManagerTest.java | 9 +++++---- .../ably/lib/test/realtime/RealtimeAuthTest.java | 2 +- .../ably/lib/test/realtime/RealtimeResumeTest.java | 14 ++++++-------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelStateListener.java b/lib/src/main/java/io/ably/lib/realtime/ChannelStateListener.java index bf5582d4e..5dbb8ec48 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelStateListener.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelStateListener.java @@ -77,7 +77,7 @@ public void onChannelStateChanged(ChannelStateChange stateChange) { for (final ChannelStateListener member : getMembers()) try { member.onChannelStateChanged(stateChange); - } catch(Throwable t) {} + } catch(Throwable ignored) {} } } diff --git a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java index 1a9cf7325..292c96049 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/ConnectionManagerTest.java @@ -46,6 +46,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -388,7 +389,7 @@ public void onConnectionStateChanged(ConnectionStateChange state) { /* wait for cm thread to exit */ try { Thread.sleep(2000L); - } catch(InterruptedException e) {} + } catch(InterruptedException ignored) {} assertEquals("Verify closed state is reached", ConnectionState.closed, ably.connection.state); Thread.State cmThreadState = threadContainer[0].getState(); @@ -462,7 +463,7 @@ public void run() { connectionWaiter.waitFor(ConnectionState.connected); assertEquals("Verify connected state is reached", ConnectionState.connected, ably.connection.state); - assertTrue("Not expecting token auth", ably.auth.getAuthMethod() == AuthMethod.basic); + assertSame("Not expecting token auth", ably.auth.getAuthMethod(), AuthMethod.basic); ably.close(); connectionWaiter.waitFor(ConnectionState.closed); @@ -471,7 +472,7 @@ public void run() { /* wait for cm thread to exit */ try { Thread.sleep(2000L); - } catch(InterruptedException e) {} + } catch(InterruptedException ignored) {} Thread.State cmThreadState = threadContainer[0].getState(); assertEquals("Verify cm thread has exited", cmThreadState, Thread.State.TERMINATED); @@ -510,7 +511,7 @@ public void run() { /* wait for cm thread to exit */ try { Thread.sleep(2000L); - } catch(InterruptedException e) {} + } catch(InterruptedException ignored) {} Thread.State cmThreadState = threadContainer[0].getState(); assertEquals("Verify cm thread has exited", cmThreadState, Thread.State.TERMINATED); diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeAuthTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeAuthTest.java index d50325d0b..d46014e58 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeAuthTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeAuthTest.java @@ -88,7 +88,7 @@ public void auth_client_match_tokendetails_null_clientId() { assertEquals("Verify connected state is reached", ConnectionState.connected, ablyRealtime.connection.state); /* check expected clientId */ - assertEquals("Auth#clientId is expected to be null", null, ablyRealtime.auth.clientId); + assertNull("Auth#clientId is expected to be null", ablyRealtime.auth.clientId); ablyRealtime.close(); } catch (AblyException e) { diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java index 461388f28..068054528 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeResumeTest.java @@ -37,8 +37,6 @@ public class RealtimeResumeTest extends ParameterizedTest { - private static final String TAG = RealtimeResumeTest.class.getName(); - @Rule public Timeout testTimeout = Timeout.seconds(60); @@ -1014,7 +1012,6 @@ public void resume_rewind_1 () String testName = "resume_rewind_1"; try { - ClientOptions common_opts = createOptions(testVars.keys[0].keyStr); sender = new AblyRealtime(common_opts); receiver1 = new AblyRealtime(common_opts); @@ -1036,21 +1033,22 @@ public void onRawMessageRecv(ProtocolMessage message) {} }; receiver2 = new AblyRealtime(receiver2_opts); - Channel recever1_channel = receiver1.channels.get("[?rewind=1]" + testName); - Channel recever2_channel = receiver2.channels.get("[?rewind=1]" + testName); - Channel sender_channel = sender.channels.get(testName); + Channel receiver1_channel = receiver1.channels.get("[?rewind=1]" + testName); + Channel receiver2_channel = receiver2.channels.get("[?rewind=1]" + testName); + + Channel sender_channel = sender.channels.get(testName); sender_channel.attach(); (new ChannelWaiter(sender_channel)).waitFor(ChannelState.attached); sender_channel.publish("0", testMessage); /* subscribe 1*/ - MessageWaiter messageWaiter_1 = new MessageWaiter(recever1_channel); + MessageWaiter messageWaiter_1 = new MessageWaiter(receiver1_channel); messageWaiter_1.waitFor(1); assertEquals("Verify rewound message", testMessage, messageWaiter_1.receivedMessages.get(0).data); /* subscribe 2*/ - MessageWaiter messageWaiter_2 = new MessageWaiter(recever2_channel); + MessageWaiter messageWaiter_2 = new MessageWaiter(receiver2_channel); messageWaiter_2.waitFor(1, 7000); assertEquals("Verify no message received on attach_rewind", 0, messageWaiter_2.receivedMessages.size()); From c0fd61f1b4980fab2ff10c39cb9034b14107f3c9 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 7 Feb 2024 02:12:08 +0530 Subject: [PATCH 71/73] Fixed realtime channel flaky test using conditional waiter --- .../java/io/ably/lib/test/common/Helpers.java | 35 ++++++++++++++++++- .../test/realtime/RealtimeChannelTest.java | 6 +++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/common/Helpers.java b/lib/src/test/java/io/ably/lib/test/common/Helpers.java index 32033acc4..80d3c5b80 100644 --- a/lib/src/test/java/io/ably/lib/test/common/Helpers.java +++ b/lib/src/test/java/io/ably/lib/test/common/Helpers.java @@ -19,7 +19,10 @@ import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -188,7 +191,7 @@ public synchronized ErrorInfo waitFor(int count, long timeoutInMillis) { } wait(); - } catch(InterruptedException e) {} + } catch(InterruptedException ignored) {} success = successCount >= count; if (error != null) { assertNotNull(error.message); @@ -1185,4 +1188,34 @@ public T apply(Arg arg) throws AblyException { public interface AblyFunction { Result apply(Arg arg) throws AblyException; } + + public interface ConditionFn { + O call(); + } + + public static class ConditionalWaiter { + public Exception wait(ConditionFn condition, int timeoutInMs) { + AtomicBoolean taskTimedOut = new AtomicBoolean(); + new Timer().schedule(new TimerTask() { + @Override + public void run() { + taskTimedOut.set(true); + } + }, timeoutInMs); + while (true) { + try { + Boolean result = condition.call(); + if (result) { + return null; + } + if (taskTimedOut.get()) { + throw new Exception("Timed out after " + timeoutInMs + "ms waiting for condition"); + } + Thread.sleep(200); + } catch (Exception e) { + return e; + } + } + } + } } diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index a3a7b30cf..8076d8aef 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -1672,7 +1672,11 @@ public void channel_invalid_resume_reattach_channels() throws AblyException { assertTrue(resumeError.message.contains("Invalid connection key")); assertSame(resumeError, ably.connection.connectionManager.getStateErrorInfo()); assertNotEquals("A new connection was created", originalConnectionId, ably.connection.id); - assertEquals(ably.connection.connectionManager.msgSerial, 0); + + AblyRealtime finalAbly = ably; + Exception conditionError = new Helpers.ConditionalWaiter(). + wait(() -> finalAbly.connection.connectionManager.msgSerial == 0, 5000); + assertNull(conditionError); attachedChannelWaiter.waitFor(ChannelState.attaching, ChannelState.attached); suspendedChannelWaiter.waitFor(ChannelState.attached); From ca35a08e49bdc7c7ea0ffcd10ea9df19dc0b7566 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 7 Feb 2024 17:16:59 +0530 Subject: [PATCH 72/73] Refactored realtime channel and presence tests --- .../ably/lib/test/realtime/RealtimeChannelTest.java | 10 +++++----- .../ably/lib/test/realtime/RealtimePresenceTest.java | 12 ++++-------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index 8076d8aef..4a77a08a9 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -1244,6 +1244,7 @@ public void transient_publish_connecting() throws AblyException { assertEquals("Verify channel remains in initialized state", pubChannel.state, ChannelState.initialized); ErrorInfo errorInfo = completionWaiter.waitFor(); + assertNull(errorInfo); assertEquals("Verify channel remains in initialized state", pubChannel.state, ChannelState.initialized); messageWaiter.waitFor(1); @@ -1282,7 +1283,7 @@ public void transient_publish_connection_failed() { try { pubChannel.publish("Lorem", "Ipsum!", completionWaiter); fail("failed to raise expected exception"); - } catch(AblyException e) { + } catch(AblyException ignored) { } } catch(AblyException e) { fail("unexpected exception"); @@ -1342,7 +1343,6 @@ public void transient_publish_channel_failed() { * Spec: RTL7c *

* - * @throws AblyException */ @Test public void attach_implicit_subscribe_fail() throws AblyException { @@ -1811,7 +1811,7 @@ public void onError(ErrorInfo reason) { if (errorDetaching[0] != null) errorDetaching.wait(1000); } - } catch (InterruptedException e) {} + } catch (InterruptedException ignored) {} assertNotNull("Verify detach operation failed", errorDetaching[0]); @@ -2014,7 +2014,7 @@ public void onError(ErrorInfo reason) { /* wait until the listener is called */ while(listenerError[0] == null) { - try { listenerError.wait(); } catch(InterruptedException e) {} + try { listenerError.wait(); } catch(InterruptedException ignored) {} } } @@ -2107,7 +2107,7 @@ public void detach_message_to_released_channel_is_dropped() throws AblyException } } - class DetachingProtocolListener implements DebugOptions.RawProtocolListener { + static class DetachingProtocolListener implements DebugOptions.RawProtocolListener { public Channel theChannel; boolean messageReceived; diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java index a11a8c339..50cddef83 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimePresenceTest.java @@ -338,8 +338,7 @@ public void enter_leave_simple() { } finally { if(clientAbly1 != null) clientAbly1.close(); - if(testChannel != null) - testChannel.dispose(); + testChannel.dispose(); } } @@ -408,8 +407,7 @@ public void enter_enter_simple() { } finally { if(clientAbly1 != null) clientAbly1.close(); - if(testChannel != null) - testChannel.dispose(); + testChannel.dispose(); } } @@ -478,8 +476,7 @@ public void enter_update_simple() { } finally { if(clientAbly1 != null) clientAbly1.close(); - if(testChannel != null) - testChannel.dispose(); + testChannel.dispose(); } } @@ -548,8 +545,7 @@ public void enter_update_null() { } finally { if(clientAbly1 != null) clientAbly1.close(); - if(testChannel != null) - testChannel.dispose(); + testChannel.dispose(); } } From 7e7485e8feae3ca3bbfd100fba7c56f885ff7faa Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 26 Feb 2024 18:34:33 +0530 Subject: [PATCH 73/73] Added back unused sync method, marked as deprecated --- lib/src/main/java/io/ably/lib/realtime/ChannelBase.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index 616ac98f4..8a91fdada 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -367,6 +367,12 @@ private static void callCompletionListenerSuccess(CompletionListener listener) { } } + @Deprecated + public void sync() throws AblyException { + Log.w(TAG, "sync() method is deprecated since protocol 1.2, current protocol " + + Defaults.ABLY_PROTOCOL_VERSION); + } + private static void callCompletionListenerError(CompletionListener listener, ErrorInfo err) { if(listener != null) { try {