Author: dbkr
Date: 2008-04-30 22:28:37 +0000 (Wed, 30 Apr 2008)
New Revision: 19639

Modified:
   trunk/apps/Freemail/src/freemail/OutboundContact.java
Log:
Don't re-insert mail to different slots - re-send the RTS instead. This will 
allow contacts to be re-established if the inbound contact has been deleted.


Modified: trunk/apps/Freemail/src/freemail/OutboundContact.java
===================================================================
--- trunk/apps/Freemail/src/freemail/OutboundContact.java       2008-04-30 
21:55:00 UTC (rev 19638)
+++ trunk/apps/Freemail/src/freemail/OutboundContact.java       2008-04-30 
22:28:37 UTC (rev 19639)
@@ -73,10 +73,9 @@
        private static final long CTS_WAIT_TIME = 26 * 60 * 60 * 1000;

        private static final String PROPSFILE_NAME = "props";
-       // how long do we wait before retransmitting the message? 26 hours 
allows
-       // for people starting Freemail at roughly the same time every day

-       private static final long RETRANSMIT_DELAY = 26 * 60 * 60 * 1000;
+       // How long to wait for a *message ack* before sending another RTS.
+       private static final long RTS_RETRANSMIT_DELAY = 2 * 24 * 60 * 60 * 
1000;

        // how long do we wait before we give up all hope and just bounce the 
message back?
        // 5 days is fairly standard, so we'll go with that for now, except 
that means things
@@ -91,6 +90,10 @@
        // we read 128 bytes for our IV, so it needs to be constant.)
        private static final int AES_BLOCK_LENGTH = 128 / 8;

+       // If we last fetched the mailsite longer than this number of 
milliseconds
+    // ago, re-fetch it.
+       private static final long MAILSITE_CACHE_TIME = 60 * 60 * 1000;
+       
        public OutboundContact(FreemailAccount acc, EmailAddress a) throws 
BadFreemailAddressException, IOException,
                                                                   
OutboundContactFatalException, ConnectionTerminatedException {
                this.address = a;
@@ -274,20 +277,39 @@
                return rtsksk;
        }

-       private String getInitialSlot() {
-               String retval = this.contactfile.get("initialslot");
-               
-               if (retval != null) return retval;
-               
-               SecureRandom rnd = new SecureRandom();
-               SHA256Digest sha256 = new SHA256Digest();
-               byte[] buf = new byte[sha256.getDigestSize()];
-               
-               rnd.nextBytes(buf);
-               
-               this.contactfile.put("initialslot", Base32.encode(buf));
-               
-               return Base32.encode(buf);
+       /**
+        * Get the first slot from which all messages that are still 'in 
transit' can be retrieved.
+        * That is to say, if we have message IDs 3,4 and 5 in transit, this 
would return the slot
+        * for message 3. If there are no messages in transit, returns the next 
slot on which a message will be inserted.
+        */
+       private String getCurrentLowestSlot() {
+               QueuedMessage[] queue = getSendQueue();
+               if (queue.length > 0) {
+                       int messageWithHighestUid = 0;
+                       for (int i = 0; i < queue.length; i++) {
+                               if (queue[i] == null) continue;
+                               if (queue[i].uid > messageWithHighestUid) 
messageWithHighestUid = i;
+                       }
+                       return queue[messageWithHighestUid].slot;
+               } else {
+                       String retval = this.contactfile.get("nextslot");
+                       if (retval != null) {
+                               return retval;
+                       } else {
+                               Logger.minor(this, "Generating first slot for 
contact");
+                               SecureRandom rnd = new SecureRandom();
+                               SHA256Digest sha256 = new SHA256Digest();
+                               byte[] buf = new byte[sha256.getDigestSize()];
+                               
+                               rnd.nextBytes(buf);
+                               
+                               String firstSlot = Base32.encode(buf);
+                               
+                               this.contactfile.put("nextslot", 
Base32.encode(buf));
+                               
+                               return firstSlot;
+                       }
+               }
        }

        private byte[] getAESParams() {
@@ -318,7 +340,7 @@
                Logger.normal(this, "Initialising Outbound Contact 
"+address.toString());

                // try to fetch get all necessary info. will fetch mailsite / 
generate new keys if necessary
-               String initialslot = this.getInitialSlot();
+               String initialslot = this.getCurrentLowestSlot();
                SSKKeyPair commssk = this.getCommKeyPair();
                if (commssk == null) return false;
                SSKKeyPair ackssk = this.getAckKeyPair();
@@ -481,6 +503,19 @@
        }

        private boolean fetchMailSite() throws OutboundContactFatalException, 
ConnectionTerminatedException {
+               String lastFetchedStr = this.contactfile.get("lastfetched");
+               long lastFetched = 0;
+               if (lastFetchedStr != null) {
+                       lastFetched = Long.parseLong(lastFetchedStr);
+               }
+               if (lastFetched > System.currentTimeMillis()) {
+                       Logger.error(this, "Mailsite was apparently last 
fetched in the future! System time gone backwards? Refetching.");
+                       lastFetched = 0;
+               }
+               if (lastFetched > System.currentTimeMillis() - 
MAILSITE_CACHE_TIME) {
+                       return true;
+               }
+               
                HighLevelFCPClient cli = new HighLevelFCPClient();

                Logger.normal(this,"Attempting to fetch 
"+this.address.getMailpageKey());
@@ -503,15 +538,16 @@
                mailsite_file.delete();

                if (rtsksk == null || keymod_str == null || keyexp_str == null) 
{
-                       // TODO: More failure mechanisms - this is fatal.
+                       // Not actually fatal - the other party could publish a 
new, valid mailsite
                        Logger.normal(this,"Mailsite for "+this.address+" does 
not contain all necessary information!");
-                       throw new OutboundContactFatalException("Mailsite for 
"+this.address+" does not contain all necessary information!");
+                       return false;
                }

                // add this to a new outbound contact file
                this.contactfile.put("rtsksk", rtsksk);
                this.contactfile.put("asymkey.modulus", keymod_str);
                this.contactfile.put("asymkey.pubexponent", keyexp_str);
+               this.contactfile.put("lastfetched", 
Long.toString(System.currentTimeMillis()));

                return true;
        }
@@ -519,7 +555,8 @@
        private String popNextSlot() {
                String slot = this.contactfile.get("nextslot");
                if (slot == null) {
-                       slot = this.getInitialSlot();
+                       Logger.error(this, "Contact has no 'nextslot' prop! 
This shouldn't happen!");
+                       return null;
                }
                SHA256Digest sha256 = new SHA256Digest();
                sha256.update(Base32.decode(slot), 0, 
Base32.decode(slot).length);
@@ -601,27 +638,23 @@
                try {
                        this.sendQueued();
                        this.pollAcks();
-                       try {
-                               this.checkCTS();
-                       } catch (OutboundContactFatalException fe) {
-                       }
+                       this.checkCTS();
+               } catch (OutboundContactFatalException fe) {
+                       Logger.error(this, "Fatal exception on outbound 
contact: "+fe.getMessage()+". This contact in invalid.");
+                       // TODO: probably bounce all the messages and delete 
the contact.
                } catch (ConnectionTerminatedException cte) {
                        // just exit
                }
        }

-       private void sendQueued() throws ConnectionTerminatedException {
+       private void sendQueued() throws ConnectionTerminatedException, 
OutboundContactFatalException {
                boolean ready;
                String ctstatus = this.contactfile.get("status");
                if (ctstatus == null) ctstatus = "notsent";
                if (ctstatus.equals("rts-sent") || 
ctstatus.equals("cts-received")) {
                        ready = true;
                } else {
-                       try {
-                               ready = this.init();
-                       } catch (OutboundContactFatalException fe) {
-                               ready = false;
-                       }
+                       ready = this.init();
                }

                HighLevelFCPClient fcpcli = null;
@@ -690,7 +723,7 @@
                }
        }

-       private void pollAcks() throws ConnectionTerminatedException {
+       private void pollAcks() throws ConnectionTerminatedException, 
OutboundContactFatalException {
                HighLevelFCPClient fcpcli = null;
                QueuedMessage[] msgs = this.getSendQueue();

@@ -733,11 +766,12 @@
                                                                +"If you 
believe this is likely, try resending the message.", true);
                                                Logger.normal(this,"Giving up 
on message - been trying for too long.");
                                                msgs[i].delete();
-                                       } else if (System.currentTimeMillis() > 
msgs[i].last_send_time + RETRANSMIT_DELAY) {
-                                               // no ack yet - retransmit on 
another slot
-                                               msgs[i].slot = 
this.popNextSlot();
-                                               // mark for re-insertion
-                                               msgs[i].last_send_time = -1;
+                                       } else if (System.currentTimeMillis() > 
msgs[i].last_send_time + RTS_RETRANSMIT_DELAY) {
+                                               Logger.normal(this, "Resending 
RTS for contact");
+                                               init();
+                                               // bit of a fudge - this won't 
actually be the last send time, since we won't re-send messages at all now,
+                                               // it will be the last time the 
RTS was sent.
+                                               msgs[i].last_send_time = 
System.currentTimeMillis();
                                                msgs[i].saveProps();
                                        }
                                }


Reply via email to