1717import java .util .List ;
1818import java .util .Map ;
1919import java .util .concurrent .TimeUnit ;
20+ import java .util .HashSet ;
21+ import java .util .Set ;
2022
2123import okhttp3 .OkHttpClient ;
2224import okhttp3 .Request ;
@@ -303,9 +305,6 @@ private void onNetworkLost() {
303305 connections .clear ();
304306 }
305307
306- /**
307- * Parse channels from JSON string passed via Intent
308- */
309308 private List <ChannelConfig > parseChannelsJson (String channelsJson ) {
310309 List <ChannelConfig > channels = new ArrayList <>();
311310
@@ -327,7 +326,7 @@ private List<ChannelConfig> parseChannelsJson(String channelsJson) {
327326 String channel = data .optString ("channel" , null );
328327 String channel_dec = data .optString ("channel_dec" , null );
329328 String serverPort = data .optString ("server" , "mles.io:443" );
330- String msgChksum = data .optString ("msg_chksum " , "0 " );
329+ String msgChksums = data .optString ("msg_chksums " , "[] " ); // Changed from msg_chksum to msg_chksums
331330
332331 if (userName == null || channel == null || userName .isEmpty () || channel .isEmpty ()) {
333332 //Log.w(TAG, "Skipping channel, missing data: " + channelName);
@@ -351,8 +350,8 @@ private List<ChannelConfig> parseChannelsJson(String channelsJson) {
351350 }
352351 }
353352
354- //Log.d(TAG, "Parsed channel: " + channel + " channel_dec: " + channel_dec + " user: " + userName + " @ " + server + ":" + port + " chksum : " + msgChksum );
355- channels .add (new ChannelConfig (userName , channel , channel_dec , server , port , msgChksum ));
353+ //Log.d(TAG, "Parsed channel: " + channel + " channel_dec: " + channel_dec + " user: " + userName + " @ " + server + ":" + port + " chksums : " + msgChksums );
354+ channels .add (new ChannelConfig (userName , channel , channel_dec , server , port , msgChksums ));
356355 }
357356 } catch (Exception e ) {
358357 Log .e (TAG , "Error parsing channels JSON" , e );
@@ -370,27 +369,77 @@ private static class ChannelConfig {
370369 String channelDecrypted ;
371370 String server ;
372371 int port ;
373- String msgChksum ;
372+ String msgChksumsJson ; // Store checksums as JSON string
373+ private Set <String > checksumsCache ;
374+ private String latestChecksum ;
374375
375- ChannelConfig (String userName , String channelName , String channelDecrypted , String server , int port , String msgChksum ) {
376+ ChannelConfig (String userName , String channelName , String channelDecrypted , String server , int port , String msgChksumsJson ) {
376377 this .userName = userName ;
377378 this .channelName = channelName ;
378379 this .channelDecrypted = channelDecrypted ;
379380 this .server = server ;
380381 this .port = port ;
381- this .msgChksum = msgChksum ;
382+ this .msgChksumsJson = msgChksumsJson != null ? msgChksumsJson : "[]" ;
383+ this .checksumsCache = new HashSet <>();
384+
385+ //Log.d(TAG, "ChannelConfig, msgChksumsJson: " + msgChksumsJson);
386+
387+ initChecksumsFromJson ();
388+ }
389+
390+ private void initChecksumsFromJson () {
391+ try {
392+ if (!msgChksumsJson .isEmpty () && !msgChksumsJson .equals ("[]" )) {
393+ org .json .JSONArray array = new org .json .JSONArray (msgChksumsJson );
394+
395+ //Log.d(TAG, "Loaded " + array.length() + " checksums from JSON");
396+ for (int i = 0 ; i < array .length (); i ++) {
397+ String chk = array .getString (i );
398+ checksumsCache .add (chk );
399+ latestChecksum = chk ; // last item wins
400+ }
401+ }
402+ else {
403+ // Handle empty or invalid JSON string
404+ Log .w (TAG , "Empty or invalid checksums JSON" );
405+ }
406+ } catch (Exception e ) {
407+ Log .e (TAG , "Error parsing checksums JSON" , e );
408+ }
382409 }
383410
384411 String getWsUrl () {
385412 return "wss://" + server + ":" + port ;
386413 }
387414
388- void setChksum (String msgChksum ) {
389- this .msgChksum = msgChksum ;
415+ Set <String > getChksums () {
416+ return checksumsCache ;
417+ }
418+
419+ /**
420+ * Get the latest (most recent) checksum
421+ */
422+ String getLatestChksum () {
423+ return latestChecksum ;
390424 }
391425
392- String getChksum () {
393- return this .msgChksum ;
426+ /**
427+ * Check if a checksum exists in the array
428+ */
429+ boolean hasChksum (String checksum ) {
430+ if (checksum == null ) {
431+ return false ;
432+ }
433+ return getChksums ().contains (checksum );
434+ }
435+
436+ /**
437+ * Add a new checksum to the in-memory array (no persistence)
438+ */
439+ void addChksum (String checksum ) {
440+ if (checksum != null ) {
441+ getChksums ().add (checksum );
442+ }
394443 }
395444 }
396445
@@ -401,8 +450,7 @@ private List<ChannelConfig> loadChannels() {
401450 List <ChannelConfig > channels = new ArrayList <>();
402451
403452 try {
404- @ SuppressWarnings ("deprecation" )
405- SharedPreferences prefs = context .getSharedPreferences (PREFS_NAME , Context .MODE_MULTI_PROCESS );
453+ SharedPreferences prefs = context .getSharedPreferences (PREFS_NAME , Context .MODE_PRIVATE );
406454 String activeChannelsJson = prefs .getString ("gActiveChannelsJSON" , null );
407455
408456 if (activeChannelsJson == null || activeChannelsJson .isEmpty ()) {
@@ -431,9 +479,6 @@ private List<ChannelConfig> loadChannels() {
431479 return channels ;
432480 }
433481
434- /**
435- * Save channels to cache for process restart recovery
436- */
437482 private void saveChannelsToCache (List <ChannelConfig > channels ) {
438483 if (channels == null || channels .isEmpty ()) {
439484 //Log.d(TAG, "No channels to save to cache");
@@ -453,7 +498,7 @@ private void saveChannelsToCache(List<ChannelConfig> channels) {
453498 channelJson .put ("channelDecrypted" , config .channelDecrypted );
454499 channelJson .put ("server" , config .server );
455500 channelJson .put ("port" , config .port );
456- channelJson .put ("msgChksum " , config .msgChksum );
501+ channelJson .put ("msgChksumsJson " , config .msgChksumsJson ); // Changed from msgChksum
457502
458503 cacheJson .put (config .channelName , channelJson );
459504 }
@@ -468,9 +513,6 @@ private void saveChannelsToCache(List<ChannelConfig> channels) {
468513 }
469514 }
470515
471- /**
472- * Load channels from cache (for process restart)
473- */
474516 private List <ChannelConfig > loadChannelsFromCache () {
475517 List <ChannelConfig > channels = new ArrayList <>();
476518
@@ -497,10 +539,10 @@ private List<ChannelConfig> loadChannelsFromCache() {
497539 String channelDec = channelJson .optString ("channelDecrypted" , null );
498540 String server = channelJson .optString ("server" , "mles.io" );
499541 int port = channelJson .optInt ("port" , 443 );
500- String msgChksum = channelJson .optString ("msgChksum " , "0 " );
542+ String msgChksumsJson = channelJson .optString ("msgChksumsJson " , "[] " ); // Changed from msgChksum
501543
502544 if (userName != null && channel != null ) {
503- channels .add (new ChannelConfig (userName , channel , channelDec , server , port , msgChksum ));
545+ channels .add (new ChannelConfig (userName , channel , channelDec , server , port , msgChksumsJson ));
504546 //Log.d(TAG, "Restored channel from cache: " + channel);
505547 }
506548 }
@@ -530,41 +572,43 @@ private void clearChannelsCache() {
530572 /**
531573 * Load configuration for a specific channel
532574 */
533- private ChannelConfig loadChannelConfig (SharedPreferences prefs , String channelName ) {
534- try {
535- String userName = prefs .getString ("gMyName" + channelName , null );
536- String channel = prefs .getString ("gMyChannel" + channelName , null );
537- String channelDec = prefs .getString ("gMyChannelDec" + channelName , null );
538- String addrPort = prefs .getString ("gAddrPortInput" + channelName , "mles.io:443" );
539- String msgChksum = prefs .getString ("gMsgChksum" + channelName , "0" );
540-
541- //Log.d(TAG, "Channel " + channelName + ", Channel decrypted " + channelDec + ": name=" + userName + ", channel=" + channel + ", server=" + addrPort + ", chksum=" + msgChksum);
542-
543- // Parse server:port
544- String server = "mles.io" ;
545- int port = 443 ;
546-
547- if (addrPort != null && !addrPort .isEmpty ()) {
548- String [] parts = addrPort .split (":" );
549- if (parts .length >= 1 && !parts [0 ].isEmpty ()) {
550- server = parts [0 ];
551- }
552- if (parts .length >= 2 ) {
553- try {
554- port = Integer .parseInt (parts [1 ]);
555- } catch (NumberFormatException e ) {
556- port = 443 ;
557- }
558- }
559- }
560-
561- return new ChannelConfig (userName , channel , channelDec , server , port , msgChksum );
562-
563- } catch (Exception e ) {
564- //Log.e(TAG, "Error loading channel config: " + channelName, e);
565- return null ;
566- }
567- }
575+ /**
576+ * Load configuration for a specific channel
577+ */
578+ private ChannelConfig loadChannelConfig (SharedPreferences prefs , String channelName ) {
579+ try {
580+ String userName = prefs .getString ("gMyName" + channelName , null );
581+ String channel = prefs .getString ("gMyChannel" + channelName , null );
582+ String channelDec = prefs .getString ("gMyChannelDec" + channelName , null );
583+ String addrPort = prefs .getString ("gAddrPortInput" + channelName , "mles.io:443" );
584+ String msgChksums = prefs .getString ("gMsgChksums" + channelName , "[]" ); // Load array, default to empty array
585+
586+ //Log.d(TAG, "Channel " + channelName + ", Channel decrypted " + channelDec + ": name=" + userName + ", channel=" + channel + ", server=" + addrPort + ", chksums=" + msgChksums);
587+
588+ // Parse server:port
589+ String server = "mles.io" ;
590+ int port = 443 ;
591+ if (addrPort != null && !addrPort .isEmpty ()) {
592+ String [] parts = addrPort .split (":" );
593+ if (parts .length >= 1 && !parts [0 ].isEmpty ()) {
594+ server = parts [0 ];
595+ }
596+ if (parts .length >= 2 ) {
597+ try {
598+ port = Integer .parseInt (parts [1 ]);
599+ } catch (NumberFormatException e ) {
600+ port = 443 ;
601+ }
602+ }
603+ }
604+
605+ return new ChannelConfig (userName , channel , channelDec , server , port , msgChksums );
606+
607+ } catch (Exception e ) {
608+ //Log.e(TAG, "Error loading channel config: " + channelName, e);
609+ return null ;
610+ }
611+ }
568612
569613 /**
570614 * Connect to a channel via WebSocket
@@ -729,33 +773,31 @@ private void handleMessage(String message, ChannelConfig config) {
729773 }
730774
731775 /**
732- * Handle incoming message - sync first, then notify on new
776+ * Handle incoming message - sync by matching latest checksum, check against all for duplicates
733777 */
734778 private void handleMessage (ByteString message , ChannelConfig config ) {
735779 byte [] messageBytes = message .toByteArray ();
736780
737781 // Compute SipHash with zero key
738782 long hash = SipHash .hash (SIPHASH_KEY , messageBytes );
739783 String hashHex = SipHash .toHexString (hash );
740-
741- String lastChksum = config .getChksum ();
742- //Log.d(TAG, "Last chksum for channel "+ config.channelName + ": " + lastChksum + ", got " + hashHex + " message: " + message);
743784 Boolean synced = channelSynced .get (config .channelName );
744785 long now = System .currentTimeMillis ();
745786
746- // Not synced yet - waiting for initial checksum match
787+ // Not synced yet - waiting for latest checksum match
747788 if (synced == null || !synced ) {
748- if (lastChksum == null || lastChksum .isEmpty () || lastChksum .equals ("0" )) {
789+ String latestChksum = config .getLatestChksum ();
790+ if (latestChksum == null || latestChksum .isEmpty ()) {
749791 // No previous checksum - sync immediately
750- // Log.d(TAG, "No previous checksum for " + config.channelName + ", synced");
792+ Log .d (TAG , "No previous checksum for " + config .channelName + ", synced" );
751793 channelSynced .put (config .channelName , true );
752- config .setChksum (hashHex );
794+ config .addChksum (hashHex );
753795 if (resyncListener != null ) {
754796 resyncListener .onResync (config .channelName );
755797 }
756- } else if (lastChksum .equals (hashHex )) {
757- // Found the last known message - now synced
758- //Log.d(TAG, "Synced to last message on " + config.channelName);
798+ } else if (latestChksum .equals (hashHex )) {
799+ // Found the latest message - now synced
800+ // Log.d(TAG, "Synced to last message on " + config.channelName);
759801 channelSynced .put (config .channelName , true );
760802 syncStartTime .remove (config .channelName );
761803 if (resyncListener != null ) {
@@ -770,9 +812,9 @@ private void handleMessage(ByteString message, ChannelConfig config) {
770812 //Log.d(TAG, "Catching up on " + config.channelName + ", waiting for sync");
771813 } else if (now - startTime > SYNC_TIMEOUT_MS ) {
772814 // Timeout - sync to latest
773- //Log.d(TAG, "Sync timeout on " + config.channelName + ", syncing to latest");
815+ // Log.d(TAG, "Sync timeout on " + config.channelName + ", syncing to latest");
774816 channelSynced .put (config .channelName , true );
775- config .setChksum (hashHex );
817+ config .addChksum (hashHex );
776818 syncStartTime .remove (config .channelName );
777819 if (resyncListener != null ) {
778820 resyncListener .onResync (config .channelName );
@@ -785,14 +827,13 @@ private void handleMessage(ByteString message, ChannelConfig config) {
785827 return ;
786828 }
787829
788- // Already synced - check for new messages
789- if (lastChksum != null && lastChksum .equals (hashHex )) {
790- // Same message, ignore
830+ if (config .hasChksum (hashHex )) {
831+ // Already seen (original or resend), ignore
791832 return ;
792833 }
793834
794835 // New message!
795- config .setChksum (hashHex );
836+ config .addChksum (hashHex );
796837 //Log.d(TAG, "New message on " + config.channelName + ", checksum: " + hashHex);
797838
798839 if (listener != null ) {
0 commit comments