Merged
authorjonathandicarlo@jonathan-dicarlos-macbook-pro.local
Thu, 26 Jun 2008 17:01:12 -0700
changeset 44787 f6bff896df6ff6b8bc3aa5513eea8de9743e8a78
parent 44786 bc621930f193ce578ba098667dc24f89e3a55f23 (current diff)
parent 44784 03b5bb5b81bd4c8fc107bb5b0f27261f5161c35f (diff)
child 44789 5d7003b9641067711999739ca10779c37455bf99
child 44790 1ca8407589c0098910a0e3e6686194773c666dec
push idunknown
push userunknown
push dateunknown
Merged
services/sync/services-sync.js
--- a/services/crypto/WeaveCrypto.cpp
+++ b/services/crypto/WeaveCrypto.cpp
@@ -45,16 +45,22 @@
 #include "plbase64.h"
 #include "prmem.h"
 #include "secerr.h"
 
 #include "pk11func.h"
 #include "keyhi.h"
 #include "nss.h"
 
+/*
+ * In a number of places we use stack buffers to hold smallish temporary data.
+ * 4K is plenty big for the exptected uses, and avoids poking holes in the
+ * heap for small allocations. (Yes, we still check for overflow.)
+ */
+#define STACK_BUFFER_SIZE 4096
 
 NS_IMPL_ISUPPORTS1(WeaveCrypto, IWeaveCrypto)
 
 WeaveCrypto::WeaveCrypto() :
   mAlgorithm(SEC_OID_AES_256_CBC),
   mKeypairBits(2048)
 {
 }
@@ -268,19 +274,19 @@ WeaveCrypto::CommonCrypt(const char *inp
 {
   nsresult rv = NS_OK;
   PK11SymKey   *symKey  = nsnull;
   PK11Context  *ctx     = nsnull;
   PK11SlotInfo *slot    = nsnull;
   SECItem      *ivParam = nsnull;
   PRUint32 maxOutputSize;
 
-  char keyData[aSymmetricKey.Length()];
+  char keyData[STACK_BUFFER_SIZE];
   PRUint32 keyDataSize = sizeof(keyData);
-  char ivData[aIV.Length()];
+  char ivData[STACK_BUFFER_SIZE];
   PRUint32 ivDataSize = sizeof(ivData);
 
   rv = DecodeBase64(aSymmetricKey, keyData, &keyDataSize);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = DecodeBase64(aIV, ivData, &ivDataSize);
   NS_ENSURE_SUCCESS(rv, rv);
 
   SECItem keyItem = {siBuffer, (unsigned char*)keyData, keyDataSize}; 
@@ -463,18 +469,18 @@ WeaveCrypto::DeriveKeyFromPassphrase(con
                                      const nsACString& aSalt,
                                      PK11SymKey **aSymKey)
 {
   nsresult rv;
 
   PromiseFlatCString fPass(aPassphrase);
   SECItem passphrase = {siBuffer, (unsigned char *)fPass.get(), fPass.Length()}; 
 
-  char saltBytes[aSalt.Length()];
-  PRUint32 saltBytesLength = aSalt.Length();
+  char saltBytes[STACK_BUFFER_SIZE];
+  PRUint32 saltBytesLength = sizeof(saltBytes);
   rv = DecodeBase64(aSalt, saltBytes, &saltBytesLength);
   NS_ENSURE_SUCCESS(rv, rv);
   SECItem salt = {siBuffer, (unsigned char*)saltBytes, saltBytesLength}; 
 
   // http://mxr.mozilla.org/seamonkey/source/security/nss/lib/pk11wrap/pk11pbe.c#1261
 
   // Bug 436577 prevents us from just using SEC_OID_PKCS5_PBKDF2 here
   SECOidTag pbeAlg = mAlgorithm;
@@ -526,17 +532,17 @@ WeaveCrypto::WrapPrivateKey(SECKEYPrivat
   nsresult rv;
   SECStatus s;
   PK11SymKey *pbeKey = nsnull;
 
   // Convert our passphrase to a symkey and get the IV in the form we want.
   rv = DeriveKeyFromPassphrase(aPassphrase, aSalt, &pbeKey);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  char ivData[aIV.Length()];
+  char ivData[STACK_BUFFER_SIZE];
   PRUint32 ivDataSize = sizeof(ivData);
   rv = DecodeBase64(aIV, ivData, &ivDataSize);
   NS_ENSURE_SUCCESS(rv, rv);
   SECItem ivItem  = {siBuffer, (unsigned char*)ivData,  ivDataSize}; 
 
   // AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
   CK_MECHANISM_TYPE wrapMech = PK11_AlgtagToMechanism(mAlgorithm);
   wrapMech = PK11_GetPadMechanism(wrapMech);
@@ -549,17 +555,17 @@ WeaveCrypto::WrapPrivateKey(SECKEYPrivat
   if (!ivParam) {
     NS_WARNING("Couldn't create IV param");
     return NS_ERROR_FAILURE;
   }
 
 
   // Use a stack buffer to hold the wrapped key. NSS says about 1200 bytes for
   // a 2048-bit RSA key, so our 4096 byte buffer should be plenty.
-  unsigned char stackBuffer[4096];
+  unsigned char stackBuffer[STACK_BUFFER_SIZE];
   SECItem wrappedKey = {siBuffer, stackBuffer, sizeof(stackBuffer)}; 
 
   s = PK11_WrapPrivKey(aPrivateKey->pkcs11Slot,
                        pbeKey, aPrivateKey,
                        wrapMech, ivParam,
                        &wrappedKey, nsnull);
 
   SECITEM_FreeItem(ivParam, PR_TRUE);
@@ -603,17 +609,20 @@ WeaveCrypto::EncodePublicKey(SECKEYPubli
 /*
  * GenerateRandomBytes
  */
 NS_IMETHODIMP
 WeaveCrypto::GenerateRandomBytes(PRUint32 aByteCount,
                                  nsACString& aEncodedBytes)
 {
   nsresult rv;
-  char random[aByteCount];
+  char random[STACK_BUFFER_SIZE];
+
+  if (aByteCount > STACK_BUFFER_SIZE)
+    return NS_ERROR_OUT_OF_MEMORY;
 
   rv = PK11_GenerateRandom((unsigned char *)random, aByteCount);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = EncodeBase64(random, aByteCount, aEncodedBytes);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
@@ -626,17 +635,20 @@ WeaveCrypto::GenerateRandomBytes(PRUint3
 NS_IMETHODIMP
 WeaveCrypto::GenerateRandomIV(nsACString& aEncodedBytes)
 {
   nsresult rv;
 
   CK_MECHANISM_TYPE mech = PK11_AlgtagToMechanism(mAlgorithm);
   PRUint32 size = PK11_GetIVLength(mech);
 
-  char random[size];
+  char random[STACK_BUFFER_SIZE];
+
+  if (size > STACK_BUFFER_SIZE)
+    return NS_ERROR_OUT_OF_MEMORY;
  
   rv = PK11_GenerateRandom((unsigned char *)random, size);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = EncodeBase64(random, size, aEncodedBytes);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
@@ -737,29 +749,29 @@ WeaveCrypto::WrapSymmetricKey(const nsAC
   PK11SlotInfo *slot = nsnull;
   PK11SymKey *symKey = nsnull;
   SECKEYPublicKey *pubKey = nsnull;
   CERTSubjectPublicKeyInfo *pubKeyInfo = nsnull;
   CK_MECHANISM_TYPE keyMech, wrapMech;
 
   // Step 1. Get rid of the base64 encoding on the inputs.
 
-  char publicKeyBuffer[aPublicKey.Length()];
-  PRUint32 publicKeyBufferSize = aPublicKey.Length();
+  char publicKeyBuffer[STACK_BUFFER_SIZE];
+  PRUint32 publicKeyBufferSize = sizeof(publicKeyBuffer);
   rv = DecodeBase64(aPublicKey, publicKeyBuffer, &publicKeyBufferSize);
   NS_ENSURE_SUCCESS(rv, rv);
   SECItem pubKeyData = {siBuffer, (unsigned char *)publicKeyBuffer, publicKeyBufferSize};
 
-  char symKeyBuffer[aSymmetricKey.Length()];
-  PRUint32 symKeyBufferSize = aSymmetricKey.Length();
+  char symKeyBuffer[STACK_BUFFER_SIZE];
+  PRUint32 symKeyBufferSize = sizeof(symKeyBuffer);
   rv = DecodeBase64(aSymmetricKey, symKeyBuffer, &symKeyBufferSize);
   NS_ENSURE_SUCCESS(rv, rv);
   SECItem symKeyData = {siBuffer, (unsigned char *)symKeyBuffer, symKeyBufferSize};
 
-  char wrappedBuffer[4096];
+  char wrappedBuffer[STACK_BUFFER_SIZE];
   SECItem wrappedKey = {siBuffer, (unsigned char *)wrappedBuffer, sizeof(wrappedBuffer)};
 
 
   // Step 2. Put the symmetric key bits into a P11 key object.
 
   slot = PK11_GetInternalSlot();
   if (!slot) {
     NS_WARNING("Can't get internal PK11 slot");
@@ -864,34 +876,34 @@ WeaveCrypto::UnwrapSymmetricKey(const ns
   SECItem *keyID = nsnull;
 
   CK_ATTRIBUTE_TYPE privKeyUsage[] = { CKA_UNWRAP };
   PRUint32 privKeyUsageLength = sizeof(privKeyUsage) / sizeof(CK_ATTRIBUTE_TYPE);
 
 
   // Step 1. Get rid of the base64 encoding on the inputs.
 
-  char privateKeyBuffer[aWrappedPrivateKey.Length()];
-  PRUint32 privateKeyBufferSize = aWrappedPrivateKey.Length();
+  char privateKeyBuffer[STACK_BUFFER_SIZE];
+  PRUint32 privateKeyBufferSize = sizeof(privateKeyBuffer);
   rv = DecodeBase64(aWrappedPrivateKey, privateKeyBuffer, &privateKeyBufferSize);
   NS_ENSURE_SUCCESS(rv, rv);
   SECItem wrappedPrivKey = {siBuffer, (unsigned char *)privateKeyBuffer, privateKeyBufferSize};
 
-  char wrappedKeyBuffer[aWrappedSymmetricKey.Length()];
-  PRUint32 wrappedKeyBufferSize = aWrappedSymmetricKey.Length();
+  char wrappedKeyBuffer[STACK_BUFFER_SIZE];
+  PRUint32 wrappedKeyBufferSize = sizeof(wrappedKeyBuffer);
   rv = DecodeBase64(aWrappedSymmetricKey, wrappedKeyBuffer, &wrappedKeyBufferSize);
   NS_ENSURE_SUCCESS(rv, rv);
   SECItem wrappedSymKey = {siBuffer, (unsigned char *)wrappedKeyBuffer, wrappedKeyBufferSize};
 
 
   // Step 2. Convert the passphrase to a symmetric key and get the IV in the proper form.
   rv = DeriveKeyFromPassphrase(aPassphrase, aSalt, &pbeKey);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  char ivData[aIV.Length()];
+  char ivData[STACK_BUFFER_SIZE];
   PRUint32 ivDataSize = sizeof(ivData);
   rv = DecodeBase64(aIV, ivData, &ivDataSize);
   NS_ENSURE_SUCCESS(rv, rv);
   SECItem ivItem  = {siBuffer, (unsigned char*)ivData,  ivDataSize}; 
 
   // AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
   CK_MECHANISM_TYPE wrapMech = PK11_AlgtagToMechanism(mAlgorithm);
   wrapMech = PK11_GetPadMechanism(wrapMech);
--- a/services/sync/locales/en-US/wizard.dtd
+++ b/services/sync/locales/en-US/wizard.dtd
@@ -1,89 +1,99 @@
-<!ENTITY serverError1.description         "Server Error:  ">
-<!ENTITY serverError2.description         ",  ">
-
-<!ENTITY wizard.title                     "Weave Setup">
-
-<!ENTITY intro.title                      "Welcome to Weave">
-<!ENTITY intro.description                "[description of Weave]">
-<!ENTITY intro-more.label                 "Learn more">
-<!ENTITY intro-more.link                  "http://labs.mozilla.com">
-
-<!ENTITY welcome.title			  "Installation Type">
-<!ENTITY curUser-title.label              "Sign In">
-<!ENTITY curUser.description              "Sign into your existing account to set up Weave on this computer.">
-<!ENTITY newUser-title.label              "Get Started">
-<!ENTITY newUser.description              "Create a new account.">
-
-<!ENTITY verify.title                     "Account Verification (Step 1 of 3)">
-<!ENTITY username.label                   "User name:">
-<!ENTITY password.label                   "Password:">
-<!ENTITY passphrase.label                 "Passphrase:">
-<!ENTITY reminder.label                   "Forgot your password? ">
-<!ENTITY unverified.label                 "Unverified">
-<!ENTITY recovery.link                    "https://sm-labs01.mozilla.org:81/client/forgot.php">
-
-<!ENTITY create.title                     "Create Account (Step 1 of 5)">
-<!ENTITY createUsername.label             "Desired login name:">
-<!ENTITY createUsernameHint.label         "Examples: Maria, Maria.Emerson">
-<!ENTITY createPassword.label             "Choose a password:">
-<!ENTITY createPasswordHint.label         "Minimum of 5 characters in length.">
-<!ENTITY createReenterPassword.label      "Re-enter password:">
-<!ENTITY createEmail.label                "Current email address:">
-<!ENTITY createEmailHint.label            "e.g. yourname@domain.com">
-
-<!ENTITY create2.title                    "Create Account (Step 2 of 5)">
-
-<!ENTITY passphrase.description           "You must also now choose an encryption passphrase that is different from your password. This will be used to protect your data on the server."> 
-<!ENTITY passphrase-more.label            "Learn More">
-<!ENTITY passphrase-more.link             "http://labs.mozilla.com">
-
-<!ENTITY reenterPassphrase.label          "Re-enter passphrase:">
-<!ENTITY moreInfo.label                   "More information on choosing a phrase.">
-
-<!ENTITY create3.title                    "Create Account (Step 3 of 5)">
-
-<!ENTITY instanceName.description         "Name this device... explain why... ">
-<!ENTITY instanceName.label               "Device name:">
-<!ENTITY examples.label                   "Examples: &quot;Home computer&quot;, &quot;Mobile phone&quot;, &quot;Work laptop&quot;"> 
-
-<!ENTITY data.description                 "Choose the data that you would like Weave to store for you.">
-<!ENTITY bookmarks.label                  "Bookmarks">
-<!ENTITY history.label                    "Browsing History">
-<!ENTITY cookies.label                    "Cookies">
-<!ENTITY passwords.label                  "Saved Passwords">
-<!ENTITY tabs.label                       "Tabs">
-<!ENTITY formdata.label                   "Saved Form Data">
-
-<!ENTITY captcha.label                    "Type the characters you see in the image below:">
-<!ENTITY captchaDirections.label          "">
-<!ENTITY reloadCaptcha.text               "Reload">
-<!ENTITY captchaHint.label                "Letters are not case-sensitive.">
-
-<!ENTITY terms.label                      "Terms of Service">
-<!ENTITY acceptTerms.label                "I have read and accept the Terms of Service.">
-
-<!ENTITY final.description                "Your account will be created with the following preferences [this screen in progress]">
-
-<!ENTITY initialLogin.label               "Signing you in.">
-<!ENTITY initialPrefs.label               "Setting your preferences.">
-<!ENTITY initialReset.label               "Clearing your default bookmarks.">
-<!ENTITY initialSync.label                "Synchronizing your data.">
-<!ENTITY finalStep1Finished.label         "Signed in.">
-<!ENTITY finalStep2.label                 "Synchronizing data...">
-<!ENTITY finalStep3Finished.label         "Data synchronized.">
+<!ENTITY wizard.title                     "Weave Setup">
+
+<!ENTITY intro.title                      "Welcome to Weave">
+<!ENTITY intro-weave.description          "Weave is an experimental prototype from Mozilla Labs that integrates online services with Firefox. ">
+<!ENTITY intro-more.label                 "Learn more about the Weave project">
+<!ENTITY intro-more.link                  "http://labs.mozilla.com">
+<!ENTITY intro-warning.description        "Warning: Use at your own risk! Backup Firefox data before continuing with installation.">
+
+<!ENTITY eula.title                       "Software License Agreement">
+<!ENTITY eula.description                 "Terms and conditions for using this software.">
+<!ENTITY eula.accept                      "I accept the terms of the License Agreement">
+<!ENTITY eula.decline                     "I do NOT accept the terms of the License Agreement">
+
+<!ENTITY welcome.title			  "Installation Type">
+<!ENTITY curUser-title.label              "Sign In">
+<!ENTITY curUser.description              "Sign into your existing account to set up Weave on this computer.">
+<!ENTITY newUser-title.label              "Get Started">
+<!ENTITY newUser.description              "Create a new account and upload your Weave data to the server.">
+
+<!ENTITY verify.title                     "Account Verification (Step 1 of 3)">
+<!ENTITY username.label                   "User name:">
+<!ENTITY password.label                   "Password:">
+<!ENTITY passphrase.label                 "Passphrase:">
+<!ENTITY reminder.label                   "Forgot your password? ">
+<!ENTITY unverified.label                 "Unverified">
+<!ENTITY recovery.link                    "https://sm-labs01.mozilla.org:81/client/forgot.php">
+
+<!ENTITY create.title                     "Create Account (Step 1 of 5)">
+<!ENTITY createUsername.label             "Desired login name:">
+<!ENTITY createUsernameHint.label         "Examples: Maria, Maria.Emerson">
+<!ENTITY createPassword.label             "Choose a password:">
+<!ENTITY createPasswordHint.label         "Minimum of 5 characters in length.">
+<!ENTITY createReenterPassword.label      "Re-enter password:">
+<!ENTITY createEmail.label                "Current email address:">
+<!ENTITY createEmailHint.label            "e.g. yourname@domain.com">
+
+<!ENTITY create2.title                    "Create Account (Step 2 of 5)">
+
+<!ENTITY passphrase.description           "You must also now choose an encryption passphrase that is different from your password. This will be used to protect your data on the server."> 
+<!ENTITY passphrase-more.label            "Learn More">
+<!ENTITY passphrase-more.link             "http://labs.mozilla.com">
+
+<!ENTITY reenterPassphrase.label          "Re-enter passphrase:">
+<!ENTITY moreInfo.label                   "More information on choosing a phrase.">
+
+<!ENTITY create3.title                    "Create Account (Step 3 of 5)">
+
+<!ENTITY instanceName.description         "Choose a name for this device for Weave">
+<!ENTITY instanceName.label               "Device name:">
+<!ENTITY examples.label                   "Examples: &quot;Home computer&quot;, &quot;Mobile phone&quot;, &quot;Work laptop&quot;"> 
+
+<!ENTITY data.description                 "Choose the data that you would like Weave to store for you.">
+<!ENTITY bookmarks.label                  "Bookmarks">
+<!ENTITY history.label                    "Browsing History">
+<!ENTITY cookies.label                    "Cookies">
+<!ENTITY passwords.label                  "Saved Passwords">
+<!ENTITY tabs.label                       "Tabs">
+<!ENTITY formdata.label                   "Saved Form Data">
+
+<!ENTITY captcha.label                    "Type the characters you see in the image below:">
+<!ENTITY captchaDirections.label          "">
+<!ENTITY reloadCaptcha.text               "Reload">
+<!ENTITY captchaHint.label                "Letters are not case-sensitive.">
+
+<!ENTITY terms.label                      "Terms of Service">
+<!ENTITY acceptTerms.label                "I have read and accept the Terms of Service.">
+
+<!ENTITY final.description                "Weave perform an initial synchronization with the following settings. Continue to the next wizard page to accept.">
+
+<!ENTITY initialLogin.label               "Signing you in.">
+<!ENTITY initialPrefs.label               "Setting your preferences.">
+<!ENTITY initialReset.label               "Clearing your default bookmarks.">
+<!ENTITY initialSync.label                "Synchronizing your data.">
+<!ENTITY finalStep1Finished.label         "Signed in.">
+<!ENTITY finalStep2.label                 "Synchronizing data...">
+<!ENTITY finalStep3Finished.label         "Data synchronized.">
 
 <!ENTITY thankyou.title                   "Thank you!">
-<!ENTITY thankyou.description             "You successfully installed Weave. etc... ">
+<!ENTITY thankyou.description             "Weave has been installed on this device and your data has been synchronized. To change your Weave preferences, use the Weave tab of your browser preferences. ">
+
+
+<!ENTITY final-pref-title.description     "Preferences">
+<!ENTITY final-account-title.description  "Account">
+<!ENTITY final-sync-title.description     "Initial Synchronization">
+
+<!ENTITY userCheckFailed1.description     "Our server is having problems and we couldn't check that username. ">
+<!ENTITY userCheckFailed2.description     " to try again, or click &quot;Done&quot; to exit the setup wizard and try again later.">
 
-<!ENTITY final-pref-title.description     "Preferences">
<!ENTITY final-account-title.description  "Account">
<!ENTITY final-sync-title.description     "Initial Synchronization">
-<!ENTITY userCheckFailed1.description     "Our server is having problems and we couldn't check that username. ">
-<!ENTITY userCheckFailed2.description     " to try again, or click &quot;Done&quot; to exit the setup wizard and try again later.">
-
-<!ENTITY finished.description             "Good job. You installed Weave. You can change preferences in the Weave tab...">
-<!ENTITY initialLoginFailed1.description  "Our server is having problems and we couldn't log you in. ">
-<!ENTITY initialLoginFailed2.description  " to try again, or click &quot;Done&quot; to exit the setup wizard and try again later.">
-<!ENTITY initialSyncFailed1.description   "Our server is having problems and we couldn't synchronize your data. ">
-<!ENTITY initialSyncFailed2.description   " to try again, or click &quot;Done&quot; to exit the setup wizard and try again later."> 
-
-<!ENTITY clickHere.text                   "Click here">
-<!ENTITY tryAgain.text                    "Try again">
+<!ENTITY finished.description             "Good job. You installed Weave. You can change preferences in the Weave tab...">
+<!ENTITY initialLoginFailed1.description  "Our server is having problems and we couldn't log you in. ">
+<!ENTITY initialLoginFailed2.description  " to try again, or click &quot;Done&quot; to exit the setup wizard and try again later.">
+<!ENTITY initialSyncFailed1.description   "Our server is having problems and we couldn't synchronize your data. ">
+<!ENTITY initialSyncFailed2.description   " to try again, or click &quot;Done&quot; to exit the setup wizard and try again later."> 
+
+<!ENTITY clickHere.text                   "Click here">
+<!ENTITY tryAgain.text                    "Try again">
+
+<!ENTITY serverError1.description         "Server Error:  ">
+<!ENTITY serverError2.description         ",  ">
--- a/services/sync/locales/en-US/wizard.properties
+++ b/services/sync/locales/en-US/wizard.properties
@@ -35,17 +35,17 @@ create-mail-inuse.label         = Emali 
 create-captcha-missing.label    = Captcha response missing
 create-password-missing.label   = Password missing
 create-password-incorrect.label = Password incorrect
 
 create-success.label            = Account for %S created.
 
 final-pref-value.label          = %S    
 final-account-value.label       = Username: %S
-final-sync-value.label          = [Explain that a sync will happen]
+final-sync-value.label          = Weave will upload your data to the server.
 final-success.label             = Weave was successfully installed.
 
 default-name.label              = %S's Firefox
 default-name-nouser.label       = Firefox
 
 bookmarks.label                 = Bookmarks
 history.label                   = Browsing History
 cookies.label                   = Cookies
--- a/services/sync/modules/async.js
+++ b/services/sync/modules/async.js
@@ -187,16 +187,18 @@ Generator.prototype = {
           if (e.asyncStack.indexOf(formatAsyncFrame(this._initFrame)) == -1)
             e.addAsyncFrame(this._initFrame);
         } else {
           e = new AsyncException(this.asyncStack, e);
         }
 
       this._exception = e;
 
+    } else if (e.message && e.message == 'Cannot acquire lock (internal lock)') {
+      this._log.warn("Exception: " + Utils.exceptionStr(e));
     } else {
       this._log.error("Exception: " + Utils.exceptionStr(e));
       this._log.debug("Stack trace:\n" + Utils.stackTrace(e));
     }
 
     // continue execution of caller.
     // in the case of StopIteration we could return an error right
     // away, but instead it's easiest/best to let the caller handle
--- a/services/sync/modules/dav.js
+++ b/services/sync/modules/dav.js
@@ -247,19 +247,16 @@ DAVCollection.prototype = {
     let headers = {'Content-type': 'text/xml; charset="utf-8"',
                    'Depth': '0'};
     headers.__proto__ = this._defaultHeaders;
     return this._makeRequest.async(this, onComplete, "PROPFIND", path,
                                    headers, data);
   },
 
   LOCK: function DC_LOCK(path, data, onComplete) {
-    if (!this._lockAllowed)
-      throw "Cannot acquire lock (internal lock)";
-
     let headers = {'Content-type': 'text/xml; charset="utf-8"',
                    'Depth': 'infinity',
                    'Timeout': 'Second-600'};
     headers.__proto__ = this._defaultHeaders;
     return this._makeRequest.async(this, onComplete, "LOCK", path, headers, data);
   },
 
   UNLOCK: function DC_UNLOCK(path, onComplete) {
@@ -314,24 +311,17 @@ DAVCollection.prototype = {
     if (lock)
       headers['If'] = "<" + lock.URL + "> (<" + lock.token + ">)";
 
     // Make a call to make sure it's working
     this._makeRequest.async(this, self.cb, "GET", "", headers);
     let resp = yield;
 
     this._log.debug("checkLogin got response status " + resp.status);
-    // XXX would be nice if 404 == invalid username, 401 == invalid password.
-    let retmsg = "";
-    if (resp.status == 401)
-      retmsg = "invalid username or password";
-    else if (resp.status < 200 || resp.status >= 300)
-      retmsg = "server error";
-
-    self.done(retmsg);
+    self.done(resp.status);
   },
 
   // Locking
 
   _getActiveLock: function DC__getActiveLock() {
     let self = yield;
     let ret = null;
 
@@ -359,50 +349,59 @@ DAVCollection.prototype = {
       this._log.trace("No active lock token found");
     self.done({URL: this._baseURL, token: ret});
   },
 
   lock: function DC_lock() {
     let self = yield;
 
     this._log.trace("Acquiring lock");
+    if (!this._lockAllowed)
+      throw {message: "Cannot acquire lock (internal lock)"};
+    this._lockAllowed = false;
 
     if (DAVLocks['default']) {
       this._log.debug("Lock called, but we already hold a token");
+      this._lockAllowed = true;
       self.done();
       return;
     }
 
     this.LOCK("lock",
               "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
               "<D:lockinfo xmlns:D=\"DAV:\">\n" +
               "  <D:locktype><D:write/></D:locktype>\n" +
               "  <D:lockscope><D:exclusive/></D:lockscope>\n" +
               "</D:lockinfo>", self.cb);
     let resp = yield;
 
-    if (resp.status < 200 || resp.status >= 300)
+    if (resp.status < 200 || resp.status >= 300) {
+      this._lockAllowed = true;
       return;
+    }
 
     let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
     let token = tokens.iterateNext();
     if (token) {
       DAVLocks['default'] = {
         URL: this._baseURL,
         token: token.textContent
       };
     }
 
     if (!DAVLocks['default']) {
       this._log.warn("Could not acquire lock");
+      this._lockAllowed = true;
       self.done();
       return;
     }
 
     this._log.trace("Lock acquired");
+    this._lockAllowed = true;
+
     self.done(DAVLocks['default']);
   },
 
   unlock: function DC_unlock() {
     let self = yield;
 
     this._log.trace("Releasing lock");
 
--- a/services/sync/modules/engines/cookies.js
+++ b/services/sync/modules/engines/cookies.js
@@ -1,8 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Jono DiCarlo <jdicarlo@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 const EXPORTED_SYMBOLS = ['CookieEngine', 'CookieTracker', 'CookieStore'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/engines.js");
--- a/services/sync/modules/engines/forms.js
+++ b/services/sync/modules/engines/forms.js
@@ -1,21 +1,109 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Anant Narayanan <anant@kix.in>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 const EXPORTED_SYMBOLS = ['FormEngine'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/util.js");
 Cu.import("resource://weave/engines.js");
 Cu.import("resource://weave/syncCores.js");
 Cu.import("resource://weave/stores.js");
 Cu.import("resource://weave/trackers.js");
 
+/*
+ * Generate GUID from a name,value pair.
+ * If the concatenated length is less than 40, we just Base64 the JSON.
+ * Otherwise, we Base64 the JSON of the name and SHA1 of the value.
+ * The first character of the key determines which method we used:
+ * '0' for full Base64, '1' for SHA-1'ed val.
+ */
+function _generateFormGUID(nam, val) {
+  var key;
+  var con = nam + val;
+  
+  var jso = Cc["@mozilla.org/dom/json;1"].
+            createInstance(Ci.nsIJSON);
+
+  if (con.length <= 40) {
+    key = '0' + btoa(jso.encode([nam, val]));
+  } else {
+    val = Utils.sha1(val);
+    key = '1' + btoa(jso.encode([nam, val]));
+  }
+  
+  return key;
+}
+
+/*
+ * Unwrap a name,value pair from a GUID.
+ * Return an array [sha1ed, name, value]
+ * sha1ed is a boolean determining if the value is SHA-1'ed or not.
+ */
+function _unwrapFormGUID(guid) {
+  var jso = Cc["@mozilla.org/dom/json;1"].
+            createInstance(Ci.nsIJSON);
+  
+  var ret;
+  var dec = atob(guid.slice(1));
+  var obj = jso.decode(dec);
+  
+  switch (guid[0]) {
+    case '0':
+      ret = [false, obj[0], obj[1]];
+      break;
+    case '1':
+      ret = [true, obj[0], obj[1]];
+      break;
+    default:
+      this._log.warn("Unexpected GUID header: " + guid[0] + ", aborting!");
+      return false;
+  }
+  
+  return ret;
+}
+
 function FormEngine(pbeId) {
   this._init(pbeId);
 }
 FormEngine.prototype = {
   get name() { return "forms"; },
   get logName() { return "FormEngine"; },
   get serverPrefix() { return "user-data/forms/"; },
 
@@ -66,17 +154,17 @@ FormSyncCore.prototype = {
     var found = false;
     var stmnt = this._formDB.createStatement("SELECT * FROM moz_formhistory");
 
     /* Same performance restrictions as PasswordSyncCore apply here:
        caching required */
     while (stmnt.executeStep()) {
       var nam = stmnt.getUTF8String(1);
       var val = stmnt.getUTF8String(2);
-      var key = Utils.sha1(nam + val);
+      var key = _generateFormGUID(nam, val);
 
       if (key == GUID)
         found = true;
     }
 
     return found;
   },
 
@@ -110,39 +198,71 @@ FormStore.prototype = {
   __formHistory: null,
   get _formHistory() {
     if (!this.__formHistory)
       this.__formHistory = Cc["@mozilla.org/satchel/form-history;1"].
                            getService(Ci.nsIFormHistory2);
     return this.__formHistory;
   },
 
+  _getValueFromSHA1: function FormStore__getValueFromSHA1(name, sha) {
+    var query = "SELECT value FROM moz_formhistory WHERE fieldname = '" + name + "'";
+    var stmnt = this._formDB.createStatement(query);
+    var found = false;
+    
+    while (stmnt.executeStep()) {
+      var val = stmnt.getUTF8String(0);
+      if (Utils.sha1(val) == sha) {
+        found = val;
+        break;
+      }
+    }
+    return found;
+  },
+  
   _createCommand: function FormStore__createCommand(command) {
-    this._log.info("FormStore got createCommand: " + command );
+    this._log.info("FormStore got createCommand: " + command);
     this._formHistory.addEntry(command.data.name, command.data.value);
   },
 
   _removeCommand: function FormStore__removeCommand(command) {
-    this._log.info("FormStore got removeCommand: " + command );
-    this._formHistory.removeEntry(command.data.name, command.data.value);
+    this._log.info("FormStore got removeCommand: " + command);
+    
+    var data = _unwrapFormGUID(command.GUID);
+    if (!data) {
+      this._log.warn("Invalid GUID found, ignoring remove request.");
+      return;
+    }
+    
+    var nam = data[1];
+    var val = data[2];
+    if (data[0]) {
+      val = this._getValueFromSHA1(nam, val);
+    }
+    
+    if (val) {
+      this._formHistory.removeEntry(nam, val);
+    } else {
+      this._log.warn("Form value not found from GUID, ignoring remove request.");
+    }
   },
 
   _editCommand: function FormStore__editCommand(command) {
-    this._log.info("FormStore got editCommand: " + command );
+    this._log.info("FormStore got editCommand: " + command);
     this._log.warn("Form syncs are expected to only be create/remove!");
   },
 
   wrap: function FormStore_wrap() {
     var items = [];
     var stmnt = this._formDB.createStatement("SELECT * FROM moz_formhistory");
 
     while (stmnt.executeStep()) {
       var nam = stmnt.getUTF8String(1);
       var val = stmnt.getUTF8String(2);
-      var key = Utils.sha1(nam + val);
+      var key = _generateFormGUID(nam, val);
 
       items[key] = { name: nam, value: val };
     }
 
     return items;
   },
 
   wipe: function FormStore_wipe() {
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -1,8 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Dan Mills <thunder@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 const EXPORTED_SYMBOLS = ['HistoryEngine'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/util.js");
--- a/services/sync/modules/engines/passwords.js
+++ b/services/sync/modules/engines/passwords.js
@@ -1,8 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Justin Dolske <dolske@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 const EXPORTED_SYMBOLS = ['PasswordEngine'];
 
 const Cu = Components.utils;
 
 Cu.import("resource://weave/util.js");
 Cu.import("resource://weave/engines.js");
 Cu.import("resource://weave/syncCores.js");
 Cu.import("resource://weave/stores.js");
--- a/services/sync/modules/engines/tabs.js
+++ b/services/sync/modules/engines/tabs.js
@@ -1,8 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Myk Melez <myk@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 const EXPORTED_SYMBOLS = ['TabEngine'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://weave/util.js");
 Cu.import("resource://weave/async.js");
@@ -72,21 +108,21 @@ TabSyncCore.prototype = {
     // window-specific tabs look the same when identified by URL.
 
     // Get the set of all real and virtual tabs.
     let tabs = this._engine.store.wrap();
 
     // XXX Should we convert both to nsIURIs and then use nsIURI::equals
     // to compare them?
     if (GUID in tabs) {
-      this._log.debug("_itemExists: " + GUID + " exists");
+      this._log.trace("_itemExists: " + GUID + " exists");
       return true;
     }
 
-    this._log.debug("_itemExists: " + GUID + " doesn't exist");
+    this._log.trace("_itemExists: " + GUID + " doesn't exist");
     return false;
   },
 
   _commandLike: function TSC_commandLike(a, b) {
     // Not implemented.
     return false;
   }
 };
@@ -328,32 +364,32 @@ TabStore.prototype = {
     let session = this._json.decode(this._sessionStore.getBrowserState());
 
     for (let i = 0; i < session.windows.length; i++) {
       let window = session.windows[i];
       // For some reason, session store uses one-based array index references,
       // (f.e. in the "selectedWindow" and each tab's "index" properties), so we
       // convert them to and from JavaScript's zero-based indexes as needed.
       let windowID = i + 1;
-      this._log.debug("_wrapRealTabs: window " + windowID);
+      this._log.trace("_wrapRealTabs: window " + windowID);
       for (let j = 0; j < window.tabs.length; j++) {
         let tab = window.tabs[j];
 
 	// The session history entry for the page currently loaded in the tab.
 	// We use the URL of the current page as the ID for the tab.
 	let currentEntry = tab.entries[tab.index - 1];
 
 	if (!currentEntry || !currentEntry.url) {
 	  this._log.warn("_wrapRealTabs: no current entry or no URL, can't " +
                          "identify " + this._json.encode(tab));
 	  continue;
 	}
 
 	let tabID = currentEntry.url;
-        this._log.debug("_wrapRealTabs: tab " + tabID);
+        this._log.trace("_wrapRealTabs: tab " + tabID);
 
         // The ID property of each entry in the tab, which I think contains
         // nsISHEntry::ID, changes every time session store restores the tab,
         // so we can't sync them, or we would generate edit commands on every
         // restart (even though nothing has actually changed).
         for each (let entry in tab.entries)
           delete entry.ID;
 
--- a/services/sync/modules/faultTolerance.js
+++ b/services/sync/modules/faultTolerance.js
@@ -1,8 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Atul Varma <varmaa@toolness.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 const Cu = Components.utils;
 Cu.import("resource://weave/log4moz.js");
 
 const EXPORTED_SYMBOLS = ["FaultTolerance"];
 
 FaultTolerance = {
   get Service() {
     if (!this._Service)
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -255,18 +255,22 @@ WeaveSvc.prototype = {
       this._scheduleTimer.cancel();
       this._scheduleTimer = null;
     }
     this._log.info("Weave scheduler disabled");
   },
 
   _onSchedule: function WeaveSync__onSchedule() {
     if (this.enabled) {
-      this._log.info("Running scheduled sync");
-      this._notify("syncAsNeeded", this._lock(this._syncAsNeeded)).async(this);
+      if (!DAV.allowLock) {
+        this._log.info("Skipping scheduled sync; local operation in progress")
+      } else {
+        this._log.info("Running scheduled sync");
+        this._notify("syncAsNeeded", this._lock(this._syncAsNeeded)).async(this);
+      }
     }
   },
 
   _initLogs: function WeaveSync__initLogs() {
     this._log = Log4Moz.Service.getLogger("Service.Main");
     this._log.level =
       Log4Moz.Level[Utils.prefs.getCharPref("log.logger.service.main")];
 
@@ -504,26 +508,26 @@ WeaveSvc.prototype = {
 
   _verifyLogin: function WeaveSync__verifyLogin(username, password) {
     let self = yield;
     this._log.debug("Verifying login for user " + username);
 
     DAV.baseURL = Utils.prefs.getCharPref("serverURL");
     DAV.defaultPrefix = "user/" + username;
 
-    DAV.checkLogin.async(DAV, self.cb, username, password);
-    let resultMsg = yield;
+    this._log.info("Using server URL: " + DAV.baseURL + DAV.defaultPrefix);
 
-    // If we got an error message, throw it. [need to throw to cause the
-    // _notify() wrapper to generate an error notification for observers].
-    if (resultMsg) {
-      this._log.debug("Login verification: " + resultMsg);
-      throw resultMsg;
+    let status = yield DAV.checkLogin.async(DAV, self.cb, username, password);
+    if (status == 404) {
+      // create user directory (for self-hosted webdav shares)
+      // XXX do this in login?
+      yield this._checkUserDir.async(this, self.cb);
+      status = yield DAV.checkLogin.async(DAV, self.cb, username, password);
     }
-
+    Utils.ensureStatus(status, "Login verification failed");
   },
 
   login: function WeaveSync_login(onComplete) {
     this._localLock(this._notify("login", this._login)).async(this, onComplete);
   },
   _login: function WeaveSync__login() {
     let self = yield;
 
@@ -534,25 +538,22 @@ WeaveSvc.prototype = {
     if (!this.password)
       throw "No password given or found in password manager";
 
     DAV.baseURL = Utils.prefs.getCharPref("serverURL");
     DAV.defaultPrefix = "user/" + this.userPath;
 
     this._log.info("Using server URL: " + DAV.baseURL + DAV.defaultPrefix);
 
-    this._versionCheck.async(this, self.cb);
-    yield;
-    this._getKeypair.async(this, self.cb);
-    yield;
-
-    this._loggedIn = true;
+    yield this._versionCheck.async(this, self.cb);
+    yield this._getKeypair.async(this, self.cb);
 
     this._setSchedule(this.schedule);
 
+    this._loggedIn = true;
     self.done(true);
   },
 
   logout: function WeaveSync_logout() {
     this._log.info("Logging out");
     this._disableSchedule();
     this._loggedIn = false;
     ID.get('WeaveID').setTempPassword(null); // clear cached password
--- a/services/sync/modules/sharing.js
+++ b/services/sync/modules/sharing.js
@@ -1,8 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Atul Varma <varmaa@toolness.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 EXPORTED_SYMBOLS = ["Sharing"];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://weave/async.js");
 
--- a/services/sync/modules/stores.js
+++ b/services/sync/modules/stores.js
@@ -163,17 +163,18 @@ SnapshotStore.prototype = {
       // special-case guid changes
       let newGUID = command.data.GUID,
       oldGUID = command.GUID;
 
       this._data[newGUID] = this._data[oldGUID];
       delete this._data[oldGUID];
 
       for (let GUID in this._data) {
-        if (this._data[GUID].parentGUID == oldGUID)
+        if (("parentGUID" in this._data[GUID]) &&
+            (this._data[GUID].parentGUID == oldGUID))
           this._data[GUID].parentGUID = newGUID;
       }
     }
     for (let prop in command.data) {
       if (prop == "GUID")
         continue;
       this._data[command.GUID][prop] = command.data[prop];
     }
--- a/services/sync/modules/trackers.js
+++ b/services/sync/modules/trackers.js
@@ -9,17 +9,17 @@
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is Bookmarks Sync.
  *
  * The Initial Developer of the Original Code is Mozilla.
- * Portions created by the Initial Developer are Copyright (C) 2007
+ * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Anant Narayanan <anant@kix.in>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -198,17 +198,17 @@ let Utils = {
       for (let key in thing)
         ret[key] = Utils.deepCopy(thing[key]);
     }
 
     return ret;
   },
 
   exceptionStr: function Weave_exceptionStr(e) {
-    let message = e.message? e.message : e;
+    let message = e.message ? e.message : e;
     let location = "";
 
     if (e.location)
       // It's a wrapped nsIException.
       location = e.location;
     else if (e.fileName && e.lineNumber)
       // It's a standard JS exception.
       location = "file '" + e.fileName + "', line " + e.lineNumber;
@@ -302,41 +302,16 @@ let Utils = {
     let root = xmlDoc.ownerDocument == null ?
       xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement;
     let nsResolver = xmlDoc.createNSResolver(root);
 
     return xmlDoc.evaluate(xpathString, xmlDoc, nsResolver,
                            Ci.nsIDOMXPathResult.ANY_TYPE, null);
   },
 
-  runCmd: function Weave_runCmd() {
-    var binary;
-    var args = [];
-
-    for (let i = 0; i < arguments.length; ++i) {
-      args.push(arguments[i]);
-    }
-
-    if (args[0] instanceof Ci.nsIFile) {
-      binary = args.shift();
-    } else {
-      binary = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
-      binary.initWithPath(args.shift());
-    }
-
-    var p = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
-    p.init(binary);
-
-    let log = Log4Moz.Service.getLogger("Service.Util");
-    log.debug("Running command: " + binary.path + " " + args.join(" "));
-
-    p.run(true, args, args.length);
-    return p.exitValue;
-  },
-
   getTmp: function Weave_getTmp(name) {
     let ds = Cc["@mozilla.org/file/directory_service;1"].
       getService(Ci.nsIProperties);
 
     let tmp = ds.get("ProfD", Ci.nsIFile);
     tmp.QueryInterface(Ci.nsILocalFile);
 
     tmp.append("weave");
--- a/services/sync/modules/xmpp/authenticationLayer.js
+++ b/services/sync/modules/xmpp/authenticationLayer.js
@@ -1,8 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Jono DiCarlo <jdicarlo@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 const EXPORTED_SYMBOLS = [ "PlainAuthenticator", "Md5DigestAuthenticator" ];
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 
 Cu.import("resource://weave/log4moz.js");
 
--- a/services/sync/modules/xmpp/transportLayer.js
+++ b/services/sync/modules/xmpp/transportLayer.js
@@ -1,8 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Jono DiCarlo <jdicarlo@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 const EXPORTED_SYMBOLS = ['HTTPPollingTransport'];
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 
 Cu.import("resource://weave/log4moz.js");
 
--- a/services/sync/modules/xmpp/xmppClient.js
+++ b/services/sync/modules/xmpp/xmppClient.js
@@ -1,8 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Jono DiCarlo <jdicarlo@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
 const EXPORTED_SYMBOLS = ['XmppClient', 'HTTPPollingTransport', 'PlainAuthenticator', 'Md5DigestAuthenticator'];
 
 // See www.xulplanet.com/tutorials/mozsdk/sockets.php
 // http://www.xmpp.org/specs/rfc3920.html
 // http://www.process-one.net/docs/ejabberd/guide_en.html
 // http://www.xulplanet.com/tutorials/mozsdk/xmlparse.php
 // http://developer.mozilla.org/en/docs/xpcshell
 // http://developer.mozilla.org/en/docs/Writing_xpcshell-based_unit_tests
--- a/services/sync/services-sync.js
+++ b/services/sync/services-sync.js
@@ -1,9 +1,9 @@
-pref("extensions.weave.serverURL", "https://sm-labs01.mozilla.org:81/");
+pref("extensions.weave.serverURL", "https://services.mozilla.com/0.2/");
 pref("extensions.weave.username", "nobody");
 
 pref("extensions.weave.encryption", "aes-256-cbc");
 
 pref("extensions.weave.lastversion", "firstrun");
 pref("extensions.weave.lastsync", "0");
 
 pref("extensions.weave.ui.syncnow", true);
@@ -13,17 +13,17 @@ pref("extensions.weave.autoconnect", tru
 pref("extensions.weave.enabled", true);
 pref("extensions.weave.schedule", 1);
 
 pref("extensions.weave.syncOnQuit.enabled", true);
 
 pref("extensions.weave.engine.bookmarks", true);
 pref("extensions.weave.engine.history", true);
 pref("extensions.weave.engine.cookies", true );
-pref("extensions.weave.engine.passwords", true );
+pref("extensions.weave.engine.passwords", false);
 pref("extensions.weave.engine.forms", true );
 pref("extensions.weave.engine.tabs", true);
 
 pref("extensions.weave.log.appender.console", "Warn");
 pref("extensions.weave.log.appender.dump", "Error");
 pref("extensions.weave.log.appender.briefLog", "Info");
 pref("extensions.weave.log.appender.debugLog", "Trace");
 
old mode 120000
new mode 100644
--- a/services/sync/tests/unit/Makefile
+++ b/services/sync/tests/unit/Makefile
@@ -1,1 +1,2 @@
-../harness/Makefile
\ No newline at end of file
+all:
+	${MAKE} -f ../harness/Makefile
--- a/services/sync/tests/unit/fake_login_manager.js
+++ b/services/sync/tests/unit/fake_login_manager.js
@@ -22,16 +22,17 @@ let fakeSampleLogins = [
 function FakeLoginManager(fakeLogins) {
   this.fakeLogins = fakeLogins;
 
   let self = this;
 
   Utils.getLoginManager = function fake_getLoginManager() {
     // Return a fake nsILoginManager object.
     return {
+      removeAllLogins: function() { self.fakeLogins = []; },
       getAllLogins: function() { return self.fakeLogins; },
       addLogin: function(login) {
         getTestLogger().info("nsILoginManager.addLogin() called " +
                              "with hostname '" + login.hostname + "'.");
         self.fakeLogins.push(login);
       }
     };
   };
--- a/services/sync/tests/unit/head_first.js
+++ b/services/sync/tests/unit/head_first.js
@@ -269,59 +269,122 @@ function FakeFilesystemService(contents)
 function FakeGUIDService() {
   let latestGUID = 0;
 
   Utils.makeGUID = function fake_makeGUID() {
     return "fake-guid-" + latestGUID++;
   };
 }
 
-function SyncTestingInfrastructure() {
+function SyncTestingInfrastructure(engineFactory) {
   let __fakePasswords = {
     'Mozilla Services Password': {foo: "bar"},
     'Mozilla Services Encryption Passphrase': {foo: "passphrase"}
   };
 
   let __fakePrefs = {
     "encryption" : "none",
     "log.logger.service.crypto" : "Debug",
     "log.logger.service.engine" : "Debug",
     "log.logger.async" : "Debug",
     "xmpp.enabled" : false
   };
 
   Cu.import("resource://weave/identity.js");
+  Cu.import("resource://weave/util.js");
 
   ID.set('WeaveID',
          new Identity('Mozilla Services Encryption Passphrase', 'foo'));
 
   this.fakePasswordService = new FakePasswordService(__fakePasswords);
   this.fakePrefService = new FakePrefService(__fakePrefs);
   this.fakeDAVService = new FakeDAVService({});
   this.fakeTimerService = new FakeTimerService();
   this.logStats = initTestLogging();
   this.fakeFilesystem = new FakeFilesystemService({});
   this.fakeGUIDService = new FakeGUIDService();
 
+  this._logger = getTestLogger();
+  this._engineFactory = engineFactory;
+  this._clientStates = [];
+
+  this.saveClientState = function pushClientState(label) {
+    let state = Utils.deepCopy(this.fakeFilesystem.fakeContents);
+    let currContents = this.fakeFilesystem.fakeContents;
+    this.fakeFilesystem.fakeContents = [];
+    let engine = this._engineFactory();
+    let snapshot = Utils.deepCopy(engine._store.wrap());
+    this._clientStates[label] = {state: state, snapshot: snapshot};
+    this.fakeFilesystem.fakeContents = currContents;
+  };
+
+  this.restoreClientState = function restoreClientState(label) {
+    let state = this._clientStates[label].state;
+    let snapshot = this._clientStates[label].snapshot;
+
+    function _restoreState() {
+      let self = yield;
+
+      this.fakeFilesystem.fakeContents = [];
+      let engine = this._engineFactory();
+      engine._store.wipe();
+      let originalSnapshot = Utils.deepCopy(engine._store.wrap());
+
+      engine._core.detectUpdates(self.cb, originalSnapshot, snapshot);
+      let commands = yield;
+
+      engine._store.applyCommands.async(engine._store, self.cb, commands);
+      yield;
+
+      this.fakeFilesystem.fakeContents = Utils.deepCopy(state);
+    }
+
+    let self = this;
+
+    function restoreState(cb) {
+      _restoreState.async(self, cb);
+    }
+
+    this.runAsyncFunc("restore client state of " + label,
+                      restoreState);
+  };
+
   this.__makeCallback = function __makeCallback() {
     this.__callbackCalled = false;
     let self = this;
     return function callback() {
       self.__callbackCalled = true;
     };
   };
 
+  this.doSync = function doSync(name) {
+    let self = this;
+
+    function freshEngineSync(cb) {
+      let engine = self._engineFactory();
+      engine.sync(cb);
+    }
+
+    this.runAsyncFunc(name, freshEngineSync);
+  };
+
   this.runAsyncFunc = function runAsyncFunc(name, func) {
-    let logger = getTestLogger();
+    let logger = this._logger;
 
     logger.info("-----------------------------------------");
     logger.info("Step '" + name + "' starting.");
     logger.info("-----------------------------------------");
     func(this.__makeCallback());
     while (this.fakeTimerService.processCallback()) {}
     do_check_true(this.__callbackCalled);
     for (name in Async.outstandingGenerators)
       logger.warn("Outstanding generator exists: " + name);
     do_check_eq(this.logStats.errorsLogged, 0);
     do_check_eq(Async.outstandingGenerators.length, 0);
     logger.info("Step '" + name + "' succeeded.");
   };
+
+  this.resetClientState = function resetClientState() {
+    this.fakeFilesystem.fakeContents = {};
+    let engine = this._engineFactory();
+    engine._store.wipe();
+  };
 }
--- a/services/sync/tests/unit/test_bookmark_syncing.js
+++ b/services/sync/tests/unit/test_bookmark_syncing.js
@@ -1,65 +1,110 @@
 Cu.import("resource://weave/engines/bookmarks.js");
+Cu.import("resource://weave/util.js");
+Cu.import("resource://weave/async.js");
+
+Function.prototype.async = Async.sugar;
 
 load("bookmark_setup.js");
 
 // ----------------------------------------
 // Test Logic
 // ----------------------------------------
 
 function FakeMicrosummaryService() {
   return {hasMicrosummary: function() { return false; }};
 }
 
-function run_test() {
-  var syncTesting = new SyncTestingInfrastructure();
+function makeBookmarksEngine() {
+  let engine = new BookmarksEngine();
+  engine._store.__ms = new FakeMicrosummaryService();
+  return engine;
+}
 
-  function freshEngineSync(cb) {
-    let engine = new BookmarksEngine();
-    engine._store.__ms = new FakeMicrosummaryService();
-    engine.sync(cb);
-  };
+function run_test() {
+  // -----
+  // Setup
+  // -----
 
-  function resetProfile() {
-    // Simulate going to another computer by removing stuff from our
-    // objects.
-    syncTesting.fakeFilesystem.fakeContents = {};
-    bms.removeItem(boogleBm);
-    bms.removeItem(yoogleBm);
-  }
+  var syncTesting = new SyncTestingInfrastructure(makeBookmarksEngine);
 
   let bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
     getService(Ci.nsINavBookmarksService);
 
   cleanUp();
 
+  // -----------
+  // Test Proper
+  // -----------
+
   let boogleBm = bms.insertBookmark(bms.bookmarksMenuFolder,
                                     uri("http://www.boogle.com"),
                                     -1,
                                     "Boogle");
   bms.setItemGUID(boogleBm, "boogle-bookmark-guid");
 
-  syncTesting.runAsyncFunc("initial sync w/ one bookmark", freshEngineSync);
+  syncTesting.doSync("initial sync w/ one bookmark");
 
-  syncTesting.runAsyncFunc("trivial re-sync", freshEngineSync);
+  syncTesting.doSync("trivial re-sync");
 
   let yoogleBm = bms.insertBookmark(bms.bookmarksMenuFolder,
                                     uri("http://www.yoogle.com"),
                                     -1,
                                     "Yoogle");
   bms.setItemGUID(yoogleBm, "yoogle-bookmark-guid");
 
-  syncTesting.runAsyncFunc("add bookmark and re-sync", freshEngineSync);
+  syncTesting.doSync("add bookmark and re-sync");
 
   bms.moveItem(yoogleBm,
                bms.bookmarksMenuFolder,
                0);
 
-  syncTesting.runAsyncFunc("swap bookmark order and re-sync",
-                           freshEngineSync);
+  syncTesting.doSync("swap bookmark order and re-sync");
+
+  syncTesting.saveClientState("first computer");
+
+  syncTesting.resetClientState();
+
+  syncTesting.doSync("re-sync on second computer");
+
+  let zoogleBm = bms.insertBookmark(bms.bookmarksMenuFolder,
+                                    uri("http://www.zoogle.com"),
+                                    -1,
+                                    "Zoogle");
+  bms.setItemGUID(zoogleBm, "zoogle-bookmark-guid");
+
+  syncTesting.doSync("add bookmark on second computer and resync");
+
+  syncTesting.saveClientState("second computer");
+
+  syncTesting.restoreClientState("first computer");
+  syncTesting.doSync("re-sync on first computer");
 
-  resetProfile();
+  let binkBm1 = bms.insertBookmark(bms.bookmarksMenuFolder,
+                                   uri("http://www.bink.com"),
+                                   -1,
+                                   "Bink");
+  bms.setItemGUID(binkBm1, "bink-bookmark-guid-1");
+
+  syncTesting.doSync("add bookmark 'bink' on first computer and resync");
+  syncTesting.restoreClientState("second computer");
 
-  syncTesting.runAsyncFunc("re-sync on second computer", freshEngineSync);
+  let binkBm2 = bms.insertBookmark(bms.bookmarksMenuFolder,
+                                   uri("http://www.bink.com"),
+                                   -1,
+                                   "Bink");
+
+  bms.setItemGUID(binkBm2, "bink-bookmark-guid-2");
+
+  syncTesting.doSync("Manually add same bookmark 'bink', but with " +
+                     "different GUID, to second computer and resync");
+
+  binkBm2 = bms.getBookmarkIdsForURI(uri("http://www.bink.com"), {})[0];
+
+  do_check_eq(bms.getItemGUID(binkBm2), "bink-bookmark-guid-1");
+
+  // --------
+  // Teardown
+  // --------
 
   cleanUp();
 }
--- a/services/sync/tests/unit/test_bookmark_syncing.log.expected
+++ b/services/sync/tests/unit/test_bookmark_syncing.log.expected
@@ -170,11 +170,188 @@ Service.BStore	TRACE	Processing command:
 Service.BStore	DEBUG	 -> creating bookmark "Yoogle"
 Service.SnapStore	INFO	Saving snapshot to disk
 Testing	INFO	Opening 'weave/snapshots/bookmarks.json' for writing.
 Testing	INFO	Writing data to local file 'weave/snapshots/bookmarks.json': {"version":2,"GUID":"fake-guid-0","snapshot":{"menu":{"type":"folder"},"toolbar":{"type":"folder"},"unfiled":{"type":"folder"},"boogle-bookmark-guid":{"parentGUID":"menu","index":1,"type":"bookmark","title":"Boogle","URI":"http://www.boogle.com/","tags":[],"keyword":null},"yoogle-bookmark-guid":{"parentGUID":"menu","index":0,"type":"bookmark","title":"Yoogle","URI":"http://www.yoogle.com/","tags":[],"keyword":null}}}
 Service.BmkEngine	INFO	Actual changes for server: 0
 Service.BmkEngine	DEBUG	Actual changes for server: []
 Service.BmkEngine	INFO	Sync complete
 Testing	INFO	Step 're-sync on second computer' succeeded.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Step 'add bookmark on second computer and resync' starting.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Opening 'weave/snapshots/bookmarks.json' for reading.
+Testing	INFO	Reading from stream.
+Service.SnapStore	INFO	Read saved snapshot from disk
+Service.BmkEngine	INFO	Beginning sync
+Testing	INFO	HTTP MKCOL on user-data/bookmarks/deltas
+Service.RemoteStore	DEBUG	Downloading status file
+Testing	INFO	HTTP GET from user-data/bookmarks/status.json, returning status 200
+Service.Resource	DEBUG	GET request successful
+Service.JsonFilter	DEBUG	Decoding JSON data
+Service.RemoteStore	DEBUG	Downloading status file... done
+Service.BmkEngine	INFO	Local snapshot version: 2
+Service.BmkEngine	INFO	Server maxVersion: 2
+Service.RemoteStore	DEBUG	Using last sync snapshot as server snapshot (snap version == max version)
+Service.RemoteStore	TRACE	Local snapshot version == server maxVersion
+Service.BmkEngine	INFO	Reconciling client/server updates
+Service.BMSync	DEBUG	Reconciling 1 against 0 commands
+Service.BmkEngine	INFO	Changes for client: 0
+Service.BmkEngine	INFO	Predicted changes for server: 1
+Service.BmkEngine	INFO	Client conflicts: 0
+Service.BmkEngine	INFO	Server conflicts: 0
+Service.BmkEngine	INFO	Actual changes for server: 1
+Service.BmkEngine	DEBUG	Actual changes for server: [{"action":"create","GUID":"zoogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null}}]
+Service.BmkEngine	INFO	Uploading changes to server
+Service.JsonFilter	DEBUG	Encoding data as JSON
+Service.CryptoFilter	DEBUG	Encrypting data
+Service.Crypto	DEBUG	NOT encrypting data
+Testing	INFO	HTTP PUT to user-data/bookmarks/deltas/3 with data: [{"action":"create","GUID":"zoogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null}}]
+Service.ResourceSet	DEBUG	PUT request successful
+Service.JsonFilter	DEBUG	Encoding data as JSON
+Testing	INFO	HTTP PUT to user-data/bookmarks/status.json with data: {"GUID":"fake-guid-0","formatVersion":2,"snapVersion":0,"maxVersion":3,"snapEncryption":"none","deltasEncryption":"none","itemCount":6}
+Service.Resource	DEBUG	PUT request successful
+Service.BmkEngine	INFO	Successfully updated deltas and status on server
+Service.SnapStore	INFO	Saving snapshot to disk
+Testing	INFO	Opening 'weave/snapshots/bookmarks.json' for writing.
+Testing	INFO	Writing data to local file 'weave/snapshots/bookmarks.json': {"version":3,"GUID":"fake-guid-0","snapshot":{"yoogle-bookmark-guid":{"parentGUID":"menu","index":0,"type":"bookmark","title":"Yoogle","URI":"http://www.yoogle.com/","tags":[],"keyword":null},"boogle-bookmark-guid":{"parentGUID":"menu","index":1,"type":"bookmark","title":"Boogle","URI":"http://www.boogle.com/","tags":[],"keyword":null},"zoogle-bookmark-guid":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null},"menu":{"type":"folder"},"toolbar":{"type":"folder"},"unfiled":{"type":"folder"}}}
+Service.BmkEngine	INFO	Sync complete
+Testing	INFO	Step 'add bookmark on second computer and resync' succeeded.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Step 'restore client state of first computer' starting.
+Testing	INFO	-----------------------------------------
+Service.BStore	TRACE	Processing command: {"action":"create","GUID":"yoogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":0,"type":"bookmark","title":"Yoogle","URI":"http://www.yoogle.com/","tags":[],"keyword":null}}
+Service.BStore	DEBUG	 -> creating bookmark "Yoogle"
+Service.BStore	TRACE	Processing command: {"action":"create","GUID":"boogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":1,"type":"bookmark","title":"Boogle","URI":"http://www.boogle.com/","tags":[],"keyword":null}}
+Service.BStore	DEBUG	 -> creating bookmark "Boogle"
+Testing	INFO	Step 'restore client state of first computer' succeeded.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Step 're-sync on first computer' starting.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Opening 'weave/snapshots/bookmarks.json' for reading.
+Testing	INFO	Reading from stream.
+Service.SnapStore	INFO	Read saved snapshot from disk
+Service.BmkEngine	INFO	Beginning sync
+Testing	INFO	HTTP MKCOL on user-data/bookmarks/deltas
+Service.RemoteStore	DEBUG	Downloading status file
+Testing	INFO	HTTP GET from user-data/bookmarks/status.json, returning status 200
+Service.Resource	DEBUG	GET request successful
+Service.JsonFilter	DEBUG	Decoding JSON data
+Service.RemoteStore	DEBUG	Downloading status file... done
+Service.BmkEngine	INFO	Local snapshot version: 2
+Service.BmkEngine	INFO	Server maxVersion: 3
+Service.RemoteStore	DEBUG	Using last sync snapshot as starting point for server snapshot
+Service.RemoteStore	INFO	Downloading server deltas
+Testing	INFO	HTTP GET from user-data/bookmarks/deltas/3, returning status 200
+Service.ResourceSet	DEBUG	GET request successful
+Service.CryptoFilter	DEBUG	Decrypting data
+Service.Crypto	DEBUG	NOT decrypting data
+Service.JsonFilter	DEBUG	Decoding JSON data
+Service.SnapStore	TRACE	Processing command: {"action":"create","GUID":"zoogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null}}
+Service.BmkEngine	INFO	Reconciling client/server updates
+Service.BMSync	DEBUG	Reconciling 0 against 1 commands
+Service.BmkEngine	INFO	Changes for client: 1
+Service.BmkEngine	INFO	Predicted changes for server: 0
+Service.BmkEngine	INFO	Client conflicts: 0
+Service.BmkEngine	INFO	Server conflicts: 0
+Service.BmkEngine	INFO	Applying changes locally
+Service.SnapStore	TRACE	Processing command: {"action":"create","GUID":"zoogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null}}
+Service.BStore	TRACE	Processing command: {"action":"create","GUID":"zoogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null}}
+Service.BStore	DEBUG	 -> creating bookmark "Zoogle"
+Service.SnapStore	INFO	Saving snapshot to disk
+Testing	INFO	Opening 'weave/snapshots/bookmarks.json' for writing.
+Testing	INFO	Writing data to local file 'weave/snapshots/bookmarks.json': {"version":3,"GUID":"fake-guid-0","snapshot":{"yoogle-bookmark-guid":{"parentGUID":"menu","index":0,"type":"bookmark","title":"Yoogle","URI":"http://www.yoogle.com/","tags":[],"keyword":null},"boogle-bookmark-guid":{"parentGUID":"menu","index":1,"type":"bookmark","title":"Boogle","URI":"http://www.boogle.com/","tags":[],"keyword":null},"menu":{"type":"folder"},"toolbar":{"type":"folder"},"unfiled":{"type":"folder"},"zoogle-bookmark-guid":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null}}}
+Service.BmkEngine	INFO	Actual changes for server: 0
+Service.BmkEngine	DEBUG	Actual changes for server: []
+Service.BmkEngine	INFO	Sync complete
+Testing	INFO	Step 're-sync on first computer' succeeded.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Step 'add bookmark 'bink' on first computer and resync' starting.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Opening 'weave/snapshots/bookmarks.json' for reading.
+Testing	INFO	Reading from stream.
+Service.SnapStore	INFO	Read saved snapshot from disk
+Service.BmkEngine	INFO	Beginning sync
+Testing	INFO	HTTP MKCOL on user-data/bookmarks/deltas
+Service.RemoteStore	DEBUG	Downloading status file
+Testing	INFO	HTTP GET from user-data/bookmarks/status.json, returning status 200
+Service.Resource	DEBUG	GET request successful
+Service.JsonFilter	DEBUG	Decoding JSON data
+Service.RemoteStore	DEBUG	Downloading status file... done
+Service.BmkEngine	INFO	Local snapshot version: 3
+Service.BmkEngine	INFO	Server maxVersion: 3
+Service.RemoteStore	DEBUG	Using last sync snapshot as server snapshot (snap version == max version)
+Service.RemoteStore	TRACE	Local snapshot version == server maxVersion
+Service.BmkEngine	INFO	Reconciling client/server updates
+Service.BMSync	DEBUG	Reconciling 1 against 0 commands
+Service.BmkEngine	INFO	Changes for client: 0
+Service.BmkEngine	INFO	Predicted changes for server: 1
+Service.BmkEngine	INFO	Client conflicts: 0
+Service.BmkEngine	INFO	Server conflicts: 0
+Service.BmkEngine	INFO	Actual changes for server: 1
+Service.BmkEngine	DEBUG	Actual changes for server: [{"action":"create","GUID":"bink-bookmark-guid-1","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":3,"type":"bookmark","title":"Bink","URI":"http://www.bink.com/","tags":[],"keyword":null}}]
+Service.BmkEngine	INFO	Uploading changes to server
+Service.JsonFilter	DEBUG	Encoding data as JSON
+Service.CryptoFilter	DEBUG	Encrypting data
+Service.Crypto	DEBUG	NOT encrypting data
+Testing	INFO	HTTP PUT to user-data/bookmarks/deltas/4 with data: [{"action":"create","GUID":"bink-bookmark-guid-1","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":3,"type":"bookmark","title":"Bink","URI":"http://www.bink.com/","tags":[],"keyword":null}}]
+Service.ResourceSet	DEBUG	PUT request successful
+Service.JsonFilter	DEBUG	Encoding data as JSON
+Testing	INFO	HTTP PUT to user-data/bookmarks/status.json with data: {"GUID":"fake-guid-0","formatVersion":2,"snapVersion":0,"maxVersion":4,"snapEncryption":"none","deltasEncryption":"none","itemCount":7}
+Service.Resource	DEBUG	PUT request successful
+Service.BmkEngine	INFO	Successfully updated deltas and status on server
+Service.SnapStore	INFO	Saving snapshot to disk
+Testing	INFO	Opening 'weave/snapshots/bookmarks.json' for writing.
+Testing	INFO	Writing data to local file 'weave/snapshots/bookmarks.json': {"version":4,"GUID":"fake-guid-0","snapshot":{"yoogle-bookmark-guid":{"parentGUID":"menu","index":0,"type":"bookmark","title":"Yoogle","URI":"http://www.yoogle.com/","tags":[],"keyword":null},"boogle-bookmark-guid":{"parentGUID":"menu","index":1,"type":"bookmark","title":"Boogle","URI":"http://www.boogle.com/","tags":[],"keyword":null},"zoogle-bookmark-guid":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null},"bink-bookmark-guid-1":{"parentGUID":"menu","index":3,"type":"bookmark","title":"Bink","URI":"http://www.bink.com/","tags":[],"keyword":null},"menu":{"type":"folder"},"toolbar":{"type":"folder"},"unfiled":{"type":"folder"}}}
+Service.BmkEngine	INFO	Sync complete
+Testing	INFO	Step 'add bookmark 'bink' on first computer and resync' succeeded.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Step 'restore client state of second computer' starting.
+Testing	INFO	-----------------------------------------
+Service.BStore	TRACE	Processing command: {"action":"create","GUID":"yoogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":0,"type":"bookmark","title":"Yoogle","URI":"http://www.yoogle.com/","tags":[],"keyword":null}}
+Service.BStore	DEBUG	 -> creating bookmark "Yoogle"
+Service.BStore	TRACE	Processing command: {"action":"create","GUID":"boogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":1,"type":"bookmark","title":"Boogle","URI":"http://www.boogle.com/","tags":[],"keyword":null}}
+Service.BStore	DEBUG	 -> creating bookmark "Boogle"
+Service.BStore	TRACE	Processing command: {"action":"create","GUID":"zoogle-bookmark-guid","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null}}
+Service.BStore	DEBUG	 -> creating bookmark "Zoogle"
+Testing	INFO	Step 'restore client state of second computer' succeeded.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Step 'Manually add same bookmark 'bink', but with different GUID, to second computer and resync' starting.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Opening 'weave/snapshots/bookmarks.json' for reading.
+Testing	INFO	Reading from stream.
+Service.SnapStore	INFO	Read saved snapshot from disk
+Service.BmkEngine	INFO	Beginning sync
+Testing	INFO	HTTP MKCOL on user-data/bookmarks/deltas
+Service.RemoteStore	DEBUG	Downloading status file
+Testing	INFO	HTTP GET from user-data/bookmarks/status.json, returning status 200
+Service.Resource	DEBUG	GET request successful
+Service.JsonFilter	DEBUG	Decoding JSON data
+Service.RemoteStore	DEBUG	Downloading status file... done
+Service.BmkEngine	INFO	Local snapshot version: 3
+Service.BmkEngine	INFO	Server maxVersion: 4
+Service.RemoteStore	DEBUG	Using last sync snapshot as starting point for server snapshot
+Service.RemoteStore	INFO	Downloading server deltas
+Testing	INFO	HTTP GET from user-data/bookmarks/deltas/4, returning status 200
+Service.ResourceSet	DEBUG	GET request successful
+Service.CryptoFilter	DEBUG	Decrypting data
+Service.Crypto	DEBUG	NOT decrypting data
+Service.JsonFilter	DEBUG	Decoding JSON data
+Service.SnapStore	TRACE	Processing command: {"action":"create","GUID":"bink-bookmark-guid-1","depth":1,"parents":["menu"],"data":{"parentGUID":"menu","index":3,"type":"bookmark","title":"Bink","URI":"http://www.bink.com/","tags":[],"keyword":null}}
+Service.BmkEngine	INFO	Reconciling client/server updates
+Service.BMSync	DEBUG	Reconciling 1 against 1 commands
+Service.BmkEngine	INFO	Changes for client: 1
+Service.BmkEngine	INFO	Predicted changes for server: 0
+Service.BmkEngine	INFO	Client conflicts: 0
+Service.BmkEngine	INFO	Server conflicts: 0
+Service.BmkEngine	INFO	Applying changes locally
+Service.SnapStore	TRACE	Processing command: {"action":"edit","GUID":"bink-bookmark-guid-2","data":{"GUID":"bink-bookmark-guid-1"}}
+Service.BStore	TRACE	Processing command: {"action":"edit","GUID":"bink-bookmark-guid-2","data":{"GUID":"bink-bookmark-guid-1"}}
+Service.SnapStore	INFO	Saving snapshot to disk
+Testing	INFO	Opening 'weave/snapshots/bookmarks.json' for writing.
+Testing	INFO	Writing data to local file 'weave/snapshots/bookmarks.json': {"version":4,"GUID":"fake-guid-0","snapshot":{"yoogle-bookmark-guid":{"parentGUID":"menu","index":0,"type":"bookmark","title":"Yoogle","URI":"http://www.yoogle.com/","tags":[],"keyword":null},"boogle-bookmark-guid":{"parentGUID":"menu","index":1,"type":"bookmark","title":"Boogle","URI":"http://www.boogle.com/","tags":[],"keyword":null},"zoogle-bookmark-guid":{"parentGUID":"menu","index":2,"type":"bookmark","title":"Zoogle","URI":"http://www.zoogle.com/","tags":[],"keyword":null},"menu":{"type":"folder"},"toolbar":{"type":"folder"},"unfiled":{"type":"folder"},"bink-bookmark-guid-1":{"parentGUID":"menu","index":3,"type":"bookmark","title":"Bink","URI":"http://www.bink.com/","tags":[],"keyword":null}}}
+Service.BmkEngine	INFO	Actual changes for server: 0
+Service.BmkEngine	DEBUG	Actual changes for server: []
+Service.BmkEngine	INFO	Sync complete
+Testing	INFO	Step 'Manually add same bookmark 'bink', but with different GUID, to second computer and resync' succeeded.
 *** test finished
 *** exiting
 *** PASS ***
--- a/services/sync/tests/unit/test_password_syncing.js
+++ b/services/sync/tests/unit/test_password_syncing.js
@@ -2,41 +2,36 @@ Cu.import("resource://weave/engines/pass
 
 load("fake_login_manager.js");
 
 // ----------------------------------------
 // Test Logic
 // ----------------------------------------
 
 function run_test() {
-  var syncTesting = new SyncTestingInfrastructure();
+  function passwdFactory() { return new PasswordEngine(); }
+  var syncTesting = new SyncTestingInfrastructure(passwdFactory);
   var fakeLoginManager = new FakeLoginManager(fakeSampleLogins);
 
-  function freshEngineSync(cb) {
-    let engine = new PasswordEngine();
-    engine.sync(cb);
-  };
+  syncTesting.doSync("initial sync");
 
-  syncTesting.runAsyncFunc("initial sync", freshEngineSync);
-
-  syncTesting.runAsyncFunc("trivial re-sync", freshEngineSync);
+  syncTesting.doSync("trivial re-sync");
 
   fakeLoginManager.fakeLogins.push(
     {hostname: "www.yoogle.com",
      formSubmitURL: "http://www.yoogle.com/search",
      httpRealm: "",
      username: "",
      password: "",
      usernameField: "test_person2",
      passwordField: "test_password2"}
   );
 
-  syncTesting.runAsyncFunc("add user and re-sync", freshEngineSync);
+  syncTesting.doSync("add user and re-sync");
 
   fakeLoginManager.fakeLogins.pop();
 
-  syncTesting.runAsyncFunc("remove user and re-sync", freshEngineSync);
+  syncTesting.doSync("remove user and re-sync");
 
-  syncTesting.fakeFilesystem.fakeContents = {};
-  fakeLoginManager.fakeLogins = [];
+  syncTesting.resetClientState();
 
-  syncTesting.runAsyncFunc("resync on second computer", freshEngineSync);
+  syncTesting.doSync("resync on second computer");
 }
--- a/services/sync/tests/unit/test_password_syncing.log.expected
+++ b/services/sync/tests/unit/test_password_syncing.log.expected
@@ -1,46 +1,41 @@
 *** test pending
 Testing	INFO	-----------------------------------------
 Testing	INFO	Step 'initial sync' starting.
 Testing	INFO	-----------------------------------------
 Service.PasswordEngine	INFO	Beginning sync
-Testing	INFO	HTTP MKCOL on user-data/passwords/
+Testing	INFO	HTTP MKCOL on user-data/passwords/deltas
 Service.RemoteStore	DEBUG	Downloading status file
 Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 404
 Service.PasswordEngine	INFO	Initial upload to server
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Testing	INFO	HTTP PUT to user-data/passwords/keys.json with data: {"ring":{},"bulkIV":null}
 Service.Resource	DEBUG	PUT request successful
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Service.CryptoFilter	DEBUG	Encrypting data
 Service.Crypto	DEBUG	NOT encrypting data
 Testing	INFO	HTTP PUT to user-data/passwords/snapshot.json with data: {"805ec58eb8dcded602999967e139be21acd0f194":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"}}
 Service.Resource	DEBUG	PUT request successful
 Service.JsonFilter	DEBUG	Encoding data as JSON
-Service.CryptoFilter	DEBUG	Encrypting data
-Service.Crypto	DEBUG	NOT encrypting data
-Testing	INFO	HTTP PUT to user-data/passwords/deltas.json with data: []
-Service.Resource	DEBUG	PUT request successful
-Service.JsonFilter	DEBUG	Encoding data as JSON
 Testing	INFO	HTTP PUT to user-data/passwords/status.json with data: {"GUID":"fake-guid-0","formatVersion":2,"snapVersion":0,"maxVersion":0,"snapEncryption":"none","deltasEncryption":"none","itemCount":1}
 Service.Resource	DEBUG	PUT request successful
 Service.RemoteStore	INFO	Full upload to server successful
 Service.SnapStore	INFO	Saving snapshot to disk
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for writing.
 Testing	INFO	Writing data to local file 'weave/snapshots/passwords.json': {"version":0,"GUID":"fake-guid-0","snapshot":{"805ec58eb8dcded602999967e139be21acd0f194":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"}}}
 Testing	INFO	Step 'initial sync' succeeded.
 Testing	INFO	-----------------------------------------
 Testing	INFO	Step 'trivial re-sync' starting.
 Testing	INFO	-----------------------------------------
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for reading.
 Testing	INFO	Reading from stream.
 Service.SnapStore	INFO	Read saved snapshot from disk
 Service.PasswordEngine	INFO	Beginning sync
-Testing	INFO	HTTP MKCOL on user-data/passwords/
+Testing	INFO	HTTP MKCOL on user-data/passwords/deltas
 Service.RemoteStore	DEBUG	Downloading status file
 Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 200
 Service.Resource	DEBUG	GET request successful
 Service.JsonFilter	DEBUG	Decoding JSON data
 Service.RemoteStore	DEBUG	Downloading status file... done
 Service.PasswordEngine	INFO	Local snapshot version: 0
 Service.PasswordEngine	INFO	Server maxVersion: 0
 Service.RemoteStore	DEBUG	Using last sync snapshot as server snapshot (snap version == max version)
@@ -49,17 +44,17 @@ Service.PasswordEngine	INFO	Sync complet
 Testing	INFO	Step 'trivial re-sync' succeeded.
 Testing	INFO	-----------------------------------------
 Testing	INFO	Step 'add user and re-sync' starting.
 Testing	INFO	-----------------------------------------
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for reading.
 Testing	INFO	Reading from stream.
 Service.SnapStore	INFO	Read saved snapshot from disk
 Service.PasswordEngine	INFO	Beginning sync
-Testing	INFO	HTTP MKCOL on user-data/passwords/
+Testing	INFO	HTTP MKCOL on user-data/passwords/deltas
 Service.RemoteStore	DEBUG	Downloading status file
 Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 200
 Service.Resource	DEBUG	GET request successful
 Service.JsonFilter	DEBUG	Decoding JSON data
 Service.RemoteStore	DEBUG	Downloading status file... done
 Service.PasswordEngine	INFO	Local snapshot version: 0
 Service.PasswordEngine	INFO	Server maxVersion: 0
 Service.RemoteStore	DEBUG	Using last sync snapshot as server snapshot (snap version == max version)
@@ -68,43 +63,38 @@ Service.PasswordEngine	INFO	Reconciling 
 Service.PasswordSync	DEBUG	Reconciling 1 against 0 commands
 Service.PasswordEngine	INFO	Changes for client: 0
 Service.PasswordEngine	INFO	Predicted changes for server: 1
 Service.PasswordEngine	INFO	Client conflicts: 0
 Service.PasswordEngine	INFO	Server conflicts: 0
 Service.PasswordEngine	INFO	Actual changes for server: 1
 Service.PasswordEngine	DEBUG	Actual changes for server: [{"action":"create","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[],"data":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}]
 Service.PasswordEngine	INFO	Uploading changes to server
-Testing	INFO	HTTP GET from user-data/passwords/deltas.json, returning status 200
-Service.Resource	DEBUG	GET request successful
-Service.CryptoFilter	DEBUG	Decrypting data
-Service.Crypto	DEBUG	NOT decrypting data
-Service.JsonFilter	DEBUG	Decoding JSON data
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Service.CryptoFilter	DEBUG	Encrypting data
 Service.Crypto	DEBUG	NOT encrypting data
-Testing	INFO	HTTP PUT to user-data/passwords/deltas.json with data: [[{"action":"create","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[],"data":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}]]
-Service.Resource	DEBUG	PUT request successful
+Testing	INFO	HTTP PUT to user-data/passwords/deltas/1 with data: [{"action":"create","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[],"data":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}]
+Service.ResourceSet	DEBUG	PUT request successful
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Testing	INFO	HTTP PUT to user-data/passwords/status.json with data: {"GUID":"fake-guid-0","formatVersion":2,"snapVersion":0,"maxVersion":1,"snapEncryption":"none","deltasEncryption":"none","itemCount":2}
 Service.Resource	DEBUG	PUT request successful
 Service.PasswordEngine	INFO	Successfully updated deltas and status on server
 Service.SnapStore	INFO	Saving snapshot to disk
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for writing.
 Testing	INFO	Writing data to local file 'weave/snapshots/passwords.json': {"version":1,"GUID":"fake-guid-0","snapshot":{"805ec58eb8dcded602999967e139be21acd0f194":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"},"1b3869fc36234b39cd354f661ed1d7d148394ca3":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}}
 Service.PasswordEngine	INFO	Sync complete
 Testing	INFO	Step 'add user and re-sync' succeeded.
 Testing	INFO	-----------------------------------------
 Testing	INFO	Step 'remove user and re-sync' starting.
 Testing	INFO	-----------------------------------------
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for reading.
 Testing	INFO	Reading from stream.
 Service.SnapStore	INFO	Read saved snapshot from disk
 Service.PasswordEngine	INFO	Beginning sync
-Testing	INFO	HTTP MKCOL on user-data/passwords/
+Testing	INFO	HTTP MKCOL on user-data/passwords/deltas
 Service.RemoteStore	DEBUG	Downloading status file
 Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 200
 Service.Resource	DEBUG	GET request successful
 Service.JsonFilter	DEBUG	Decoding JSON data
 Service.RemoteStore	DEBUG	Downloading status file... done
 Service.PasswordEngine	INFO	Local snapshot version: 1
 Service.PasswordEngine	INFO	Server maxVersion: 1
 Service.RemoteStore	DEBUG	Using last sync snapshot as server snapshot (snap version == max version)
@@ -113,63 +103,61 @@ Service.PasswordEngine	INFO	Reconciling 
 Service.PasswordSync	DEBUG	Reconciling 1 against 0 commands
 Service.PasswordEngine	INFO	Changes for client: 0
 Service.PasswordEngine	INFO	Predicted changes for server: 1
 Service.PasswordEngine	INFO	Client conflicts: 0
 Service.PasswordEngine	INFO	Server conflicts: 0
 Service.PasswordEngine	INFO	Actual changes for server: 1
 Service.PasswordEngine	DEBUG	Actual changes for server: [{"action":"remove","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[]}]
 Service.PasswordEngine	INFO	Uploading changes to server
-Testing	INFO	HTTP GET from user-data/passwords/deltas.json, returning status 200
-Service.Resource	DEBUG	GET request successful
-Service.CryptoFilter	DEBUG	Decrypting data
-Service.Crypto	DEBUG	NOT decrypting data
-Service.JsonFilter	DEBUG	Decoding JSON data
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Service.CryptoFilter	DEBUG	Encrypting data
 Service.Crypto	DEBUG	NOT encrypting data
-Testing	INFO	HTTP PUT to user-data/passwords/deltas.json with data: [[{"action":"create","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[],"data":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}],[{"action":"remove","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[]}]]
-Service.Resource	DEBUG	PUT request successful
+Testing	INFO	HTTP PUT to user-data/passwords/deltas/2 with data: [{"action":"remove","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[]}]
+Service.ResourceSet	DEBUG	PUT request successful
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Testing	INFO	HTTP PUT to user-data/passwords/status.json with data: {"GUID":"fake-guid-0","formatVersion":2,"snapVersion":0,"maxVersion":2,"snapEncryption":"none","deltasEncryption":"none","itemCount":1}
 Service.Resource	DEBUG	PUT request successful
 Service.PasswordEngine	INFO	Successfully updated deltas and status on server
 Service.SnapStore	INFO	Saving snapshot to disk
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for writing.
 Testing	INFO	Writing data to local file 'weave/snapshots/passwords.json': {"version":2,"GUID":"fake-guid-0","snapshot":{"805ec58eb8dcded602999967e139be21acd0f194":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"}}}
 Service.PasswordEngine	INFO	Sync complete
 Testing	INFO	Step 'remove user and re-sync' succeeded.
 Testing	INFO	-----------------------------------------
 Testing	INFO	Step 'resync on second computer' starting.
 Testing	INFO	-----------------------------------------
 Service.PasswordEngine	INFO	Beginning sync
-Testing	INFO	HTTP MKCOL on user-data/passwords/
+Testing	INFO	HTTP MKCOL on user-data/passwords/deltas
 Service.RemoteStore	DEBUG	Downloading status file
 Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 200
 Service.Resource	DEBUG	GET request successful
 Service.JsonFilter	DEBUG	Decoding JSON data
 Service.RemoteStore	DEBUG	Downloading status file... done
 Service.PasswordEngine	DEBUG	Remote/local sync GUIDs do not match.  Forcing initial sync.
 Service.PasswordEngine	INFO	Local snapshot version: -1
 Service.PasswordEngine	INFO	Server maxVersion: 2
 Service.RemoteStore	TRACE	Getting latest from snap --> scratch
 Service.RemoteStore	INFO	Downloading all server data from scratch
-Service.RemoteStore	DEBUG	Downloading server snapshot
 Testing	INFO	HTTP GET from user-data/passwords/snapshot.json, returning status 200
 Service.Resource	DEBUG	GET request successful
 Service.CryptoFilter	DEBUG	Decrypting data
 Service.Crypto	DEBUG	NOT decrypting data
 Service.JsonFilter	DEBUG	Decoding JSON data
-Service.RemoteStore	DEBUG	Downloading server deltas
-Testing	INFO	HTTP GET from user-data/passwords/deltas.json, returning status 200
-Service.Resource	DEBUG	GET request successful
+Testing	INFO	HTTP GET from user-data/passwords/deltas/1, returning status 200
+Service.ResourceSet	DEBUG	GET request successful
 Service.CryptoFilter	DEBUG	Decrypting data
 Service.Crypto	DEBUG	NOT decrypting data
 Service.JsonFilter	DEBUG	Decoding JSON data
 Service.SnapStore	TRACE	Processing command: {"action":"create","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[],"data":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}
+Testing	INFO	HTTP GET from user-data/passwords/deltas/2, returning status 200
+Service.ResourceSet	DEBUG	GET request successful
+Service.CryptoFilter	DEBUG	Decrypting data
+Service.Crypto	DEBUG	NOT decrypting data
+Service.JsonFilter	DEBUG	Decoding JSON data
 Service.SnapStore	TRACE	Processing command: {"action":"remove","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[]}
 Service.PasswordEngine	INFO	Reconciling client/server updates
 Service.PasswordSync	DEBUG	Reconciling 0 against 1 commands
 Service.PasswordEngine	INFO	Changes for client: 1
 Service.PasswordEngine	INFO	Predicted changes for server: 0
 Service.PasswordEngine	INFO	Client conflicts: 0
 Service.PasswordEngine	INFO	Server conflicts: 0
 Service.PasswordEngine	INFO	Applying changes locally