From 418704999d84af9e474a7730dd0935f886141558 Mon Sep 17 00:00:00 2001 From: Moe Jafari Date: Sat, 14 Sep 2024 15:42:46 +0330 Subject: [PATCH] feat: Add delta compression support --- centrifuge/api.txt | 31 +++ .../github/centrifugal/centrifuge/Client.java | 10 +- .../github/centrifugal/centrifuge/Fossil.java | 191 +++++++++++++++ .../centrifuge/PublicationEvent.java | 1 - .../centrifugal/centrifuge/Subscription.java | 16 ++ .../centrifuge/SubscriptionOptions.java | 10 + centrifuge/src/main/proto/client.proto | 3 + .../centrifugal/centrifuge/FossilTest.java | 222 ++++++++++++++++++ .../centrifugal/centrifuge/example/Main.java | 2 + 9 files changed, 484 insertions(+), 2 deletions(-) create mode 100644 centrifuge/src/main/java/io/github/centrifugal/centrifuge/Fossil.java create mode 100644 centrifuge/src/test/java/io/github/centrifugal/centrifuge/FossilTest.java diff --git a/centrifuge/api.txt b/centrifuge/api.txt index bf8adaf..e4c17e6 100644 --- a/centrifuge/api.txt +++ b/centrifuge/api.txt @@ -9,6 +9,7 @@ package io.github.centrifugal.centrifuge { method public io.github.centrifugal.centrifuge.ClientState! getState(); method public io.github.centrifugal.centrifuge.Subscription! getSubscription(String); method public void history(String, io.github.centrifugal.centrifuge.HistoryOptions!, io.github.centrifugal.centrifuge.ResultCallback!); + method public io.github.centrifugal.centrifuge.Subscription! newSubscription(String, io.github.centrifugal.centrifuge.SubscriptionOptions!, io.github.centrifugal.centrifuge.SubscriptionEventListener!) throws io.github.centrifugal.centrifuge.DuplicateSubscriptionException; method public io.github.centrifugal.centrifuge.Subscription! newSubscription(String, io.github.centrifugal.centrifuge.SubscriptionEventListener!) throws io.github.centrifugal.centrifuge.DuplicateSubscriptionException; method public void presence(String, io.github.centrifugal.centrifuge.ResultCallback!); method public void presenceStats(String, io.github.centrifugal.centrifuge.ResultCallback!); @@ -16,6 +17,7 @@ package io.github.centrifugal.centrifuge { method public void removeSubscription(io.github.centrifugal.centrifuge.Subscription!); method public void rpc(String, byte[]!, io.github.centrifugal.centrifuge.ResultCallback!); method public void send(byte[]!, io.github.centrifugal.centrifuge.CompletionCallback!); + method public void setToken(String); } public class ClientInfo { @@ -46,6 +48,10 @@ package io.github.centrifugal.centrifuge { method public void onDone(Throwable); } + public class ConfigurationError { + method public Throwable getError(); + } + public class ConnectedEvent { ctor public ConnectedEvent(); method public String getClient(); @@ -82,6 +88,7 @@ package io.github.centrifugal.centrifuge { public class ErrorEvent { method public Throwable getError(); + method public Integer getHttpResponseCode(); } public abstract class EventListener { @@ -99,6 +106,17 @@ package io.github.centrifugal.centrifuge { method public void onUnsubscribed(io.github.centrifugal.centrifuge.Client!, io.github.centrifugal.centrifuge.ServerUnsubscribedEvent!); } + public class Fossil { + ctor public Fossil(); + method public static byte[]! applyDelta(byte[]!, byte[]!); + method public static long checksum(byte[]!); + } + + public class FossilTest { + ctor public FossilTest(); + method public void testApplyDelta(); + } + public class HistoryOptions { method public int getLimit(); method public boolean getReverse(); @@ -182,6 +200,7 @@ package io.github.centrifugal.centrifuge { public class Publication { ctor public Publication(); method public byte[]! getData(); + method public io.github.centrifugal.centrifuge.ClientInfo! getInfo(); method public long getOffset(); } @@ -284,11 +303,13 @@ package io.github.centrifugal.centrifuge { public class Subscription { method public String getChannel(); + method public byte[]! getPrevData(); method public io.github.centrifugal.centrifuge.SubscriptionState! getState(); method public void history(io.github.centrifugal.centrifuge.HistoryOptions!, io.github.centrifugal.centrifuge.ResultCallback!); method public void presence(io.github.centrifugal.centrifuge.ResultCallback!); method public void presenceStats(io.github.centrifugal.centrifuge.ResultCallback!); method public void publish(byte[]!, io.github.centrifugal.centrifuge.ResultCallback!); + method public void setPrevData(byte[]!); method public void subscribe(); method public void unsubscribe(); } @@ -311,6 +332,7 @@ package io.github.centrifugal.centrifuge { public class SubscriptionOptions { ctor public SubscriptionOptions(); method public byte[]! getData(); + method public String getDelta(); method public int getMaxResubscribeDelay(); method public int getMinResubscribeDelay(); method public String getToken(); @@ -319,6 +341,7 @@ package io.github.centrifugal.centrifuge { method public boolean isPositioned(); method public boolean isRecoverable(); method public void setData(byte[]!); + method public void setDelta(String); method public void setJoinLeave(boolean); method public void setMaxResubscribeDelay(int); method public void setMinResubscribeDelay(int); @@ -369,6 +392,14 @@ package io.github.centrifugal.centrifuge { method public Throwable getError(); } + public class UnauthorizedException { + ctor public UnauthorizedException(); + } + + public class UnclassifiedError { + method public Throwable getError(); + } + public class UnsubscribedEvent { ctor public UnsubscribedEvent(int, String); method public int getCode(); diff --git a/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java index fc9fc12..cb138ea 100644 --- a/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java +++ b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java @@ -873,7 +873,15 @@ private void handlePub(String channel, Protocol.Publication pub) { Subscription sub = this.getSub(channel); if (sub != null) { PublicationEvent event = new PublicationEvent(); - event.setData(pub.getData().toByteArray()); + byte[] pubData = pub.getData().toByteArray(); + byte[] prevData = sub.getPrevData(); + if (prevData != null && pub.getDelta()) { + try { + pubData = Fossil.applyDelta(prevData, pubData); + } catch (Exception e) {} + } + sub.setPrevData(pubData); + event.setData(pubData); event.setInfo(info); event.setOffset(pub.getOffset()); event.setTags(pub.getTagsMap()); diff --git a/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Fossil.java b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Fossil.java new file mode 100644 index 0000000..946a23d --- /dev/null +++ b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Fossil.java @@ -0,0 +1,191 @@ +package io.github.centrifugal.centrifuge; + +import java.io.ByteArrayOutputStream; + + +public class Fossil { + + private static final int[] zValue = new int[] { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, + -1, 36, -1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -1, + }; + + // Reader class + static class Reader { + private byte[] a; + private int pos; + + public Reader(byte[] array) { + this.a = array; + this.pos = 0; + } + + public boolean haveBytes() { + return this.pos < this.a.length; + } + + public int getByte() { + if (this.pos >= this.a.length) { + throw new IndexOutOfBoundsException("out of bounds"); + } + int b = this.a[this.pos++] & 0xFF; + return b; + } + + public char getChar() { + return (char) getByte(); + } + + public int getInt() { + int v = 0; + int c; + while (haveBytes() && (c = zValue[getByte() & 0x7F]) >= 0) { + v = (v << 6) + c; + } + this.pos--; + return v; + } + } + + // Writer class + static class Writer { + private ByteArrayOutputStream a = new ByteArrayOutputStream(); + + public byte[] toByteArray() { + return a.toByteArray(); + } + + // Copy from array 'arr' from 'start' to 'end' (exclusive) + public void putArray(byte[] arr, int start, int end) { + if (start < 0 || end > arr.length || start > end) { + throw new IndexOutOfBoundsException("Invalid start or end index"); + } + a.write(arr, start, end - start); + } + } + + // Checksum function + public static long checksum(byte[] arr) { + int sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0; + int z = 0; + int N = arr.length; + while (N >= 16) { + sum0 += (arr[z + 0] & 0xFF); + sum1 += (arr[z + 1] & 0xFF); + sum2 += (arr[z + 2] & 0xFF); + sum3 += (arr[z + 3] & 0xFF); + + sum0 += (arr[z + 4] & 0xFF); + sum1 += (arr[z + 5] & 0xFF); + sum2 += (arr[z + 6] & 0xFF); + sum3 += (arr[z + 7] & 0xFF); + + sum0 += (arr[z + 8] & 0xFF); + sum1 += (arr[z + 9] & 0xFF); + sum2 += (arr[z + 10] & 0xFF); + sum3 += (arr[z + 11] & 0xFF); + + sum0 += (arr[z + 12] & 0xFF); + sum1 += (arr[z + 13] & 0xFF); + sum2 += (arr[z + 14] & 0xFF); + sum3 += (arr[z + 15] & 0xFF); + + z += 16; + N -= 16; + } + while (N >= 4) { + sum0 += (arr[z + 0] & 0xFF); + sum1 += (arr[z + 1] & 0xFF); + sum2 += (arr[z + 2] & 0xFF); + sum3 += (arr[z + 3] & 0xFF); + z += 4; + N -= 4; + } + sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); + switch (N) { + case 3: + sum3 += (arr[z + 2] & 0xFF) << 8; + case 2: + sum3 += (arr[z + 1] & 0xFF) << 16; + case 1: + sum3 += (arr[z + 0] & 0xFF) << 24; + break; + default: + break; + } + return sum3 & 0xFFFFFFFFL; + } + + /** + * Apply a delta byte array to a source byte array, returning the target byte array. + */ + public static byte[] applyDelta(byte[] source, byte[] delta) throws Exception { + int total = 0; + Reader zDelta = new Reader(delta); + int lenSrc = source.length; + int lenDelta = delta.length; + + int limit = zDelta.getInt(); + char c = zDelta.getChar(); + if (c != '\n') { + throw new Exception("size integer not terminated by '\\n'"); + } + Writer zOut = new Writer(); + while (zDelta.haveBytes()) { + int cnt = zDelta.getInt(); + int ofst; + + c = zDelta.getChar(); + switch (c) { + case '@': + ofst = zDelta.getInt(); + if (zDelta.haveBytes() && zDelta.getChar() != ',') { + throw new Exception("copy command not terminated by ','"); + } + total += cnt; + if (total > limit) { + throw new Exception("copy exceeds output file size"); + } + if (ofst + cnt > lenSrc) { + throw new Exception("copy extends past end of input"); + } + zOut.putArray(source, ofst, ofst + cnt); + break; + + case ':': + total += cnt; + if (total > limit) { + throw new Exception("insert command gives an output larger than predicted"); + } + if (cnt > lenDelta - zDelta.pos) { + throw new Exception("insert count exceeds size of delta"); + } + zOut.putArray(zDelta.a, zDelta.pos, zDelta.pos + cnt); + zDelta.pos += cnt; + break; + + case ';': + byte[] out = zOut.toByteArray(); + long checksumValue = checksum(out); + if (cnt != (int) checksumValue) { + throw new Exception("bad checksum"); + } + if (total != limit) { + throw new Exception("generated size does not match predicted size"); + } + return out; + + default: + System.out.println(c); + throw new Exception("unknown delta operator"); + } + } + throw new Exception("unterminated delta"); + } + +} diff --git a/centrifuge/src/main/java/io/github/centrifugal/centrifuge/PublicationEvent.java b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/PublicationEvent.java index cf4acc7..f5e3b8e 100644 --- a/centrifuge/src/main/java/io/github/centrifugal/centrifuge/PublicationEvent.java +++ b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/PublicationEvent.java @@ -23,7 +23,6 @@ void setInfo(ClientInfo info) { private ClientInfo info; - public long getOffset() { return offset; } diff --git a/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Subscription.java b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Subscription.java index 001f9ac..bddec26 100644 --- a/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Subscription.java +++ b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Subscription.java @@ -27,6 +27,9 @@ public class Subscription { private int resubscribeAttempts = 0; private String token; private com.google.protobuf.ByteString data; + private String delta; + private boolean deltaNegotiated; + private byte[] prevData; Subscription(final Client client, final String channel, final SubscriptionEventListener listener, final SubscriptionOptions options) { this.client = client; @@ -38,6 +41,9 @@ public class Subscription { if (opts.getData() != null) { this.data = com.google.protobuf.ByteString.copyFrom(opts.getData()); } + this.prevData = null; + this.delta = ""; + this.deltaNegotiated = false; } Subscription(final Client client, final String channel, final SubscriptionEventListener listener) { @@ -172,6 +178,7 @@ void moveToSubscribed(Protocol.SubscribeResult result) { this.recover = true; } this.setEpoch(result.getEpoch()); + this.deltaNegotiated = result.getDelta(); byte[] data = null; if (result.getData() != null) { @@ -255,6 +262,7 @@ Protocol.SubscribeRequest createSubscribeRequest() { builder.setPositioned(this.opts.isPositioned()); builder.setRecoverable(this.opts.isRecoverable()); builder.setJoinLeave(this.opts.isJoinLeave()); + builder.setDelta(this.opts.getDelta()); return builder.build(); } @@ -459,4 +467,12 @@ private void presenceStatsSynchronized(ResultCallback cb) { f.complete(null); } } + + public byte[] getPrevData() { + return prevData; + } + + public void setPrevData(byte[] prevData) { + this.prevData = prevData; + } } diff --git a/centrifuge/src/main/java/io/github/centrifugal/centrifuge/SubscriptionOptions.java b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/SubscriptionOptions.java index 2e5d2b2..6aaa541 100644 --- a/centrifuge/src/main/java/io/github/centrifugal/centrifuge/SubscriptionOptions.java +++ b/centrifuge/src/main/java/io/github/centrifugal/centrifuge/SubscriptionOptions.java @@ -85,4 +85,14 @@ public void setJoinLeave(boolean joinLeave) { } private boolean joinLeave = false; + + public String getDelta() { + return delta; + } + + public void setDelta(String delta) { + this.delta = delta; + } + + private String delta = ""; } diff --git a/centrifuge/src/main/proto/client.proto b/centrifuge/src/main/proto/client.proto index faaece7..5fc8ff3 100644 --- a/centrifuge/src/main/proto/client.proto +++ b/centrifuge/src/main/proto/client.proto @@ -103,6 +103,7 @@ message Publication { ClientInfo info = 5; uint64 offset = 6; map tags = 7; + bool delta = 8; } message Join { @@ -199,6 +200,7 @@ message SubscribeRequest { bool positioned = 9; bool recoverable = 10; bool join_leave = 11; + string delta = 12; } message SubscribeResult { @@ -213,6 +215,7 @@ message SubscribeResult { bool positioned = 10; bytes data = 11; bool was_recovering = 12; + bool delta = 13; } message SubRefreshRequest { diff --git a/centrifuge/src/test/java/io/github/centrifugal/centrifuge/FossilTest.java b/centrifuge/src/test/java/io/github/centrifugal/centrifuge/FossilTest.java new file mode 100644 index 0000000..5a9c0e6 --- /dev/null +++ b/centrifuge/src/test/java/io/github/centrifugal/centrifuge/FossilTest.java @@ -0,0 +1,222 @@ +package io.github.centrifugal.centrifuge; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +public class FossilTest { + + @Test + public void testApplyDelta() throws Exception { + // Test 1 + String source1_str = "{\"asks\":[[\"609590\",\"3792.6\"],[\"609600\",\"11507.8\"],[\"609640\",\"663.11\"]," + + "[\"609690\",\"302.71\"],[\"609700\",\"744.52\"],[\"609730\",\"209.94\"]," + + "[\"609750\",\"18.59\"],[\"609790\",\"156\"],[\"609800\",\"859.03\"],[\"609830\"," + + "\"216.98\"],[\"609860\",\"217.42\"],[\"609870\",\"60\"],[\"609880\",\"384.06\"]," + + "[\"609890\",\"4615.87\"],[\"609900\",\"25.98\"],[\"609940\",\"63.95\"],[\"609950\"," + + "\"242.6\"],[\"609960\",\"2000\"],[\"609970\",\"1573\"],[\"609980\",\"47.56\"]," + + "[\"609990\",\"582.26\"],[\"610000\",\"42899.13\"],[\"610020\",\"24.46\"]," + + "[\"610110\",\"150\"]]," + + "\"bids\":[[\"609520\",\"2010.12\"],[\"609510\",\"5080.7\"],[\"609500\",\"297.5\"]," + + "[\"609490\",\"1238.52\"],[\"609480\",\"896.37\"],[\"609470\",\"1234.91\"]," + + "[\"609460\",\"451.36\"],[\"609250\",\"58.45\"],[\"609220\",\"786.48\"]," + + "[\"609200\",\"101.64\"],[\"609190\",\"41.03\"],[\"609160\",\"650.49\"]," + + "[\"609100\",\"6932.07\"],[\"609070\",\"16.59\"],[\"609050\",\"149.22\"]," + + "[\"609040\",\"52.53\"],[\"609030\",\"11.52\"],[\"609020\",\"1038.35\"]," + + "[\"609010\",\"334.83\"],[\"609000\",\"3453.95\"],[\"608900\",\"850.81\"]," + + "[\"608880\",\"57\"],[\"608850\",\"5.47\"],[\"608840\",\"41.23\"]]," + + "\"lastTradePrice\":\"609520\",\"lastUpdate\":1727632299611}"; + + String delta1_str = "Fm\n7i@0,6:1852.77s@7o,7:303488}3~2wv0;"; + + String out1 = "{\"asks\":[[\"609590\",\"3792.6\"],[\"609600\",\"11507.8\"],[\"609640\",\"663.11\"]," + + "[\"609690\",\"302.71\"],[\"609700\",\"744.52\"],[\"609730\",\"209.94\"]," + + "[\"609750\",\"18.59\"],[\"609790\",\"156\"],[\"609800\",\"859.03\"]," + + "[\"609830\",\"216.98\"],[\"609860\",\"217.42\"],[\"609870\",\"60\"]," + + "[\"609880\",\"384.06\"],[\"609890\",\"4615.87\"],[\"609900\",\"25.98\"]," + + "[\"609940\",\"63.95\"],[\"609950\",\"242.6\"],[\"609960\",\"2000\"]," + + "[\"609970\",\"1573\"],[\"609980\",\"47.56\"],[\"609990\",\"582.26\"]," + + "[\"610000\",\"42899.13\"],[\"610020\",\"24.46\"],[\"610110\",\"150\"]]," + + "\"bids\":[[\"609520\",\"1852.72\"],[\"609510\",\"5080.7\"],[\"609500\",\"297.5\"]," + + "[\"609490\",\"1238.52\"],[\"609480\",\"896.37\"],[\"609470\",\"1234.91\"]," + + "[\"609460\",\"451.36\"],[\"609250\",\"58.45\"],[\"609220\",\"786.48\"]," + + "[\"609200\",\"101.64\"],[\"609190\",\"41.03\"],[\"609160\",\"650.49\"]," + + "[\"609100\",\"6932.07\"],[\"609070\",\"16.59\"],[\"609050\",\"149.22\"]," + + "[\"609040\",\"52.53\"],[\"609030\",\"11.52\"],[\"609020\",\"1038.35\"]," + + "[\"609010\",\"334.83\"],[\"609000\",\"3453.95\"],[\"608900\",\"850.81\"]," + + "[\"608880\",\"57\"],[\"608850\",\"5.47\"],[\"608840\",\"41.23\"]]," + + "\"lastTradePrice\":\"609520\",\"lastUpdate\":1727632303488}"; + + byte[] result_bytes = Fossil.applyDelta( + source1_str.getBytes("UTF-8"), + delta1_str.getBytes("UTF-8") + ); + assertEquals(out1, new String(result_bytes, "UTF-8")); + + // Test 2 + String source2_str = "{\"asks\":[[\"610480\",\"26.96\"],[\"610490\",\"32.76\"],[\"610500\",\"622.44\"]," + + "[\"610530\",\"238.7\"],[\"610540\",\"990.9\"],[\"610830\",\"33\"]," + + "[\"610840\",\"159.9\"],[\"610880\",\"100\"],[\"610890\",\"33\"]," + + "[\"610900\",\"913.87\"],[\"610920\",\"30.52\"],[\"610970\",\"480.74\"]," + + "[\"610980\",\"1500\"],[\"610990\",\"266.61\"],[\"611000\",\"9672.99\"]," + + "[\"611100\",\"404.63\"],[\"611150\",\"56.25\"],[\"611170\",\"2011.71\"]," + + "[\"611200\",\"25.2\"],[\"611210\",\"10.17\"],[\"611240\",\"150\"]," + + "[\"611320\",\"8.27\"],[\"611350\",\"76.6\"],[\"611400\",\"777.8\"]]," + + "\"bids\":[[\"610360\",\"115.64\"],[\"610350\",\"525.38\"],[\"610340\",\"575.77\"]," + + "[\"610330\",\"421.83\"],[\"610320\",\"1943.17\"],[\"610310\",\"241.36\"]," + + "[\"610300\",\"3186.21\"],[\"610080\",\"418.23\"],[\"610050\",\"167.12\"]," + + "[\"610030\",\"30\"],[\"610010\",\"31.14\"],[\"610000\",\"2989.86\"]," + + "[\"609920\",\"85.04\"],[\"609910\",\"58.72\"],[\"609900\",\"2.05\"]," + + "[\"609730\",\"50\"],[\"609700\",\"729.81\"],[\"609690\",\"3608.06\"]," + + "[\"609590\",\"3.48\"],[\"609580\",\"17.48\"],[\"609520\",\"163.92\"]," + + "[\"609510\",\"500\"],[\"609500\",\"3802.96\"],[\"609460\",\"86.27\"]]," + + "\"lastTradePrice\":\"610490\",\"lastUpdate\":1727679775574}"; + + String delta2_str = "FS\nEx@0,2:36P@Ez,5:6413}icT15;"; + + String out2 = "{\"asks\":[[\"610480\",\"26.96\"],[\"610490\",\"32.76\"],[\"610500\",\"622.44\"]," + + "[\"610530\",\"238.7\"],[\"610540\",\"990.9\"],[\"610830\",\"33\"]," + + "[\"610840\",\"159.9\"],[\"610880\",\"100\"],[\"610890\",\"33\"]," + + "[\"610900\",\"913.87\"],[\"610920\",\"30.52\"],[\"610970\",\"480.74\"]," + + "[\"610980\",\"1500\"],[\"610990\",\"266.61\"],[\"611000\",\"9672.99\"]," + + "[\"611100\",\"404.63\"],[\"611150\",\"56.25\"],[\"611170\",\"2011.71\"]," + + "[\"611200\",\"25.2\"],[\"611210\",\"10.17\"],[\"611240\",\"150\"]," + + "[\"611320\",\"8.27\"],[\"611350\",\"76.6\"],[\"611400\",\"777.8\"]]," + + "\"bids\":[[\"610360\",\"115.64\"],[\"610350\",\"525.38\"],[\"610340\",\"575.77\"]," + + "[\"610330\",\"421.83\"],[\"610320\",\"1943.17\"],[\"610310\",\"241.36\"]," + + "[\"610300\",\"3186.21\"],[\"610080\",\"418.23\"],[\"610050\",\"167.12\"]," + + "[\"610030\",\"30\"],[\"610010\",\"31.14\"],[\"610000\",\"2989.86\"]," + + "[\"609920\",\"85.04\"],[\"609910\",\"58.72\"],[\"609900\",\"2.05\"]," + + "[\"609730\",\"50\"],[\"609700\",\"729.81\"],[\"609690\",\"3608.06\"]," + + "[\"609590\",\"3.48\"],[\"609580\",\"17.48\"],[\"609520\",\"163.92\"]," + + "[\"609510\",\"500\"],[\"609500\",\"3802.96\"],[\"609460\",\"86.27\"]]," + + "\"lastTradePrice\":\"610360\",\"lastUpdate\":1727679776413}"; + + result_bytes = Fossil.applyDelta( + source2_str.getBytes("UTF-8"), + delta2_str.getBytes("UTF-8") + ); + assertEquals(out2, new String(result_bytes, "UTF-8")); + + // Test 3 + String source3_str = "{\"asks\":[[\"610350\",\"3422.1\"],[\"610380\",\"1743.7\"],[\"610400\",\"5133.73\"]," + + "[\"610410\",\"2690.87\"],[\"610420\",\"100\"],[\"610450\",\"610.86\"]," + + "[\"610500\",\"815.43\"],[\"610690\",\"25\"],[\"610700\",\"120.8\"]," + + "[\"610920\",\"524.38\"],[\"610930\",\"305.51\"],[\"611000\",\"937.44\"]," + + "[\"611060\",\"8.18\"],[\"611130\",\"69.91\"],[\"611140\",\"503\"]," + + "[\"611150\",\"601.79\"],[\"611190\",\"15\"],[\"611200\",\"1128.36\"]," + + "[\"611250\",\"2153.73\"],[\"611330\",\"500\"],[\"611360\",\"300\"]," + + "[\"611400\",\"21.5\"],[\"611500\",\"637.95\"],[\"611530\",\"2\"]]," + + "\"bids\":[[\"610320\",\"114.61\"],[\"610300\",\"491.56\"],[\"610290\",\"479\"]," + + "[\"610260\",\"474.2\"],[\"610240\",\"427.85\"],[\"610200\",\"183.67\"]," + + "[\"610160\",\"585.47\"],[\"610150\",\"396.31\"],[\"610140\",\"1615.92\"]," + + "[\"610120\",\"128.73\"],[\"610100\",\"5571.63\"],[\"610040\",\"6.84\"]," + + "[\"610000\",\"15505.56\"],[\"609930\",\"100\"],[\"609900\",\"46\"]," + + "[\"609810\",\"150\"],[\"609750\",\"196.76\"],[\"609730\",\"50.2\"]," + + "[\"609700\",\"411.97\"],[\"609650\",\"1640.12\"],[\"609640\",\"480.23\"]," + + "[\"609600\",\"410.04\"],[\"609560\",\"1640.36\"],[\"609530\",\"2.14\"]]," + + "\"lastTradePrice\":\"610350\",\"lastUpdate\":1727685711521}"; + + String delta3_str = "Fa\nQ:{\"asks\":[[\"610320\",\"312.757@4v,E:0350\",\"3418.837@6x,D:0380\",\"1743.78@8W," + + "68@r,A:],\"bids\":[6y@7g,I:,[\"609520\",\"143.57p@Eb,5:3315}3QQaIf;"; + + String out3 = "{\"asks\":[[\"610320\",\"312.75\"],[\"610350\",\"3418.83\"],[\"610380\",\"1743.7\"]," + + "[\"610400\",\"5133.73\"],[\"610410\",\"2690.87\"],[\"610420\",\"100\"]," + + "[\"610450\",\"610.86\"],[\"610500\",\"815.43\"],[\"610690\",\"25\"]," + + "[\"610700\",\"120.8\"],[\"610920\",\"524.38\"],[\"610930\",\"305.51\"]," + + "[\"611000\",\"937.44\"],[\"611060\",\"8.18\"],[\"611130\",\"69.91\"]," + + "[\"611140\",\"503\"],[\"611150\",\"601.79\"],[\"611190\",\"15\"]," + + "[\"611200\",\"1128.36\"],[\"611250\",\"2153.73\"],[\"611330\",\"500\"]," + + "[\"611360\",\"300\"],[\"611400\",\"21.5\"],[\"611500\",\"637.95\"]]," + + "\"bids\":[[\"610300\",\"491.56\"],[\"610290\",\"479\"],[\"610260\",\"474.2\"]," + + "[\"610240\",\"427.85\"],[\"610200\",\"183.67\"],[\"610160\",\"585.47\"]," + + "[\"610150\",\"396.31\"],[\"610140\",\"1615.92\"],[\"610120\",\"128.73\"]," + + "[\"610100\",\"5571.63\"],[\"610040\",\"6.84\"],[\"610000\",\"15505.56\"]," + + "[\"609930\",\"100\"],[\"609900\",\"46\"],[\"609810\",\"150\"]," + + "[\"609750\",\"196.76\"],[\"609730\",\"50.2\"],[\"609700\",\"411.97\"]," + + "[\"609650\",\"1640.12\"],[\"609640\",\"480.23\"],[\"609600\",\"410.04\"]," + + "[\"609560\",\"1640.36\"],[\"609530\",\"2.14\"],[\"609520\",\"143.57\"]]," + + "\"lastTradePrice\":\"610350\",\"lastUpdate\":1727685713315}"; + + result_bytes = Fossil.applyDelta( + source3_str.getBytes("UTF-8"), + delta3_str.getBytes("UTF-8") + ); + assertEquals(out3, new String(result_bytes, "UTF-8")); + + // Test 4 + String source4_str = "{\"asks\":[[\"610390\",\"600.45\"],[\"610400\",\"118.16\"],[\"610410\",\"2450.9\"]," + + "[\"610420\",\"100\"],[\"610450\",\"413.91\"],[\"610490\",\"20\"]," + + "[\"610500\",\"865.43\"],[\"610690\",\"25\"],[\"610700\",\"120.8\"]," + + "[\"610800\",\"325.49\"],[\"610900\",\"43\"],[\"610930\",\"386.35\"]," + + "[\"610980\",\"25\"],[\"610990\",\"1304.18\"],[\"611000\",\"6729.02\"]," + + "[\"611060\",\"8.18\"],[\"611140\",\"105\"],[\"611150\",\"601.79\"]," + + "[\"611190\",\"15\"],[\"611200\",\"1118.36\"],[\"611250\",\"2253.63\"]," + + "[\"611330\",\"500\"],[\"611350\",\"200.66\"],[\"611360\",\"300\"]]," + + "\"bids\":[[\"610260\",\"73.51\"],[\"610250\",\"1884.19\"],[\"610240\",\"27.79\"]," + + "[\"610230\",\"55.7\"],[\"610100\",\"88.94\"],[\"610060\",\"957.52\"]," + + "[\"610040\",\"48.84\"],[\"610000\",\"7344.08\"],[\"609990\",\"234.11\"]," + + "[\"609800\",\"2.1\"],[\"609720\",\"50\"],[\"609670\",\"2583.24\"]," + + "[\"609660\",\"50.99\"],[\"609650\",\"922.5\"],[\"609640\",\"381.02\"]," + + "[\"609600\",\"410.04\"],[\"609560\",\"1640.36\"],[\"609530\",\"2.14\"]," + + "[\"609520\",\"143.57\"],[\"609510\",\"531.75\"],[\"609500\",\"5885.67\"]," + + "[\"609460\",\"86.27\"],[\"609430\",\"100\"],[\"609400\",\"410.17\"]]," + + "\"lastTradePrice\":\"610390\",\"lastUpdate\":1727688337312}"; + + String delta4_str = "FO\nP@0,74@Q,5:68.377N@7Z,1:5P@Ew,5:9024}9u5zN;"; + + String out4 = "{\"asks\":[[\"610390\",\"600.4\"],[\"610400\",\"118.16\"],[\"610410\",\"2450.9\"]," + + "[\"610420\",\"100\"],[\"610450\",\"413.91\"],[\"610490\",\"20\"]," + + "[\"610500\",\"865.43\"],[\"610690\",\"25\"],[\"610700\",\"120.8\"]," + + "[\"610800\",\"325.49\"],[\"610900\",\"43\"],[\"610930\",\"386.35\"]," + + "[\"610980\",\"25\"],[\"610990\",\"1304.18\"],[\"611000\",\"6729.02\"]," + + "[\"611060\",\"8.18\"],[\"611140\",\"105\"],[\"611150\",\"601.79\"]," + + "[\"611190\",\"15\"],[\"611200\",\"1118.36\"],[\"611250\",\"2253.63\"]," + + "[\"611330\",\"500\"],[\"611350\",\"200.66\"],[\"611360\",\"300\"]]," + + "\"bids\":[[\"610260\",\"68.37\"],[\"610250\",\"1884.19\"],[\"610240\",\"27.79\"]," + + "[\"610230\",\"55.7\"],[\"610100\",\"88.94\"],[\"610060\",\"957.52\"]," + + "[\"610040\",\"48.84\"],[\"610000\",\"7344.08\"],[\"609990\",\"234.11\"]," + + "[\"609800\",\"2.1\"],[\"609720\",\"50\"],[\"609670\",\"2583.24\"]," + + "[\"609660\",\"50.99\"],[\"609650\",\"922.5\"],[\"609640\",\"381.02\"]," + + "[\"609600\",\"410.04\"],[\"609560\",\"1640.36\"],[\"609530\",\"2.14\"]," + + "[\"609520\",\"143.57\"],[\"609510\",\"531.75\"],[\"609500\",\"5885.67\"]," + + "[\"609460\",\"86.27\"],[\"609430\",\"100\"],[\"609400\",\"410.17\"]]," + + "\"lastTradePrice\":\"610350\",\"lastUpdate\":1727688339024}"; + + result_bytes = Fossil.applyDelta( + source4_str.getBytes("UTF-8"), + delta4_str.getBytes("UTF-8") + ); + assertEquals(out4, new String(result_bytes, "UTF-8")); + + // Test 5 + // source5_str is out4 + String source5_str = out4; + String delta5_str = "FP\nK@0,6:593.1775@P,4:0.517N@7Y,1:9Q@Ev,4:892}1bjQuR;"; + + String out5 = "{\"asks\":[[\"610390\",\"593.17\"],[\"610400\",\"118.16\"],[\"610410\",\"2450.9\"]," + + "[\"610420\",\"100\"],[\"610450\",\"413.91\"],[\"610490\",\"20\"]," + + "[\"610500\",\"865.43\"],[\"610690\",\"25\"],[\"610700\",\"120.8\"]," + + "[\"610800\",\"325.49\"],[\"610900\",\"43\"],[\"610930\",\"386.35\"]," + + "[\"610980\",\"25\"],[\"610990\",\"1304.18\"],[\"611000\",\"6729.02\"]," + + "[\"611060\",\"8.18\"],[\"611140\",\"105\"],[\"611150\",\"601.79\"]," + + "[\"611190\",\"15\"],[\"611200\",\"1118.36\"],[\"611250\",\"2253.63\"]," + + "[\"611330\",\"500\"],[\"611350\",\"200.66\"],[\"611360\",\"300\"]]," + + "\"bids\":[[\"610260\",\"60.51\"],[\"610250\",\"1884.19\"],[\"610240\",\"27.79\"]," + + "[\"610230\",\"55.7\"],[\"610100\",\"88.94\"],[\"610060\",\"957.52\"]," + + "[\"610040\",\"48.84\"],[\"610000\",\"7344.08\"],[\"609990\",\"234.11\"]," + + "[\"609800\",\"2.1\"],[\"609720\",\"50\"],[\"609670\",\"2583.24\"]," + + "[\"609660\",\"50.99\"],[\"609650\",\"922.5\"],[\"609640\",\"381.02\"]," + + "[\"609600\",\"410.04\"],[\"609560\",\"1640.36\"],[\"609530\",\"2.14\"]," + + "[\"609520\",\"143.57\"],[\"609510\",\"531.75\"],[\"609500\",\"5885.67\"]," + + "[\"609460\",\"86.27\"],[\"609430\",\"100\"],[\"609400\",\"410.17\"]]," + + "\"lastTradePrice\":\"610390\",\"lastUpdate\":1727688339892}"; + + result_bytes = Fossil.applyDelta( + source5_str.getBytes("UTF-8"), + delta5_str.getBytes("UTF-8") + ); + assertEquals(out5, new String(result_bytes, "UTF-8")); + } +} diff --git a/example/src/main/java/io/github/centrifugal/centrifuge/example/Main.java b/example/src/main/java/io/github/centrifugal/centrifuge/example/Main.java index 7f330f1..802936c 100644 --- a/example/src/main/java/io/github/centrifugal/centrifuge/example/Main.java +++ b/example/src/main/java/io/github/centrifugal/centrifuge/example/Main.java @@ -150,6 +150,8 @@ public void onLeave(Subscription sub, LeaveEvent event) { }; Subscription sub; + // You can set `delta` to `"fossil"` for using delta compression via + // `subOpts.setDelta("fossil")`; try { sub = client.newSubscription("chat:index", new SubscriptionOptions(), subListener); } catch (DuplicateSubscriptionException e) {