[epiphany/wip/sync: 20/22] sync: Replace old bookmarks sync code



commit f4b650bc9d5ec9dd3fc470201e34e0620b772e4b
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date:   Tue Apr 25 16:14:10 2017 +0300

    sync: Replace old bookmarks sync code

 configure.ac                                  |    2 +-
 data/org.gnome.epiphany.gschema.xml           |   48 +-
 lib/ephy-prefs.h                              |   11 +-
 lib/ephy-settings.h                           |    1 +
 src/Makefile.am                               |    6 +-
 src/bookmarks/ephy-add-bookmark-popover.c     |    6 +-
 src/bookmarks/ephy-bookmark-properties-grid.c |   65 +-
 src/bookmarks/ephy-bookmark.c                 |  338 ++--
 src/bookmarks/ephy-bookmark.h                 |   84 +-
 src/bookmarks/ephy-bookmarks-export.c         |    2 +-
 src/bookmarks/ephy-bookmarks-import.c         |   14 +-
 src/bookmarks/ephy-bookmarks-manager.c        |  214 +++-
 src/ephy-shell.c                              |   30 +-
 src/prefs-dialog.c                            |  670 ++++----
 src/profile-migrator/Makefile.am              |    1 +
 src/profile-migrator/ephy-profile-migrator.c  |    7 +-
 src/resources/gtk/prefs-dialog.ui             |  181 ++-
 src/sync/ephy-sync-crypto.c                   | 1047 ++++++++----
 src/sync/ephy-sync-crypto.h                   |  138 +-
 src/sync/ephy-sync-secret.c                   |  175 +-
 src/sync/ephy-sync-secret.h                   |   27 +-
 src/sync/ephy-sync-service.c                  | 2399 +++++++++++++++----------
 src/sync/ephy-sync-service.h                  |   71 +-
 src/sync/ephy-sync-utils.c                    |  195 --
 src/sync/ephy-sync-utils.h                    |   52 -
 src/sync/ephy-synchronizable-manager.c        |  225 +++
 src/sync/ephy-synchronizable-manager.h        |   71 +
 src/sync/ephy-synchronizable.c                |  273 +++
 src/sync/ephy-synchronizable.h                |   64 +
 29 files changed, 4064 insertions(+), 2353 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index cc4ffe1..92fd717 100644
--- a/configure.ac
+++ b/configure.ac
@@ -109,7 +109,7 @@ PKG_CHECK_MODULES([GTK], [gtk+-3.0 >= $GTK_REQUIRED])
 PKG_CHECK_MODULES([GTK_UNIX_PRINT], [gtk+-unix-print-3.0 >= $GTK_REQUIRED])
 PKG_CHECK_MODULES([ICU_UC], [icu-uc >= 4.6])
 PKG_CHECK_MODULES([HOGWEED], [hogweed >= 3.2])
-PKG_CHECK_MODULES([JSON_GLIB], [json-glib-1.0 >= 1.2.0])
+PKG_CHECK_MODULES([JSON_GLIB], [json-glib-1.0 >= 1.2.4])
 PKG_CHECK_MODULES([LIBNOTIFY], [libnotify >= 0.5.1])
 PKG_CHECK_MODULES([LIBSECRET], [libsecret-1 >= 0.14])
 PKG_CHECK_MODULES([LIBSOUP], [libsoup-2.4 >= 2.48.0])
diff --git a/data/org.gnome.epiphany.gschema.xml b/data/org.gnome.epiphany.gschema.xml
index 6540572..2264bbd 100644
--- a/data/org.gnome.epiphany.gschema.xml
+++ b/data/org.gnome.epiphany.gschema.xml
@@ -6,6 +6,7 @@
                <child schema="org.gnome.Epiphany.state" name="state"/>
                <child schema="org.gnome.Epiphany.lockdown" name="lockdown"/>
                <child schema="org.gnome.Epiphany.permissions" name="permissions"/>
+               <child schema="org.gnome.Epiphany.sync" name="sync"/>
                <key type="b" name="enable-caret-browsing">
                        <default>false</default>
                        <summary>Browse with caret</summary>
@@ -99,16 +100,6 @@
                         <description>This option sets a limit to the number of web processes that will be 
used at the same time for the “one-secondary-process-per-web-view” model. The default value is “0” and means 
no limit.</description>
 
                 </key>
-               <key type="s" name="sync-user">
-                       <default>''</default>
-                       <summary>The sync user currently logged in</summary>
-                       <description>The email linked to the Firefox Account used to sync data with Mozilla’s 
servers.</description>
-               </key>
-               <key type="d" name="sync-time">
-                       <default>0</default>
-                       <summary>Sync timestamp</summary>
-                       <description>The timestamp at which last we had the last sync</description>
-               </key>
                 <key type="as" name="adblock-filters">
                         <default>['https://easylist.to/easylist/easylist.txt', 
'https://easylist.to/easylist/easyprivacy.txt']</default>
                         <summary>List of adblock filters</summary>
@@ -275,6 +266,43 @@
                        <default>false</default>
                </key>
        </schema>
+       <schema path="/org/gnome/Epiphany/sync/" id="org.gnome.Epiphany.sync">
+               <key type="s" name="sync-user">
+                       <default>''</default>
+                       <summary>Currently signed in sync user</summary>
+                       <description>The email linked to the Firefox Account used to sync data with Mozilla’s 
servers.</description>
+               </key>
+               <key type="s" name="sync-client-id">
+                       <default>''</default>
+                       <summary>Sync client ID</summary>
+                       <description>The sync client ID of the current device.</description>
+               </key>
+               <key type="u" name="sync-frequency">
+                       <default>30</default>
+                       <summary>The sync frequency in minutes</summary>
+                       <description>The number of minutes between two consecutive syncs.</description>
+               </key>
+               <key type="b" name="sync-with-firefox">
+                       <default>false</default>
+                       <summary>Sync data with Firefox</summary>
+                       <description>TRUE if Ephy collections should be synced with Firefox collections, 
FALSE otherwise.</description>
+               </key>
+               <key type="b" name="sync-bookmarks-enabled">
+                       <default>false</default>
+                       <summary>Enable bookmarks sync</summary>
+                       <description>TRUE if bookmarks collection should be synced, FALSE 
otherwise.</description>
+               </key>
+               <key type="d" name="sync-bookmarks-time">
+                       <default>0</default>
+                       <summary>Bookmarks sync timestamp</summary>
+                       <description>The timestamp at which last bookmarks sync was made.</description>
+               </key>
+               <key type="b" name="sync-bookmarks-initial">
+                       <default>true</default>
+                       <summary>Initial sync or normal sync</summary>
+                       <description>TRUE if bookmarks collection needs to be synced for the first time, 
FALSE otherwise.</description>
+               </key>
+       </schema>
        <enum id="org.gnome.Epiphany.Permission">
                <value nick="undecided" value="-1"/>
                <value nick="deny" value="0"/>
diff --git a/lib/ephy-prefs.h b/lib/ephy-prefs.h
index 919037e..3167d12 100644
--- a/lib/ephy-prefs.h
+++ b/lib/ephy-prefs.h
@@ -136,8 +136,6 @@ static const char * const ephy_prefs_web_schema[] = {
 #define EPHY_PREFS_RESTORE_SESSION_DELAYING_LOADS     "restore-session-delaying-loads"
 #define EPHY_PREFS_PROCESS_MODEL                      "process-model"
 #define EPHY_PREFS_MAX_PROCESSES                      "max-processes"
-#define EPHY_PREFS_SYNC_USER                          "sync-user"
-#define EPHY_PREFS_SYNC_TIME                          "sync-time"
 #define EPHY_PREFS_ADBLOCK_FILTERS                    "adblock-filters"
 #define EPHY_PREFS_SEARCH_ENGINES                     "search-engines"
 #define EPHY_PREFS_DEFAULT_SEARCH_ENGINE              "default-search-engine"
@@ -151,6 +149,15 @@ static const char * const ephy_prefs_web_schema[] = {
 #define EPHY_PREFS_LOCKDOWN_PRINTING          "disable-printing"
 #define EPHY_PREFS_LOCKDOWN_QUIT              "disable-quit"
 
+#define EPHY_PREFS_SYNC_SCHEMA            "org.gnome.Epiphany.sync"
+#define EPHY_PREFS_SYNC_USER              "sync-user"
+#define EPHY_PREFS_SYNC_CLIENT_ID         "sync-client-id"
+#define EPHY_PREFS_SYNC_FREQUENCY         "sync-frequency"
+#define EPHY_PREFS_SYNC_WITH_FIREFOX      "sync-with-firefox"
+#define EPHY_PREFS_SYNC_BOOKMARKS_ENABLED "sync-bookmarks-enabled"
+#define EPHY_PREFS_SYNC_BOOKMARKS_TIME    "sync-bookmarks-time"
+#define EPHY_PREFS_SYNC_BOOKMARKS_INITIAL "sync-bookmarks-initial"
+
 static struct {
   const char *schema;
   const char *path;
diff --git a/lib/ephy-settings.h b/lib/ephy-settings.h
index 23d80d3..ee186c4 100644
--- a/lib/ephy-settings.h
+++ b/lib/ephy-settings.h
@@ -32,6 +32,7 @@ G_BEGIN_DECLS
 #define EPHY_SETTINGS_WEB       ephy_settings_get (EPHY_PREFS_WEB_SCHEMA)
 #define EPHY_SETTINGS_LOCKDOWN  ephy_settings_get (EPHY_PREFS_LOCKDOWN_SCHEMA)
 #define EPHY_SETTINGS_STATE     ephy_settings_get (EPHY_PREFS_STATE_SCHEMA)
+#define EPHY_SETTINGS_SYNC      ephy_settings_get (EPHY_PREFS_SYNC_SCHEMA)
 
 GSettings *ephy_settings_get (const char *schema);
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 8cafe93..43d7c34 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -79,8 +79,10 @@ libephymain_la_SOURCES = \
        sync/ephy-sync-secret.h                 \
        sync/ephy-sync-service.c                \
        sync/ephy-sync-service.h                \
-       sync/ephy-sync-utils.c                  \
-       sync/ephy-sync-utils.h
+       sync/ephy-synchronizable.c              \
+       sync/ephy-synchronizable.h              \
+       sync/ephy-synchronizable-manager.c      \
+       sync/ephy-synchronizable-manager.h
 
 nodist_libephymain_la_SOURCES = \
        $(TYPES_SOURCE)
diff --git a/src/bookmarks/ephy-add-bookmark-popover.c b/src/bookmarks/ephy-add-bookmark-popover.c
index 195396d..eb0d207 100644
--- a/src/bookmarks/ephy-add-bookmark-popover.c
+++ b/src/bookmarks/ephy-add-bookmark-popover.c
@@ -32,7 +32,6 @@ struct _EphyAddBookmarkPopover {
   GtkPopover     parent_instance;
 
   char          *address;
-  gboolean       is_new_bookmark;
 
   GtkWidget     *grid;
   EphyHeaderBar *header_bar;
@@ -206,14 +205,17 @@ ephy_add_bookmark_popover_show (EphyAddBookmarkPopover *self)
 
   bookmark = ephy_bookmarks_manager_get_bookmark_by_url (manager, address);
   if (!bookmark) {
+    char *id = ephy_sync_crypto_get_random_sync_id ();
     bookmark = ephy_bookmark_new (address,
                                   ephy_embed_get_title (embed),
-                                  g_sequence_new (g_free));
+                                  g_sequence_new (g_free),
+                                  id);
 
     ephy_bookmarks_manager_add_bookmark (manager, bookmark);
     ephy_location_entry_set_bookmark_icon_state (location_entry,
                                                  EPHY_LOCATION_ENTRY_BOOKMARK_ICON_BOOKMARKED);
     g_object_unref (bookmark);
+    g_free (id);
   }
 
   g_signal_connect_object (manager, "bookmark-removed",
diff --git a/src/bookmarks/ephy-bookmark-properties-grid.c b/src/bookmarks/ephy-bookmark-properties-grid.c
index 8b6b7cb..d85f704 100644
--- a/src/bookmarks/ephy-bookmark-properties-grid.c
+++ b/src/bookmarks/ephy-bookmark-properties-grid.c
@@ -50,10 +50,6 @@ struct _EphyBookmarkPropertiesGrid {
   GtkWidget                      *add_tag_entry;
   GtkWidget                      *add_tag_button;
   GtkWidget                      *remove_bookmark_button;
-
-  char                           *prev_name;
-  char                           *prev_address;
-  GSequence                      *prev_tags;
 };
 
 G_DEFINE_TYPE (EphyBookmarkPropertiesGrid, ephy_bookmark_properties_grid, GTK_TYPE_GRID)
@@ -248,7 +244,10 @@ ephy_bookmarks_properties_grid_actions_remove_bookmark (GSimpleAction *action,
   g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
 
   service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  ephy_sync_service_delete_bookmark (service, self->bookmark, FALSE);
+  if (ephy_sync_service_is_signed_in (service))
+    ephy_sync_service_delete_synchronizable (service,
+                                             EPHY_SYNCHRONIZABLE_MANAGER (self->manager),
+                                             EPHY_SYNCHRONIZABLE (self->bookmark));
   ephy_bookmarks_manager_remove_bookmark (self->manager,  self->bookmark);
 
   if (self->type == EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_DIALOG)
@@ -322,7 +321,6 @@ ephy_bookmark_properties_grid_constructed (GObject *object)
   /* Set text for name entry */
   gtk_entry_set_text (GTK_ENTRY (self->name_entry),
                       ephy_bookmark_get_title (self->bookmark));
-  self->prev_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->name_entry)));
 
   g_object_bind_property (GTK_ENTRY (self->name_entry), "text",
                           self->bookmark, "title",
@@ -335,15 +333,13 @@ ephy_bookmark_properties_grid_constructed (GObject *object)
     decoded_address = ephy_uri_decode (address);
     gtk_entry_set_text (GTK_ENTRY (self->address_entry), decoded_address);
     g_free (decoded_address);
-    self->prev_address = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->address_entry)));
 
     g_object_bind_property (GTK_ENTRY (self->address_entry), "text",
-                            self->bookmark, "url",
+                            self->bookmark, "bmkUri",
                             G_BINDING_DEFAULT);
   }
 
   /* Create tag widgets */
-  self->prev_tags = g_sequence_new (g_free);
   tags = ephy_bookmarks_manager_get_tags (self->manager);
   bookmark_tags = ephy_bookmark_get_tags (self->bookmark);
   for (iter = g_sequence_get_begin_iter (tags);
@@ -356,11 +352,8 @@ ephy_bookmark_properties_grid_constructed (GObject *object)
     if (g_sequence_lookup (bookmark_tags,
                            (gpointer)tag,
                            (GCompareDataFunc)ephy_bookmark_tags_compare,
-                           NULL)) {
+                           NULL))
       selected = TRUE;
-      g_sequence_insert_sorted (self->prev_tags, g_strdup (tag),
-                                (GCompareDataFunc)ephy_bookmark_tags_compare, NULL);
-    }
 
     widget = ephy_bookmark_properties_grid_create_tag_widget (self, tag, selected);
     gtk_flow_box_insert (GTK_FLOW_BOX (self->tags_box), widget, -1);
@@ -374,56 +367,10 @@ ephy_bookmark_properties_grid_constructed (GObject *object)
 }
 
 static void
-ephy_bookmark_properties_grid_check_prev_values (EphyBookmarkPropertiesGrid *self)
-{
-  if (ephy_bookmark_is_uploaded (self->bookmark) == FALSE)
-    return;
-
-  /* Check if any actual changes were made to the name, address or tags. If yes,
-   * set the uploaded flag to FALSE. */
-
-  if (g_strcmp0 (self->prev_name, ephy_bookmark_get_title (self->bookmark)) != 0) {
-    ephy_bookmark_set_is_uploaded (self->bookmark, FALSE);
-    return;
-  }
-
-  if (g_strcmp0 (self->prev_address, ephy_bookmark_get_url (self->bookmark)) != 0) {
-    ephy_bookmark_set_is_uploaded (self->bookmark, FALSE);
-    return;
-  }
-
-  if (self->prev_tags != NULL) {
-    GSequence *tags = ephy_bookmark_get_tags (self->bookmark);
-    GSequenceIter *iter;
-
-    /* Check for added tags. */
-    for (iter = g_sequence_get_begin_iter (tags);
-         !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
-      if (!g_sequence_lookup (self->prev_tags, g_sequence_get (iter),
-                              (GCompareDataFunc)ephy_bookmark_tags_compare, NULL)) {
-        ephy_bookmark_set_is_uploaded (self->bookmark, FALSE);
-        return;
-      }
-    }
-
-    /* Check for deleted tags. */
-    for (iter = g_sequence_get_begin_iter (self->prev_tags);
-         !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
-      if (!g_sequence_lookup (tags, g_sequence_get (iter),
-                              (GCompareDataFunc)ephy_bookmark_tags_compare, NULL)) {
-        ephy_bookmark_set_is_uploaded (self->bookmark, FALSE);
-        return;
-      }
-    }
-  }
-}
-
-static void
 ephy_bookmark_properties_grid_finalize (GObject *object)
 {
   EphyBookmarkPropertiesGrid *self = EPHY_BOOKMARK_PROPERTIES_GRID (object);
 
-  ephy_bookmark_properties_grid_check_prev_values (self);
   ephy_bookmarks_manager_save_to_file_async (self->manager, NULL,
                                              ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
                                              NULL);
diff --git a/src/bookmarks/ephy-bookmark.c b/src/bookmarks/ephy-bookmark.c
index 6b19dae..5861043 100644
--- a/src/bookmarks/ephy-bookmark.c
+++ b/src/bookmarks/ephy-bookmark.c
@@ -24,11 +24,14 @@
 
 #include "ephy-shell.h"
 #include "ephy-sync-crypto.h"
-#include "ephy-sync-utils.h"
+#include "ephy-synchronizable.h"
 
 #include <string.h>
 
-#define ID_LEN 32
+#define BOOKMARK_TYPE_VAL            "bookmark"
+#define BOOKMARK_PARENT_ID_VAL       "toolbar"
+#define BOOKMARK_PARENT_NAME_VAL     "Bookmarks Toolbar"
+#define BOOKMARK_LOAD_IN_SIDEBAR_VAL FALSE
 
 struct _EphyBookmark {
   GObject      parent_instance;
@@ -38,28 +41,41 @@ struct _EphyBookmark {
   GSequence   *tags;
   gint64       time_added;
 
-  /* Keep the modified timestamp as double, and not float, to
-   * preserve the precision enforced by the Storage Server. */
+  /* Firefox Sync specific fields.
+   * Time modified timestamp must be double to match server's precision. */
   char        *id;
-  double       modified;
+  char        *type;
+  char        *parent_id;
+  char        *parent_name;
+  gboolean     load_in_sidebar;
+  double       time_modified;
   gboolean     uploaded;
 };
 
-static JsonSerializableIface *serializable_iface = NULL;
-
-static void json_serializable_iface_init (gpointer g_iface);
+static void json_serializable_iface_init (JsonSerializableIface *iface);
+static void ephy_synchronizable_iface_init (EphySynchronizableInterface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (EphyBookmark, ephy_bookmark, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
-                                               json_serializable_iface_init))
+                                               json_serializable_iface_init)
+                        G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE,
+                                               ephy_synchronizable_iface_init))
 
 enum {
   PROP_0,
-  PROP_TAGS,
+  /* Epiphany specific properties. */
   PROP_TIME_ADDED,
+  /* Firefox specific properties (some of these are used by Epiphany too). */
   PROP_TITLE,
-  PROP_URL,
-  LAST_PROP
+  PROP_BMK_URI,
+  PROP_TAGS,
+  PROP_TYPE,
+  PROP_PARENT_ID,
+  PROP_PARENT_NAME,
+  PROP_LOAD_IN_SIDEBAR,
+  LAST_PROP,
+  /* Interface properties. */
+  PROP_ID
 };
 
 enum {
@@ -80,46 +96,79 @@ ephy_bookmark_set_property (GObject      *object,
   EphyBookmark *self = EPHY_BOOKMARK (object);
 
   switch (prop_id) {
-    case PROP_TAGS:
-      if (self->tags != NULL)
-        g_sequence_free (self->tags);
-      self->tags = g_value_get_pointer (value);
-      break;
     case PROP_TIME_ADDED:
       ephy_bookmark_set_time_added (self, g_value_get_int64 (value));
       break;
     case PROP_TITLE:
       ephy_bookmark_set_title (self, g_value_get_string (value));
       break;
-    case PROP_URL:
+    case PROP_BMK_URI:
       ephy_bookmark_set_url (self, g_value_get_string (value));
       break;
+    case PROP_TAGS:
+      if (self->tags != NULL)
+        g_sequence_free (self->tags);
+      self->tags = g_value_get_pointer (value);
+      break;
+    case PROP_TYPE:
+      g_free (self->type);
+      self->type = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_PARENT_ID:
+      g_free (self->parent_id);
+      self->parent_id = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_PARENT_NAME:
+      g_free (self->parent_name);
+      self->parent_name = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_LOAD_IN_SIDEBAR:
+      self->load_in_sidebar = g_value_get_boolean (value);
+      break;
+    case PROP_ID:
+      ephy_bookmark_set_id (self, g_value_get_string (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
   }
 }
 
 static void
-ephy_bookmark_get_property (GObject      *object,
-                            guint         prop_id,
-                            GValue       *value,
-                            GParamSpec   *pspec)
+ephy_bookmark_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
 {
   EphyBookmark *self = EPHY_BOOKMARK (object);
 
   switch (prop_id) {
-    case PROP_TAGS:
-      g_value_set_pointer (value, ephy_bookmark_get_tags (self));
-      break;
     case PROP_TIME_ADDED:
       g_value_set_int64 (value, ephy_bookmark_get_time_added (self));
       break;
     case PROP_TITLE:
       g_value_set_string (value, ephy_bookmark_get_title (self));
       break;
-    case PROP_URL:
+    case PROP_BMK_URI:
       g_value_set_string (value, ephy_bookmark_get_url (self));
       break;
+    case PROP_TAGS:
+      g_value_set_pointer (value, ephy_bookmark_get_tags (self));
+      break;
+    case PROP_TYPE:
+      g_value_set_string (value, self->type);
+      break;
+    case PROP_PARENT_ID:
+      g_value_set_string (value, self->parent_id);
+      break;
+    case PROP_PARENT_NAME:
+      g_value_set_string (value, self->parent_name);
+      break;
+    case PROP_LOAD_IN_SIDEBAR:
+      g_value_set_boolean (value, self->load_in_sidebar);
+      break;
+    case PROP_ID:
+      g_value_set_string (value, ephy_bookmark_get_id (self));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
   }
@@ -134,7 +183,8 @@ ephy_bookmark_finalize (GObject *object)
   g_free (self->title);
   g_free (self->id);
 
-  g_sequence_free (self->tags);
+  if (self->tags)
+    g_sequence_free (self->tags);
 
   G_OBJECT_CLASS (ephy_bookmark_parent_class)->finalize (object);
 }
@@ -148,12 +198,6 @@ ephy_bookmark_class_init (EphyBookmarkClass *klass)
   object_class->get_property = ephy_bookmark_get_property;
   object_class->finalize = ephy_bookmark_finalize;
 
-  obj_properties[PROP_TAGS] =
-    g_param_spec_pointer ("tags",
-                          "Tags",
-                          "The bookmark's tags",
-                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
-
   obj_properties[PROP_TIME_ADDED] =
     g_param_spec_int64 ("time-added",
                         "Time added",
@@ -170,14 +214,49 @@ ephy_bookmark_class_init (EphyBookmarkClass *klass)
                          "Default bookmark title",
                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
-  obj_properties[PROP_URL] =
-    g_param_spec_string ("url",
-                         "URL",
-                         "The bookmark's URL",
+  obj_properties[PROP_BMK_URI] =
+    g_param_spec_string ("bmkUri",
+                         "URI",
+                         "The bookmark's URI",
                          "about:overview",
                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
+  obj_properties[PROP_TAGS] =
+    g_param_spec_pointer ("tags",
+                          "Tags",
+                          "The bookmark's tags",
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+  obj_properties[PROP_TYPE] =
+    g_param_spec_string ("type",
+                         "Type",
+                         "Of type bookmark",
+                         "default",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+  obj_properties[PROP_PARENT_ID] =
+    g_param_spec_string ("parentid",
+                         "ParentID",
+                         "The parent's id",
+                         "default",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+  obj_properties[PROP_PARENT_NAME] =
+    g_param_spec_string ("parentName",
+                         "ParentName",
+                         "The parent's name",
+                         "default",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+  obj_properties[PROP_LOAD_IN_SIDEBAR] =
+    g_param_spec_boolean ("loadInSidebar",
+                          "LoadInSiderbar",
+                          "Load in sidebar",
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
   g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+  g_object_class_override_property (object_class, PROP_ID, "id");
 
   signals[TAG_ADDED] =
     g_signal_new ("tag-added",
@@ -201,8 +280,6 @@ ephy_bookmark_class_init (EphyBookmarkClass *klass)
 static void
 ephy_bookmark_init (EphyBookmark *self)
 {
-  self->id = g_malloc0 (ID_LEN + 1);
-  ephy_sync_crypto_random_hex_gen (NULL, ID_LEN, (guint8 *)self->id);
 }
 
 static JsonNode *
@@ -222,16 +299,19 @@ ephy_bookmark_json_serializable_serialize_property (JsonSerializable *serializab
     array = json_array_new ();
     tags = g_value_get_pointer (value);
 
-    for (iter = g_sequence_get_begin_iter (tags);
-         !g_sequence_iter_is_end (iter);
-         iter = g_sequence_iter_next (iter)) {
-      json_array_add_string_element (array, g_sequence_get (iter));
+    if (tags != NULL) {
+      for (iter = g_sequence_get_begin_iter (tags);
+           !g_sequence_iter_is_end (iter);
+           iter = g_sequence_iter_next (iter)) {
+        json_array_add_string_element (array, g_sequence_get (iter));
+      }
     }
 
     json_node_set_array (node, array);
+  } else if (!g_strcmp0 (name, "time-added")) {
+    /* This is not a Firefox bookmark property, skip it.  */
   } else {
-    node = serializable_iface->serialize_property (serializable, name,
-                                                   value, pspec);
+    node = json_serializable_default_serialize_property (serializable, name, value, pspec);
   }
 
   return node;
@@ -260,33 +340,79 @@ ephy_bookmark_json_serializable_deserialize_property (JsonSerializable *serializ
     }
 
     g_value_set_pointer (value, tags);
-  } else {
-    serializable_iface->deserialize_property (serializable, name,
-                                              value, pspec, node);
+
+    return TRUE;
   }
 
-  return TRUE;
+  return json_serializable_default_deserialize_property (serializable, name, value, pspec, node);
 }
 
 static void
-json_serializable_iface_init (gpointer g_iface)
+json_serializable_iface_init (JsonSerializableIface *iface)
 {
-  JsonSerializableIface *iface = g_iface;
-
-  serializable_iface = g_type_default_interface_peek (JSON_TYPE_SERIALIZABLE);
-
   iface->serialize_property = ephy_bookmark_json_serializable_serialize_property;
   iface->deserialize_property = ephy_bookmark_json_serializable_deserialize_property;
 }
 
+static const char *
+ephy_bookmark_synchronizable_get_id (EphySynchronizable *synchronizable)
+{
+  return ephy_bookmark_get_id (EPHY_BOOKMARK (synchronizable));
+}
+
+static double
+ephy_bookmark_synchronizable_get_time_modified (EphySynchronizable *synchronizable)
+{
+  return ephy_bookmark_get_time_modified (EPHY_BOOKMARK (synchronizable));
+}
+
+static void
+ephy_bookmark_synchronizable_set_time_modified (EphySynchronizable *synchronizable,
+                                                double              time_modified)
+{
+  ephy_bookmark_set_time_modified (EPHY_BOOKMARK (synchronizable), time_modified);
+}
+
+static void
+ephy_bookmark_synchronizable_set_is_uploaded (EphySynchronizable *synchronizable,
+                                              gboolean            uploaded)
+{
+  ephy_bookmark_set_is_uploaded (EPHY_BOOKMARK (synchronizable), uploaded);
+}
+
+static JsonNode *
+ephy_bookmark_synchronizable_to_bso (EphySynchronizable  *synchronizable,
+                                     SyncCryptoKeyBundle *bundle)
+{
+  return ephy_synchronizable_default_to_bso (synchronizable, bundle);
+}
+
+static void
+ephy_synchronizable_iface_init (EphySynchronizableInterface *iface)
+{
+  iface->get_id = ephy_bookmark_synchronizable_get_id;
+  iface->get_time_modified = ephy_bookmark_synchronizable_get_time_modified;
+  iface->set_time_modified = ephy_bookmark_synchronizable_set_time_modified;
+  iface->set_is_uploaded = ephy_bookmark_synchronizable_set_is_uploaded;
+  iface->to_bso = ephy_bookmark_synchronizable_to_bso;
+}
+
 EphyBookmark *
-ephy_bookmark_new (const char *url, const char *title, GSequence *tags)
+ephy_bookmark_new (const char *url,
+                   const char *title,
+                   GSequence  *tags,
+                   const char *id)
 {
   return g_object_new (EPHY_TYPE_BOOKMARK,
-                       "url", url,
+                       "time-added", g_get_real_time (),
                        "title", title,
+                       "bmkUri", url,
                        "tags", tags,
-                       "time-added", g_get_real_time (),
+                       "type", BOOKMARK_TYPE_VAL,
+                       "parentid", BOOKMARK_PARENT_ID_VAL,
+                       "parentName", BOOKMARK_PARENT_NAME_VAL,
+                       "loadInSidebar", BOOKMARK_LOAD_IN_SIDEBAR_VAL,
+                       "id", id,
                        NULL);
 }
 
@@ -364,20 +490,20 @@ ephy_bookmark_get_id (EphyBookmark *self)
 }
 
 void
-ephy_bookmark_set_modification_time (EphyBookmark *self,
-                                     double        modified)
+ephy_bookmark_set_time_modified (EphyBookmark *self,
+                                 double        time_modified)
 {
   g_return_if_fail (EPHY_IS_BOOKMARK (self));
 
-  self->modified = modified;
+  self->time_modified = time_modified;
 }
 
 double
-ephy_bookmark_get_modification_time (EphyBookmark *self)
+ephy_bookmark_get_time_modified (EphyBookmark *self)
 {
   g_return_val_if_fail (EPHY_IS_BOOKMARK (self), -1);
 
-  return self->modified;
+  return self->time_modified;
 }
 
 void
@@ -516,91 +642,3 @@ ephy_bookmark_tags_compare (const char *tag1, const char *tag2)
 
   return result;
 }
-
-char *
-ephy_bookmark_to_bso (EphyBookmark *self)
-{
-  EphySyncService *service;
-  guint8 *encrypted;
-  guint8 *sync_key;
-  char *serialized;
-  char *payload;
-  char *bso;
-  gsize length;
-
-  g_return_val_if_fail (EPHY_IS_BOOKMARK (self), NULL);
-
-  /* Convert a Bookmark object to a BSO (Basic Store Object). That is a generic
-   * JSON wrapper around all items passed into and out of the SyncStorage server.
-   * The current flow is:
-   * 1. Serialize the Bookmark to a JSON string.
-   * 2. Encrypt the JSON string using the sync key from the sync service.
-   * 3. Encode the encrypted bytes to base64 url safe.
-   * 4. Create a new JSON string that contains the id of the Bookmark and the
-        encoded bytes as payload. This is actually the BSO that is going to be
-        stored on the SyncStorage server.
-   * See https://docs.services.mozilla.com/storage/apis-1.5.html
-   */
-
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  sync_key = ephy_sync_crypto_decode_hex (ephy_sync_service_get_token (service, TOKEN_KB));
-  serialized = json_gobject_to_data (G_OBJECT (self), NULL);
-  encrypted = ephy_sync_crypto_aes_256 (AES_256_MODE_ENCRYPT, sync_key,
-                                        (guint8 *)serialized, strlen (serialized), &length);
-  payload = ephy_sync_crypto_base64_urlsafe_encode (encrypted, length, FALSE);
-  bso = ephy_sync_utils_create_bso_json (self->id, payload);
-
-  g_free (sync_key);
-  g_free (serialized);
-  g_free (encrypted);
-  g_free (payload);
-
-  return bso;
-}
-
-EphyBookmark *
-ephy_bookmark_from_bso (JsonObject *bso)
-{
-  EphySyncService *service;
-  EphyBookmark *bookmark = NULL;
-  GObject *object;
-  GError *error = NULL;
-  guint8 *sync_key;
-  guint8 *decoded;
-  gsize decoded_len;
-  char *decrypted;
-
-  g_return_val_if_fail (bso != NULL, NULL);
-
-  /* Convert a BSO to a Bookmark object. The flow is similar to the one from
-   * ephy_bookmark_to_bso(), only that the steps are reversed:
-   * 1. Decode the payload from base64 url safe to raw bytes.
-   * 2. Decrypt the bytes using the sync key to obtain the serialized Bookmark.
-   * 3. Deserialize the JSON string into a Bookmark object.
-   */
-
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  sync_key = ephy_sync_crypto_decode_hex (ephy_sync_service_get_token (service, TOKEN_KB));
-  decoded = ephy_sync_crypto_base64_urlsafe_decode (json_object_get_string_member (bso, "payload"),
-                                                    &decoded_len, FALSE);
-  decrypted = (char *)ephy_sync_crypto_aes_256 (AES_256_MODE_DECRYPT, sync_key,
-                                                decoded, decoded_len, NULL);
-  object = json_gobject_from_data (EPHY_TYPE_BOOKMARK, decrypted, strlen (decrypted), &error);
-
-  if (object == NULL) {
-    g_warning ("Failed to create GObject from data: %s", error->message);
-    g_error_free (error);
-    goto out;
-  }
-
-  bookmark = EPHY_BOOKMARK (object);
-  ephy_bookmark_set_id (bookmark, json_object_get_string_member (bso, "id"));
-  ephy_bookmark_set_modification_time (bookmark, json_object_get_double_member (bso, "modified"));
-  ephy_bookmark_set_is_uploaded (bookmark, TRUE);
-
-out:
-  g_free (decoded);
-  g_free (decrypted);
-
-  return bookmark;
-}
diff --git a/src/bookmarks/ephy-bookmark.h b/src/bookmarks/ephy-bookmark.h
index 9e950d9..729f5f0 100644
--- a/src/bookmarks/ephy-bookmark.h
+++ b/src/bookmarks/ephy-bookmark.h
@@ -29,48 +29,46 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphyBookmark, ephy_bookmark, EPHY, BOOKMARK, GObject)
 
-EphyBookmark        *ephy_bookmark_new                   (const char *url,
-                                                          const char *title,
-                                                          GSequence  *tags);
-
-void                 ephy_bookmark_set_time_added        (EphyBookmark *self,
-                                                          gint64        time_added);
-gint64               ephy_bookmark_get_time_added        (EphyBookmark *self);
-
-void                 ephy_bookmark_set_url               (EphyBookmark *self,
-                                                          const char   *url);
-const char          *ephy_bookmark_get_url               (EphyBookmark *self);
-
-void                 ephy_bookmark_set_title             (EphyBookmark *self,
-                                                          const char   *title);
-const char          *ephy_bookmark_get_title             (EphyBookmark *self);
-
-void                 ephy_bookmark_set_id                (EphyBookmark *self,
-                                                          const char   *id);
-const char          *ephy_bookmark_get_id                (EphyBookmark *self);
-
-void                 ephy_bookmark_set_modification_time (EphyBookmark *self,
-                                                          double        modified);
-double               ephy_bookmark_get_modification_time (EphyBookmark *self);
-
-void                 ephy_bookmark_set_is_uploaded       (EphyBookmark *self,
-                                                          gboolean      uploaded);
-gboolean             ephy_bookmark_is_uploaded           (EphyBookmark *self);
-
-void                 ephy_bookmark_add_tag               (EphyBookmark *self,
-                                                          const char   *tag);
-void                 ephy_bookmark_remove_tag            (EphyBookmark *self,
-                                                          const char   *tag);
-gboolean             ephy_bookmark_has_tag               (EphyBookmark *self,
-                                                          const char   *tag);
-GSequence           *ephy_bookmark_get_tags              (EphyBookmark *self);
-
-int                  ephy_bookmark_bookmarks_compare_func   (EphyBookmark *bookmark1,
-                                                             EphyBookmark *bookmark2);
-int                  ephy_bookmark_tags_compare          (const char *tag1,
-                                                          const char *tag2);
-
-char                *ephy_bookmark_to_bso                (EphyBookmark *self);
-EphyBookmark        *ephy_bookmark_from_bso              (JsonObject *bso);
+EphyBookmark        *ephy_bookmark_new                    (const char *url,
+                                                           const char *title,
+                                                           GSequence  *tags,
+                                                           const char *id);
+
+void                 ephy_bookmark_set_time_added         (EphyBookmark *self,
+                                                           gint64        time_added);
+gint64               ephy_bookmark_get_time_added         (EphyBookmark *self);
+
+void                 ephy_bookmark_set_url                (EphyBookmark *self,
+                                                           const char   *url);
+const char          *ephy_bookmark_get_url                (EphyBookmark *self);
+
+void                 ephy_bookmark_set_title              (EphyBookmark *self,
+                                                           const char   *title);
+const char          *ephy_bookmark_get_title              (EphyBookmark *self);
+
+void                 ephy_bookmark_set_id                 (EphyBookmark *self,
+                                                           const char   *id);
+const char          *ephy_bookmark_get_id                 (EphyBookmark *self);
+
+void                 ephy_bookmark_set_time_modified      (EphyBookmark *self,
+                                                           double        time_modified);
+double               ephy_bookmark_get_time_modified      (EphyBookmark *self);
+
+void                 ephy_bookmark_set_is_uploaded        (EphyBookmark *self,
+                                                           gboolean      uploaded);
+gboolean             ephy_bookmark_is_uploaded            (EphyBookmark *self);
+
+void                 ephy_bookmark_add_tag                (EphyBookmark *self,
+                                                           const char   *tag);
+void                 ephy_bookmark_remove_tag             (EphyBookmark *self,
+                                                           const char   *tag);
+gboolean             ephy_bookmark_has_tag                (EphyBookmark *self,
+                                                           const char   *tag);
+GSequence           *ephy_bookmark_get_tags               (EphyBookmark *self);
+
+int                  ephy_bookmark_bookmarks_compare_func (EphyBookmark *bookmark1,
+                                                           EphyBookmark *bookmark2);
+int                  ephy_bookmark_tags_compare           (const char *tag1,
+                                                           const char *tag2);
 
 G_END_DECLS
diff --git a/src/bookmarks/ephy-bookmarks-export.c b/src/bookmarks/ephy-bookmarks-export.c
index 4e4525b..4284c2b 100644
--- a/src/bookmarks/ephy-bookmarks-export.c
+++ b/src/bookmarks/ephy-bookmarks-export.c
@@ -45,7 +45,7 @@ build_variant (EphyBookmark *bookmark)
   g_variant_builder_add (&builder, "x", ephy_bookmark_get_time_added (bookmark));
   g_variant_builder_add (&builder, "s", ephy_bookmark_get_title (bookmark));
   g_variant_builder_add (&builder, "s", ephy_bookmark_get_id (bookmark));
-  g_variant_builder_add (&builder, "d", ephy_bookmark_get_modification_time (bookmark));
+  g_variant_builder_add (&builder, "d", ephy_bookmark_get_time_modified (bookmark));
   g_variant_builder_add (&builder, "b", ephy_bookmark_is_uploaded (bookmark));
 
   g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
diff --git a/src/bookmarks/ephy-bookmarks-import.c b/src/bookmarks/ephy-bookmarks-import.c
index 8d252f8..9c20654 100644
--- a/src/bookmarks/ephy-bookmarks-import.c
+++ b/src/bookmarks/ephy-bookmarks-import.c
@@ -59,13 +59,13 @@ get_bookmarks_from_table (GvdbTable *table)
     const char *title;
     gint64 time_added;
     char *id;
-    double modified;
+    double time_modified;
     gboolean uploaded;
 
     /* Obtain the corresponding GVariant. */
     value = gvdb_table_get_value (table, list[i]);
 
-    g_variant_get (value, "(x&s&sdbas)", &time_added, &title, &id, &modified, &uploaded, &iter);
+    g_variant_get (value, "(x&s&sdbas)", &time_added, &title, &id, &time_modified, &uploaded, &iter);
 
     /* Add all stored tags in a GSequence. */
     tags = g_sequence_new (g_free);
@@ -77,10 +77,9 @@ get_bookmarks_from_table (GvdbTable *table)
     g_variant_iter_free (iter);
 
     /* Create the new bookmark. */
-    bookmark = ephy_bookmark_new (list[i], title, tags);
+    bookmark = ephy_bookmark_new (list[i], title, tags, id);
     ephy_bookmark_set_time_added (bookmark, time_added);
-    ephy_bookmark_set_id (bookmark, id);
-    ephy_bookmark_set_modification_time (bookmark, modified);
+    ephy_bookmark_set_time_modified (bookmark, time_modified);
     ephy_bookmark_set_is_uploaded (bookmark, uploaded);
     g_sequence_prepend (bookmarks, bookmark);
 
@@ -214,7 +213,7 @@ ephy_bookmarks_import_from_firefox (EphyBookmarksManager  *manager,
   GSequence *bookmarks = NULL;
   gboolean ret = TRUE;
   gchar *filename;
-  const char *statement_str = "SELECT b.id, p.url, b.title, b.dateAdded "
+  const char *statement_str = "SELECT b.id, p.url, b.title, b.dateAdded, b.guid "
                               "FROM moz_bookmarks b "
                               "JOIN moz_places p ON b.fk=p.id "
                               "WHERE b.type=1 AND p.url NOT LIKE 'about%' "
@@ -260,11 +259,12 @@ ephy_bookmarks_import_from_firefox (EphyBookmarksManager  *manager,
     const char *url = ephy_sqlite_statement_get_column_as_string (statement, 1);
     const char *title = ephy_sqlite_statement_get_column_as_string (statement, 2);
     gint64 time_added = ephy_sqlite_statement_get_column_as_int64 (statement, 3);
+    const char *guid = ephy_sqlite_statement_get_column_as_string (statement, 4);
     EphyBookmark *bookmark;
     GSequence *tags;
 
     tags = g_sequence_new (g_free);
-    bookmark = ephy_bookmark_new (url, title, tags);
+    bookmark = ephy_bookmark_new (url, title, tags, guid);
     ephy_bookmark_set_time_added (bookmark, time_added);
     load_tags_for_bookmark (connection, bookmark, bookmark_id);
 
diff --git a/src/bookmarks/ephy-bookmarks-manager.c b/src/bookmarks/ephy-bookmarks-manager.c
index 1c9b8c0..e4e8911 100644
--- a/src/bookmarks/ephy-bookmarks-manager.c
+++ b/src/bookmarks/ephy-bookmarks-manager.c
@@ -26,6 +26,8 @@
 #include "ephy-bookmarks-import.h"
 #include "ephy-debug.h"
 #include "ephy-file-helpers.h"
+#include "ephy-settings.h"
+#include "ephy-synchronizable-manager.h"
 
 #include <string.h>
 
@@ -41,9 +43,13 @@ struct _EphyBookmarksManager {
 };
 
 static void list_model_iface_init     (GListModelInterface *iface);
+static void ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface);
 
-G_DEFINE_TYPE_EXTENDED (EphyBookmarksManager, ephy_bookmarks_manager, G_TYPE_OBJECT, 0,
-                        G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+G_DEFINE_TYPE_WITH_CODE (EphyBookmarksManager, ephy_bookmarks_manager, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
+                                                list_model_iface_init)
+                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
+                                                ephy_synchronizable_manager_iface_init))
 
 enum {
   BOOKMARK_ADDED,
@@ -71,6 +77,20 @@ ephy_bookmarks_manager_save_to_file (EphyBookmarksManager *self, GTask *task)
 }
 
 static void
+ephy_bookmarks_manager_create_tags_from_bookmark (EphyBookmarksManager *self,
+                                                  EphyBookmark         *bookmark)
+{
+  GSequenceIter *iter;
+
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+  g_assert (EPHY_IS_BOOKMARK (bookmark));
+
+  for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (bookmark));
+       !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
+    ephy_bookmarks_manager_create_tag (self, g_sequence_get (iter));
+}
+
+static void
 ephy_bookmarks_manager_finalize (GObject *object)
 {
   EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (object);
@@ -222,11 +242,192 @@ list_model_iface_init (GListModelInterface *iface)
   iface->get_item = ephy_bookmarks_manager_list_model_get_item;
 }
 
+static const char *
+synchronizable_manager_get_collection_name (EphySynchronizableManager *manager)
+{
+  gboolean sync_with_firefox = g_settings_get_boolean (EPHY_SETTINGS_SYNC,
+                                                       EPHY_PREFS_SYNC_WITH_FIREFOX);
+
+  return sync_with_firefox ? "bookmarks" : "ephy-bookmarks";
+}
+
+static GType
+synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager)
+{
+  return EPHY_TYPE_BOOKMARK;
+}
+
+static gboolean
+synchronizable_manager_is_initial_sync (EphySynchronizableManager *manager)
+{
+  return g_settings_get_boolean (EPHY_SETTINGS_SYNC,
+                                 EPHY_PREFS_SYNC_BOOKMARKS_INITIAL);
+}
+
+static void
+synchronizable_manager_set_is_initial_sync (EphySynchronizableManager *manager,
+                                            gboolean                   is_initial)
+{
+  g_settings_set_boolean (EPHY_SETTINGS_SYNC,
+                          EPHY_PREFS_SYNC_BOOKMARKS_INITIAL,
+                          is_initial);
+}
+
+static double
+synchronizable_manager_get_sync_time (EphySynchronizableManager *manager)
+{
+  return g_settings_get_double (EPHY_SETTINGS_SYNC,
+                                EPHY_PREFS_SYNC_BOOKMARKS_TIME);
+}
+
+static void
+synchronizable_manager_set_sync_time (EphySynchronizableManager *manager,
+                                      double                     sync_time)
+{
+  g_settings_set_double (EPHY_SETTINGS_SYNC,
+                         EPHY_PREFS_SYNC_BOOKMARKS_TIME,
+                         sync_time);
+}
+
+static void
+synchronizable_manager_add (EphySynchronizableManager *manager,
+                            EphySynchronizable        *synchronizable)
+{
+  EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (manager);
+  EphyBookmark *bookmark = EPHY_BOOKMARK (synchronizable);
+
+  ephy_bookmarks_manager_add_bookmark (self, bookmark);
+  ephy_bookmarks_manager_create_tags_from_bookmark (self, bookmark);
+}
+
+static void
+synchronizable_manager_remove (EphySynchronizableManager *manager,
+                               EphySynchronizable        *synchronizable)
+{
+  EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (manager);
+  EphyBookmark *bookmark = EPHY_BOOKMARK (synchronizable);
+
+  ephy_bookmarks_manager_remove_bookmark (self, bookmark);
+}
+
+static GList *
+synchronizable_manager_merge_remotes (EphySynchronizableManager *manager,
+                                      gboolean                   is_initial,
+                                      GList                     *remotes_deleted,
+                                      GList                     *remotes_updated)
+{
+  EphyBookmarksManager *self;
+  EphyBookmark *remote;
+  EphyBookmark *local;
+  GSequence *bookmarks;
+  GSequenceIter *iter;
+  GHashTable *handled;
+  GList *to_upload = NULL;
+  char *type;
+  char *parent_id;
+  const char *id;
+
+  self = EPHY_BOOKMARKS_MANAGER (manager);
+  handled = g_hash_table_new (g_str_hash, g_str_equal);
+
+  /* Ignore remote deleted objects if this is an initial sync. */
+  if (is_initial)
+    goto handle_updated;
+
+  for (GList *l = remotes_deleted; l && l->data; l = l->next) {
+    remote = EPHY_BOOKMARK (l->data);
+    id = ephy_bookmark_get_id (remote);
+    local = ephy_bookmarks_manager_get_bookmark_by_id (self, id);
+    if (local) {
+      if (ephy_bookmark_get_time_modified (local) > ephy_bookmark_get_time_modified (remote)) {
+        to_upload = g_list_prepend (to_upload, g_object_ref (local));
+        g_hash_table_add (handled, (char *)id);
+      } else {
+        ephy_bookmarks_manager_remove_bookmark (self, local);
+      }
+    }
+  }
+
+handle_updated:
+  for (GList *l = remotes_updated; l && l->data; l = l->next) {
+    remote = EPHY_BOOKMARK (l->data);
+    g_object_get (remote, "type", &type, "parentid", &parent_id, NULL);
+
+    /* Ignore mobile/unfiled bookmarks and everything that is not bookmark. */
+    if (g_strcmp0 (type, "bookmark") ||
+        (!g_strcmp0 (parent_id, "mobile") || !g_strcmp0 (parent_id, "unfiled")))
+      goto next;
+
+    /* Bookmarks from server may miss the time added timestamp. */
+    if (!ephy_bookmark_get_time_added (remote))
+      ephy_bookmark_set_time_added (remote, g_get_real_time ());
+
+    id = ephy_bookmark_get_id (remote);
+    local = ephy_bookmarks_manager_get_bookmark_by_id (self, id);
+    if (!local) {
+      /* Add remote bookmark. */
+      ephy_bookmarks_manager_add_bookmark (self, remote);
+      ephy_bookmarks_manager_create_tags_from_bookmark (self, remote);
+    } else {
+      /* Keep the bookmark with most recent time modified timestamp. */
+      if (ephy_bookmark_get_time_modified (remote) > ephy_bookmark_get_time_modified (local)) {
+        ephy_bookmarks_manager_remove_bookmark (self, local);
+        ephy_bookmarks_manager_add_bookmark (self, remote);
+        ephy_bookmarks_manager_create_tags_from_bookmark (self, remote);
+      } else if (ephy_bookmark_get_time_modified (local) > ephy_bookmark_get_time_modified (remote)) {
+        ephy_bookmarks_manager_create_tags_from_bookmark (self, remote);
+        to_upload = g_list_prepend (to_upload, g_object_ref (local));
+      }
+    }
+
+    g_hash_table_add (handled, (char *)id);
+next:
+    g_free (type);
+    g_free (parent_id);
+  }
+
+  /* Upload unhandled bookmarks to server. */
+  bookmarks = ephy_bookmarks_manager_get_bookmarks (self);
+  for (iter = g_sequence_get_begin_iter (bookmarks);
+       !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
+
+    local = EPHY_BOOKMARK (g_sequence_get (iter));
+    id = ephy_bookmark_get_id (local);
+    if (!g_hash_table_contains (handled, id)) {
+      if (is_initial || (!is_initial && !ephy_bookmark_is_uploaded (local)))
+        to_upload = g_list_prepend (to_upload, g_object_ref (local));
+    }
+  }
+
+  /* Commit changes to file. */
+  ephy_bookmarks_manager_save_to_file_async (self, NULL,
+                                             
(GAsyncReadyCallback)ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
+                                             NULL);
+  g_hash_table_destroy (handled);
+
+  return to_upload;
+}
+
+static void
+ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface)
+{
+  iface->get_collection_name = synchronizable_manager_get_collection_name;
+  iface->get_synchronizable_type = synchronizable_manager_get_synchronizable_type;
+  iface->is_initial_sync = synchronizable_manager_is_initial_sync;
+  iface->set_is_initial_sync = synchronizable_manager_set_is_initial_sync;
+  iface->get_sync_time = synchronizable_manager_get_sync_time;
+  iface->set_sync_time = synchronizable_manager_set_sync_time;
+  iface->add = synchronizable_manager_add;
+  iface->remove = synchronizable_manager_remove;
+  iface->merge_remotes = synchronizable_manager_merge_remotes;
+}
+
 static void
 bookmark_title_changed_cb (EphyBookmark         *bookmark,
                            GParamSpec           *pspec,
                            EphyBookmarksManager *self)
 {
+  ephy_bookmark_set_is_uploaded (bookmark, FALSE);
   g_signal_emit (self, signals[BOOKMARK_TITLE_CHANGED], 0, bookmark);
 }
 
@@ -235,6 +436,7 @@ bookmark_url_changed_cb (EphyBookmark         *bookmark,
                          GParamSpec           *pspec,
                          EphyBookmarksManager *self)
 {
+  ephy_bookmark_set_is_uploaded (bookmark, FALSE);
   g_signal_emit (self, signals[BOOKMARK_URL_CHANGED], 0, bookmark);
 }
 
@@ -243,6 +445,7 @@ bookmark_tag_added_cb (EphyBookmark         *bookmark,
                        const char           *tag,
                        EphyBookmarksManager *self)
 {
+  ephy_bookmark_set_is_uploaded (bookmark, FALSE);
   g_signal_emit (self, signals[BOOKMARK_TAG_ADDED], 0, bookmark, tag);
 }
 
@@ -251,6 +454,7 @@ bookmark_tag_removed_cb (EphyBookmark         *bookmark,
                          const char           *tag,
                          EphyBookmarksManager *self)
 {
+  ephy_bookmark_set_is_uploaded (bookmark, FALSE);
   g_signal_emit (self, signals[BOOKMARK_TAG_REMOVED], 0, bookmark, tag);
 }
 
@@ -267,7 +471,7 @@ ephy_bookmarks_manager_watch_bookmark (EphyBookmarksManager *self,
 {
   g_signal_connect_object (bookmark, "notify::title",
                            G_CALLBACK (bookmark_title_changed_cb), self, 0);
-  g_signal_connect_object (bookmark, "notify::url",
+  g_signal_connect_object (bookmark, "notify::bmkUri",
                            G_CALLBACK (bookmark_url_changed_cb), self, 0);
   g_signal_connect_object (bookmark, "tag-added",
                            G_CALLBACK (bookmark_tag_added_cb), self, 0);
@@ -376,8 +580,8 @@ ephy_bookmarks_manager_remove_bookmark (EphyBookmarksManager *self,
   for (iter = g_sequence_get_begin_iter (self->bookmarks);
          !g_sequence_iter_is_end (iter);
          iter = g_sequence_iter_next (iter)) {
-    if (g_strcmp0 (ephy_bookmark_get_url (g_sequence_get (iter)),
-                   ephy_bookmark_get_url (bookmark)) == 0)
+    if (g_strcmp0 (ephy_bookmark_get_id (g_sequence_get (iter)),
+                   ephy_bookmark_get_id (bookmark)) == 0)
       break;
   }
   g_assert (!g_sequence_iter_is_end (iter));
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index 0b40e4b..4801cfd 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -308,27 +308,6 @@ download_started_cb (WebKitWebContext *web_context,
 }
 
 static void
-sync_tokens_load_finished_cb (EphySyncService *service,
-                              GError          *error,
-                              gpointer         user_data)
-{
-  EphyNotification *notification;
-
-  g_assert (EPHY_IS_SYNC_SERVICE (service));
-
-  /* If the tokens were successfully loaded, start the periodical sync.
-   * Otherwise, notify the user to sign in again. */
-  if (error == NULL) {
-    ephy_sync_service_start_periodical_sync (service, TRUE);
-  } else {
-    notification = ephy_notification_new (error->message,
-                                          _("Please visit Preferences and sign in "
-                                            "again to continue the sync process."));
-    ephy_notification_show (notification);
-  }
-}
-
-static void
 ephy_shell_startup (GApplication *application)
 {
   EphyEmbedShell *embed_shell = EPHY_EMBED_SHELL (application);
@@ -366,12 +345,11 @@ ephy_shell_startup (GApplication *application)
                               G_BINDING_SYNC_CREATE);
     }
 
-    /* Create the sync service. */
+    /* Create the sync service and register synchronizable managers. */
     ephy_shell->sync_service = ephy_sync_service_new ();
-    g_signal_connect (ephy_shell->sync_service,
-                      "sync-tokens-load-finished",
-                      G_CALLBACK (sync_tokens_load_finished_cb), NULL);
-
+    if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_ENABLED))
+      ephy_sync_service_register_manager (ephy_shell->sync_service,
+                                          EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_bookmarks_manager 
(ephy_shell)));
     gtk_application_set_app_menu (GTK_APPLICATION (application),
                                   G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu")));
   } else {
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index e9d5410..8aec7bd 100644
--- a/src/prefs-dialog.c
+++ b/src/prefs-dialog.c
@@ -38,8 +38,6 @@
 #include "ephy-settings.h"
 #include "ephy-shell.h"
 #include "ephy-string.h"
-#include "ephy-sync-crypto.h"
-#include "ephy-sync-secret.h"
 #include "ephy-sync-service.h"
 #include "ephy-uri-tester-shared.h"
 #include "clear-data-dialog.h"
@@ -113,32 +111,29 @@ struct _PrefsDialog {
   GHashTable *iso_3166_table;
 
   /* sync */
-  GtkWidget *sync_authenticate_box;
-  GtkWidget *sync_sign_in_box;
-  GtkWidget *sync_sign_in_details;
-  GtkWidget *sync_sign_out_box;
-  GtkWidget *sync_sign_out_details;
+  EphySyncService *sync_service;
+  GtkWidget *sync_page_box;
+  GtkWidget *sync_firefox_iframe_box;
+  GtkWidget *sync_firefox_iframe_label;
+  GtkWidget *sync_firefox_account_box;
+  GtkWidget *sync_firefox_account_label;
   GtkWidget *sync_sign_out_button;
+  GtkWidget *sync_options_box;
+  GtkWidget *sync_with_firefox_checkbutton;
+  GtkWidget *sync_bookmarks_checkbutton;
+  GtkWidget *sync_frequency_5_min_radiobutton;
+  GtkWidget *sync_frequency_15_min_radiobutton;
+  GtkWidget *sync_frequency_30_min_radiobutton;
+  GtkWidget *sync_frequency_60_min_radiobutton;
+  GtkWidget *sync_now_button;
+  guint32 sync_frequency;
+  gboolean sync_was_signed_in;
 
   WebKitWebView *fxa_web_view;
   WebKitUserContentManager *fxa_manager;
   WebKitUserScript *fxa_script;
-  guint fxa_id;
 };
 
-typedef struct {
-  PrefsDialog *dialog;
-  char        *email;
-  char        *uid;
-  char        *sessionToken;
-  char        *keyFetchToken;
-  char        *unwrapBKey;
-  guint8      *tokenID;
-  guint8      *reqHMACkey;
-  guint8      *respHMACkey;
-  guint8      *respXORkey;
-} FxACallbackData;
-
 enum {
   COL_TITLE_ELIDED,
   COL_ENCODING,
@@ -147,57 +142,6 @@ enum {
 
 G_DEFINE_TYPE (PrefsDialog, prefs_dialog, GTK_TYPE_DIALOG)
 
-static FxACallbackData *
-fxa_callback_data_new (PrefsDialog *dialog,
-                       const char  *email,
-                       const char  *uid,
-                       const char  *sessionToken,
-                       const char  *keyFetchToken,
-                       const char  *unwrapBKey,
-                       guint8      *tokenID,
-                       guint8      *reqHMACkey,
-                       guint8      *respHMACkey,
-                       guint8      *respXORkey)
-{
-  FxACallbackData *data = g_slice_new (FxACallbackData);
-
-  data->dialog = g_object_ref (dialog);
-  data->email = g_strdup (email);
-  data->uid = g_strdup (uid);
-  data->sessionToken = g_strdup (sessionToken);
-  data->keyFetchToken = g_strdup (keyFetchToken);
-  data->unwrapBKey = g_strdup (unwrapBKey);
-  data->tokenID = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (data->tokenID, tokenID, EPHY_SYNC_TOKEN_LENGTH);
-  data->reqHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (data->reqHMACkey, reqHMACkey, EPHY_SYNC_TOKEN_LENGTH);
-  data->respHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (data->respHMACkey, respHMACkey, EPHY_SYNC_TOKEN_LENGTH);
-  data->respXORkey = g_malloc (2 * EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (data->respXORkey, respXORkey, 2 * EPHY_SYNC_TOKEN_LENGTH);
-
-  return data;
-}
-
-static void
-fxa_callback_data_free (FxACallbackData *data)
-{
-  g_assert (data != NULL);
-
-  g_object_unref (data->dialog);
-  g_free (data->email);
-  g_free (data->uid);
-  g_free (data->sessionToken);
-  g_free (data->keyFetchToken);
-  g_free (data->unwrapBKey);
-  g_free (data->tokenID);
-  g_free (data->reqHMACkey);
-  g_free (data->respHMACkey);
-  g_free (data->respXORkey);
-
-  g_slice_free (FxACallbackData, data);
-}
-
 static void
 prefs_dialog_finalize (GObject *object)
 {
@@ -221,103 +165,122 @@ prefs_dialog_finalize (GObject *object)
     g_object_unref (dialog->fxa_manager);
   }
 
-  if (dialog->fxa_id != 0) {
-    g_source_remove (dialog->fxa_id);
-    dialog->fxa_id = 0;
+  if (ephy_sync_service_is_signed_in (dialog->sync_service) && !dialog->sync_was_signed_in) {
+    ephy_sync_service_start_periodical_sync (dialog->sync_service);
+  } else if (dialog->sync_frequency != g_settings_get_uint (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_FREQUENCY)) {
+      g_signal_emit_by_name (dialog->sync_service, "sync-frequency-changed");
   }
 
   G_OBJECT_CLASS (prefs_dialog_parent_class)->finalize (object);
 }
 
 static void
-hide_fxa_iframe (PrefsDialog *dialog,
-                 const char  *email)
+sync_with_firefox_toggled_cb (GtkToggleButton *button,
+                              PrefsDialog     *dialog)
 {
-  char *text;
-  char *account;
+  g_settings_set_boolean (EPHY_SETTINGS_SYNC,
+                          EPHY_PREFS_SYNC_BOOKMARKS_INITIAL,
+                          TRUE);
+}
 
-  account = g_strdup_printf ("<b>%s</b>", email);
-  /* Translators: the %s refers to the email of the currently logged in user. */
-  text = g_strdup_printf (_("Currently logged in as %s"), account);
-  gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_out_details), text);
+static void
+sync_bookmarks_toggled_cb (GtkToggleButton *button,
+                           PrefsDialog     *dialog)
+{
+  EphyBookmarksManager *manager;
 
-  gtk_container_remove (GTK_CONTAINER (dialog->sync_authenticate_box),
-                        dialog->sync_sign_in_box);
-  gtk_box_pack_start (GTK_BOX (dialog->sync_authenticate_box),
-                      dialog->sync_sign_out_box,
-                      TRUE, TRUE, 0);
+  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
 
-  g_free (text);
-  g_free (account);
+  if (gtk_toggle_button_get_active (button))
+    ephy_sync_service_register_manager (dialog->sync_service,
+                                        EPHY_SYNCHRONIZABLE_MANAGER (manager));
+  else
+    ephy_sync_service_unregister_manager (dialog->sync_service,
+                                          EPHY_SYNCHRONIZABLE_MANAGER (manager));
 }
 
 static void
-sync_tokens_store_finished_cb (EphySyncService *service,
-                               GError          *error,
-                               PrefsDialog     *dialog)
+sync_finished_cb (EphySyncService *service,
+                  PrefsDialog     *dialog)
 {
   g_assert (EPHY_IS_SYNC_SERVICE (service));
   g_assert (EPHY_IS_PREFS_DIALOG (dialog));
 
-  if (error == NULL) {
-    /* Show the 'Signed in' panel. */
-    hide_fxa_iframe (dialog, ephy_sync_service_get_user_email (service));
-
-    /* Do a first time sync and set a periodical sync to be executed. */
-    ephy_sync_service_sync_bookmarks (service, TRUE);
-    ephy_sync_service_start_periodical_sync (service, FALSE);
-  } else {
-    char *message;
+  gtk_widget_set_sensitive (dialog->sync_now_button, TRUE);
+}
 
-    /* Destroy the current session. */
-    ephy_sync_service_destroy_session (service, NULL);
+static void
+sync_sign_in_details_show (PrefsDialog *dialog,
+                           const char  *text)
+{
+  char *message;
 
-    /* Unset the email and tokens. */
-    g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER, "");
-    ephy_sync_service_clear_tokens (service);
+  g_assert (EPHY_IS_PREFS_DIALOG (dialog));
 
-    /* Display the error message to the user. */
-    message = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>", error->message);
-    gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_in_details), message);
-    gtk_widget_set_visible (dialog->sync_sign_in_details, TRUE);
-    webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+  message = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>", text);
+  gtk_label_set_markup (GTK_LABEL (dialog->sync_firefox_iframe_label), message);
+  gtk_widget_set_visible (dialog->sync_firefox_iframe_label, TRUE);
 
-    g_free (message);
-  }
+  g_free (message);
 }
 
-static gboolean
-poll_fxa_server (gpointer user_data)
+static void
+sync_sign_in_error_cb (EphySyncService *service,
+                       const char      *error,
+                       PrefsDialog     *dialog)
 {
-  FxACallbackData *data;
-  EphySyncService *service;
-  char *bundle;
+  g_assert (EPHY_IS_SYNC_SERVICE (service));
+  g_assert (EPHY_IS_PREFS_DIALOG (dialog));
 
-  data = (FxACallbackData *)user_data;
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  bundle = ephy_sync_service_start_sign_in (service, data->tokenID, data->reqHMACkey);
+  /* Display the error message and reload the iframe. */
+  sync_sign_in_details_show (dialog, error);
+  webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+}
 
-  if (bundle != NULL) {
-    ephy_sync_service_finish_sign_in (service, data->email, data->uid,
-                                      data->sessionToken, data->keyFetchToken,
-                                      data->unwrapBKey, bundle,
-                                      data->respHMACkey, data->respXORkey);
+static void
+sync_secrets_store_finished_cb (EphySyncService *service,
+                                GError          *error,
+                                PrefsDialog     *dialog)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (service));
+  g_assert (EPHY_IS_PREFS_DIALOG (dialog));
 
-    g_free (bundle);
-    fxa_callback_data_free (data);
-    data->dialog->fxa_id = 0;
+  if (error == NULL) {
+    char *text;
+    char *account;
 
-    return G_SOURCE_REMOVE;
-  }
+    /* Show sync options panel. */
+    account = g_strdup_printf ("<b>%s</b>", ephy_sync_service_get_user_email (service));
+    /* Translators: the %s refers to the email of the currently logged in user. */
+    text = g_strdup_printf (_("Currently logged in as %s"), account);
+    gtk_label_set_markup (GTK_LABEL (dialog->sync_firefox_account_label), text);
+    gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+                          dialog->sync_firefox_iframe_box);
+    gtk_box_pack_start (GTK_BOX (dialog->sync_page_box),
+                        dialog->sync_firefox_account_box,
+                        FALSE, FALSE, 0);
+    gtk_box_pack_start (GTK_BOX (dialog->sync_page_box),
+                        dialog->sync_options_box,
+                        FALSE, FALSE, 0);
+
+    g_settings_set_string (EPHY_SETTINGS_SYNC,
+                           EPHY_PREFS_SYNC_USER,
+                           ephy_sync_service_get_user_email (service));
 
-  return G_SOURCE_CONTINUE;
+    g_free (text);
+    g_free (account);
+  } else {
+    /* Display the error message and reload the iframe. */
+    sync_sign_in_details_show (dialog, error->message);
+    webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+  }
 }
 
 static void
-inject_data_to_server (PrefsDialog *dialog,
-                       const char  *type,
-                       const char  *status,
-                       const char  *data)
+sync_send_data_to_fxa_server (PrefsDialog *dialog,
+                              const char  *type,
+                              const char  *status,
+                              const char  *data)
 {
   char *json;
   char *script;
@@ -341,120 +304,118 @@ inject_data_to_server (PrefsDialog *dialog,
 }
 
 static void
-server_message_received_cb (WebKitUserContentManager *manager,
+sync_fxa_server_message_cb (WebKitUserContentManager *manager,
                             WebKitJavascriptResult   *result,
                             PrefsDialog              *dialog)
 {
-  EphySyncService *service;
-  JsonParser *parser;
-  JsonObject *object;
-  JsonObject *detail;
-  char *json_string;
+  JsonNode *node = NULL;
+  JsonObject *json = NULL;
+  JsonObject *detail = NULL;
+  JsonObject *data = NULL;
+  GError *error = NULL;
+  char *json_string = NULL;
   const char *type;
   const char *command;
+  const char *email;
+  const char *uid;
+  const char *sessionToken;
+  const char *keyFetchToken;
+  const char *unwrapBKey;
 
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
   json_string = ephy_embed_utils_get_js_result_as_string (result);
-  parser = json_parser_new ();
-  json_parser_load_from_data (parser, json_string, -1, NULL);
-  object = json_node_get_object (json_parser_get_root (parser));
-  type = json_object_get_string_member (object, "type");
-
-  /* The only message type we can receive is FirefoxAccountsCommand. */
-  if (g_strcmp0 (type, "FirefoxAccountsCommand") != 0) {
+  if (!json_string) {
+    g_warning ("Failed to get JavaScript result as string");
+    goto out_error;
+  }
+  node = json_from_string (json_string, &error);
+  if (error) {
+    g_warning ("Response is not a valid JSON: %s", error->message);
+    goto out_error;
+  }
+  json = json_node_get_object (node);
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out_error;
+  }
+  type = json_object_get_string_member (json, "type");
+  if (!type) {
+    g_warning ("JSON object has missing or invalid 'type' member");
+    goto out_error;
+  }
+  /* The only message type expected is FirefoxAccountsCommand. */
+  if (g_strcmp0 (type, "FirefoxAccountsCommand")) {
     g_warning ("Unknown command type: %s", type);
-    goto out;
+    goto out_error;
+  }
+  detail = json_object_get_object_member (json, "detail");
+  if (!detail) {
+    g_warning ("JSON object has missing or invalid 'detail' member");
+    goto out_error;
   }
-
-  detail = json_object_get_object_member (object, "detail");
   command = json_object_get_string_member (detail, "command");
+  if (!command) {
+    g_warning ("JSON object has missing or invalid 'command' member");
+    goto out_error;
+  }
 
-  if (g_strcmp0 (command, "loaded") == 0) {
-    LOG ("Loaded Firefox Sign In iframe");
-    gtk_widget_set_visible (dialog->sync_sign_in_details, FALSE);
-  } else if (g_strcmp0 (command, "can_link_account") == 0) {
+  if (!g_strcmp0 (command, "loaded")) {
+    LOG ("Firefox Accounts iframe loaded");
+    goto out_no_error;
+  }
+  if (!g_strcmp0 (command, "can_link_account")) {
     /* We need to confirm a relink. */
-    inject_data_to_server (dialog, "message", "can_link_account", "{'ok': true}");
-  } else if (g_strcmp0 (command, "login") == 0) {
-    JsonObject *data = json_object_get_object_member (detail, "data");
-    const char *email = json_object_get_string_member (data, "email");
-    const char *uid = json_object_get_string_member (data, "uid");
-    const char *sessionToken = json_object_get_string_member (data, "sessionToken");
-    const char *keyFetchToken = json_object_get_string_member (data, "keyFetchToken");
-    const char *unwrapBKey = json_object_get_string_member (data, "unwrapBKey");
-    guint8 *tokenID;
-    guint8 *reqHMACkey;
-    guint8 *respHMACkey;
-    guint8 *respXORkey;
-    char *text;
-
-    inject_data_to_server (dialog, "message", "login", NULL);
-
-    /* Cannot retrieve the sync keys without keyFetchToken or unwrapBKey. */
-    if (keyFetchToken == NULL || unwrapBKey == NULL) {
-      g_warning ("Ignoring login with keyFetchToken or unwrapBKey missing!"
-                 "Cannot retrieve sync keys with one of them missing.");
-      ephy_sync_service_destroy_session (service, sessionToken);
-
-      text = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>",
-                              _("Something went wrong, please try again."));
-      gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_in_details), text);
-      gtk_widget_set_visible (dialog->sync_sign_in_details, TRUE);
-      webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
-
-      g_free (text);
-      goto out;
-    }
-
-    /* Derive tokenID, reqHMACkey, respHMACkey and respXORkey from the keyFetchToken.
-     * tokenID and reqHMACkey are used to make a HAWK request to the "GET /account/keys"
-     * API. The server looks up the stored table entry with tokenID, checks the request
-     * HMAC for validity, then returns the pre-encrypted response.
-     * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#fetching-sync-keys */
-    ephy_sync_crypto_process_key_fetch_token (keyFetchToken,
-                                              &tokenID, &reqHMACkey,
-                                              &respHMACkey, &respXORkey);
-
-    /* If the account is not verified, then poll the server repeatedly
-     * until the verification has finished. */
-    if (json_object_get_boolean_member (data, "verified") == FALSE) {
-      FxACallbackData *cb_data;
-
-      text = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>",
-                              _("Please don’t leave this page until you have completed the verification."));
-      gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_in_details), text);
-      gtk_widget_set_visible (dialog->sync_sign_in_details, TRUE);
-
-      cb_data = fxa_callback_data_new (dialog, email, uid, sessionToken,
-                                       keyFetchToken, unwrapBKey, tokenID,
-                                       reqHMACkey, respHMACkey, respXORkey);
-      dialog->fxa_id = g_timeout_add_seconds (2, (GSourceFunc)poll_fxa_server, cb_data);
-
-      g_free (text);
-    } else {
-      char *bundle;
+    sync_send_data_to_fxa_server (dialog, "message", "can_link_account", "{'ok': true}");
+    goto out_no_error;
+  }
+  if (g_strcmp0 (command, "login")) {
+    g_warning ("Unexepected command: %s", command);
+    goto out_error;
+  }
 
-      bundle = ephy_sync_service_start_sign_in (service, tokenID, reqHMACkey);
-      ephy_sync_service_finish_sign_in (service, email, uid, sessionToken, keyFetchToken,
-                                        unwrapBKey, bundle, respHMACkey, respXORkey);
+  /* Login command. */
+  gtk_widget_set_visible (dialog->sync_firefox_iframe_label, FALSE);
+  sync_send_data_to_fxa_server (dialog, "message", "login", NULL);
 
-      g_free (bundle);
-    }
-  } else if (g_strcmp0 (command, "session_status") == 0) {
-    /* We are not signed in at this time, which we signal by returning an error. */
-    inject_data_to_server (dialog, "message", "error", NULL);
-  } else if (g_strcmp0 (command, "sign_out") == 0) {
-    /* We are not signed in at this time. We should never get a sign out message! */
-    inject_data_to_server (dialog, "message", "error", NULL);
+  data = json_object_get_object_member (detail, "data");
+  if (!data) {
+    g_warning ("JSON object has invalid 'data' member");
+    goto out_error;
+  }
+  email = json_object_get_string_member (data, "email");
+  uid = json_object_get_string_member (data, "uid");
+  sessionToken = json_object_get_string_member (data, "sessionToken");
+  keyFetchToken = json_object_get_string_member (data, "keyFetchToken");
+  unwrapBKey = json_object_get_string_member (data, "unwrapBKey");
+  if (!email || !uid || !sessionToken || !keyFetchToken || !unwrapBKey) {
+    g_warning ("JSON object has missing or invalid members");
+    goto out_error;
   }
+  if (!json_object_has_member (data, "verified") ||
+      !JSON_NODE_HOLDS_VALUE (json_object_get_member (data, "verified"))) {
+    g_warning ("JSON object has missing or invalid 'verified' member");
+    goto out_error;
+  }
+
+  if (!json_object_get_boolean_member (data, "verified"))
+    sync_sign_in_details_show (dialog, _("Please don’t leave this page until "
+                                         "you have completed the verification."));
+  ephy_sync_service_do_sign_in (dialog->sync_service, email, uid,
+                                sessionToken, keyFetchToken, unwrapBKey);
+  goto out_no_error;
 
-out:
+out_error:
+  sync_sign_in_details_show (dialog, _("Something went wrong, please try again."));
+  webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+out_no_error:
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
   g_free (json_string);
-  g_object_unref (parser);
 }
 
 static void
-setup_fxa_sign_in_view (PrefsDialog *dialog)
+sync_setup_firefox_iframe (PrefsDialog *dialog)
 {
   EphyEmbedShell *shell;
   WebKitWebContext *embed_context;
@@ -466,72 +427,73 @@ setup_fxa_sign_in_view (PrefsDialog *dialog)
                    "};"
                    "window.addEventListener(\"FirefoxAccountsCommand\", handleAccountsCommand);";
 
-  dialog->fxa_script = webkit_user_script_new (js,
-                                               WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
-                                               WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
-                                               NULL, NULL);
-  dialog->fxa_manager = webkit_user_content_manager_new ();
-  webkit_user_content_manager_add_script (dialog->fxa_manager, dialog->fxa_script);
-  g_signal_connect (dialog->fxa_manager,
-                    "script-message-received::accountsCommandHandler",
-                    G_CALLBACK (server_message_received_cb),
-                    dialog);
-  webkit_user_content_manager_register_script_message_handler (dialog->fxa_manager,
-                                                               "accountsCommandHandler");
-
-  shell = ephy_embed_shell_get_default ();
-  embed_context = ephy_embed_shell_get_web_context (shell);
-
-  sync_context = webkit_web_context_new ();
-  webkit_web_context_set_preferred_languages (sync_context,
-                                              g_object_get_data (G_OBJECT (embed_context), 
"preferred-languages"));
-  dialog->fxa_web_view = WEBKIT_WEB_VIEW (g_object_new (WEBKIT_TYPE_WEB_VIEW,
-                                                        "user-content-manager", dialog->fxa_manager,
-                                                        "settings", ephy_embed_prefs_get_settings (),
-                                                        "web-context", sync_context,
-                                                        NULL));
-  g_object_unref (sync_context);
-
-  gtk_widget_set_visible (GTK_WIDGET (dialog->fxa_web_view), TRUE);
-  gtk_widget_set_size_request (GTK_WIDGET (dialog->fxa_web_view), 450, 450);
-  webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+  if (dialog->fxa_web_view == NULL) {
+    dialog->fxa_script = webkit_user_script_new (js,
+                                                 WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
+                                                 WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
+                                                 NULL, NULL);
+    dialog->fxa_manager = webkit_user_content_manager_new ();
+    webkit_user_content_manager_add_script (dialog->fxa_manager, dialog->fxa_script);
+    g_signal_connect (dialog->fxa_manager,
+                      "script-message-received::accountsCommandHandler",
+                      G_CALLBACK (sync_fxa_server_message_cb),
+                      dialog);
+    webkit_user_content_manager_register_script_message_handler (dialog->fxa_manager,
+                                                                 "accountsCommandHandler");
+
+    shell = ephy_embed_shell_get_default ();
+    embed_context = ephy_embed_shell_get_web_context (shell);
+    sync_context = webkit_web_context_new ();
+    webkit_web_context_set_preferred_languages (sync_context,
+                                                g_object_get_data (G_OBJECT (embed_context), 
"preferred-languages"));
+
+    dialog->fxa_web_view = WEBKIT_WEB_VIEW (g_object_new (WEBKIT_TYPE_WEB_VIEW,
+                                                          "user-content-manager", dialog->fxa_manager,
+                                                          "settings", ephy_embed_prefs_get_settings (),
+                                                          "web-context", sync_context,
+                                                          NULL));
+    gtk_widget_set_visible (GTK_WIDGET (dialog->fxa_web_view), TRUE);
+    gtk_widget_set_size_request (GTK_WIDGET (dialog->fxa_web_view), 450, 450);
+    gtk_box_pack_start (GTK_BOX (dialog->sync_firefox_iframe_box),
+                      GTK_WIDGET (dialog->fxa_web_view),
+                      FALSE, FALSE, 0);
+
+    g_object_unref (sync_context);
+  }
 
-  gtk_widget_set_visible (dialog->sync_sign_in_details, FALSE);
-  gtk_container_add (GTK_CONTAINER (dialog->sync_sign_in_box),
-                     GTK_WIDGET (dialog->fxa_web_view));
+  webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+  gtk_widget_set_visible (dialog->sync_firefox_iframe_label, FALSE);
 }
 
 static void
 on_sync_sign_out_button_clicked (GtkWidget   *button,
                                  PrefsDialog *dialog)
 {
-  EphySyncService *service;
-
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-
-  /* Destroy session and delete tokens. */
-  ephy_sync_service_stop_periodical_sync (service);
-  ephy_sync_service_destroy_session (service, NULL);
-  ephy_sync_service_clear_storage_credentials (service);
-  ephy_sync_service_clear_tokens (service);
-  ephy_sync_secret_forget_tokens ();
-  ephy_sync_service_set_user_email (service, NULL);
-  ephy_sync_service_set_sync_time (service, 0);
-
-  g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER, "");
 
-  /* Show sign in box. */
-  if (dialog->fxa_web_view == NULL)
-    setup_fxa_sign_in_view (dialog);
-  else
-    webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+  ephy_sync_service_do_sign_out (dialog->sync_service);
+
+  /* Show Firefox Accounts iframe. */
+  sync_setup_firefox_iframe (dialog);
+  gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+                        dialog->sync_firefox_account_box);
+  gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+                        dialog->sync_options_box);
+  gtk_box_pack_start (GTK_BOX (dialog->sync_page_box),
+                      dialog->sync_firefox_iframe_box,
+                      FALSE, FALSE, 0);
+
+  g_settings_set_boolean (EPHY_SETTINGS_SYNC,
+                          EPHY_PREFS_SYNC_BOOKMARKS_INITIAL,
+                          TRUE);
+  dialog->sync_was_signed_in = FALSE;
+}
 
-  gtk_container_remove (GTK_CONTAINER (dialog->sync_authenticate_box),
-                        dialog->sync_sign_out_box);
-  gtk_box_pack_start (GTK_BOX (dialog->sync_authenticate_box),
-                      dialog->sync_sign_in_box,
-                      TRUE, TRUE, 0);
-  gtk_widget_set_visible (dialog->sync_sign_in_details, FALSE);
+static void
+on_sync_sync_now_button_clicked (GtkWidget   *button,
+                                 PrefsDialog *dialog)
+{
+  gtk_widget_set_sensitive (button, FALSE);
+  ephy_sync_service_do_sync (dialog->sync_service);
 }
 
 static void
@@ -625,17 +587,26 @@ prefs_dialog_class_init (PrefsDialogClass *klass)
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, enable_spell_checking_checkbutton);
 
   /* sync */
-  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_authenticate_box);
-  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_in_box);
-  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_in_details);
-  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_out_box);
-  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_out_details);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_page_box);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_firefox_iframe_box);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_firefox_iframe_label);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_firefox_account_box);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_firefox_account_label);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_out_button);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_options_box);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_with_firefox_checkbutton);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_bookmarks_checkbutton);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_5_min_radiobutton);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_15_min_radiobutton);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_30_min_radiobutton);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_60_min_radiobutton);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_now_button);
 
   gtk_widget_class_bind_template_callback (widget_class, on_manage_cookies_button_clicked);
   gtk_widget_class_bind_template_callback (widget_class, on_manage_passwords_button_clicked);
   gtk_widget_class_bind_template_callback (widget_class, on_search_engine_dialog_button_clicked);
   gtk_widget_class_bind_template_callback (widget_class, on_sync_sign_out_button_clicked);
+  gtk_widget_class_bind_template_callback (widget_class, on_sync_sync_now_button_clicked);
 }
 
 static void
@@ -1241,6 +1212,28 @@ clear_personal_data_button_clicked_cb (GtkWidget   *button,
 }
 
 static gboolean
+sync_frequency_get_mapping (GValue   *value,
+                            GVariant *variant,
+                            gpointer  user_data)
+{
+  if (GPOINTER_TO_UINT (user_data) == g_variant_get_uint32 (variant))
+    g_value_set_boolean (value, TRUE);
+
+  return TRUE;
+}
+
+static GVariant *
+sync_frequency_set_mapping (const GValue       *value,
+                            const GVariantType *expected_type,
+                            gpointer            user_data)
+{
+  if (!g_value_get_boolean (value))
+    return NULL;
+
+  return g_variant_new_uint32 (GPOINTER_TO_UINT (user_data));
+}
+
+static gboolean
 cookies_get_mapping (GValue   *value,
                      GVariant *variant,
                      gpointer  user_data)
@@ -1619,32 +1612,99 @@ setup_language_page (PrefsDialog *dialog)
 static void
 setup_sync_page (PrefsDialog *dialog)
 {
-  EphySyncService *service;
+  GSettings *sync_settings;
   char *account;
   char *text;
 
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+  sync_settings = ephy_settings_get (EPHY_PREFS_SYNC_SCHEMA);
+  dialog->sync_service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+  dialog->sync_was_signed_in = ephy_sync_service_is_signed_in (dialog->sync_service);
 
-  if (ephy_sync_service_is_signed_in (service) == FALSE) {
-    setup_fxa_sign_in_view (dialog);
-    gtk_container_remove (GTK_CONTAINER (dialog->sync_authenticate_box),
-                          dialog->sync_sign_out_box);
+  if (!dialog->sync_was_signed_in) {
+    sync_setup_firefox_iframe (dialog);
+    gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+                          dialog->sync_firefox_account_box);
+    gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+                          dialog->sync_options_box);
   } else {
-    gtk_container_remove (GTK_CONTAINER (dialog->sync_authenticate_box),
-                          dialog->sync_sign_in_box);
+    gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+                          dialog->sync_firefox_iframe_box);
 
-    account = g_strdup_printf ("<b>%s</b>", ephy_sync_service_get_user_email (service));
+    account = g_strdup_printf ("<b>%s</b>",
+                               ephy_sync_service_get_user_email (dialog->sync_service));
     /* Translators: the %s refers to the email of the currently logged in user. */
     text = g_strdup_printf (_("Currently logged in as %s"), account);
-    gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_out_details), text);
+    gtk_label_set_markup (GTK_LABEL (dialog->sync_firefox_account_label), text);
 
     g_free (text);
     g_free (account);
   }
 
-  g_signal_connect_object (service, "sync-tokens-store-finished",
-                           G_CALLBACK (sync_tokens_store_finished_cb),
+  g_signal_connect_object (dialog->sync_service, "sync-secrets-store-finished",
+                           G_CALLBACK (sync_secrets_store_finished_cb),
+                           dialog, 0);
+  g_signal_connect_object (dialog->sync_service, "sync-sign-in-error",
+                           G_CALLBACK (sync_sign_in_error_cb),
+                           dialog, 0);
+  g_signal_connect_object (dialog->sync_service, "sync-finished",
+                           G_CALLBACK (sync_finished_cb),
+                           dialog, 0);
+  g_signal_connect_object (dialog->sync_with_firefox_checkbutton, "toggled",
+                           G_CALLBACK (sync_with_firefox_toggled_cb),
                            dialog, 0);
+  g_signal_connect_object (dialog->sync_bookmarks_checkbutton, "toggled",
+                           G_CALLBACK (sync_bookmarks_toggled_cb),
+                           dialog, 0);
+
+  g_settings_bind (sync_settings,
+                   EPHY_PREFS_SYNC_WITH_FIREFOX,
+                   dialog->sync_with_firefox_checkbutton,
+                   "active",
+                   G_SETTINGS_BIND_DEFAULT);
+  g_settings_bind (sync_settings,
+                   EPHY_PREFS_SYNC_BOOKMARKS_ENABLED,
+                   dialog->sync_bookmarks_checkbutton,
+                   "active",
+                   G_SETTINGS_BIND_DEFAULT);
+  g_settings_bind_with_mapping (sync_settings,
+                                EPHY_PREFS_SYNC_FREQUENCY,
+                                dialog->sync_frequency_5_min_radiobutton,
+                                "active",
+                                G_SETTINGS_BIND_DEFAULT,
+                                sync_frequency_get_mapping,
+                                sync_frequency_set_mapping,
+                                GINT_TO_POINTER (5),
+                                NULL);
+  g_settings_bind_with_mapping (sync_settings,
+                                EPHY_PREFS_SYNC_FREQUENCY,
+                                dialog->sync_frequency_15_min_radiobutton,
+                                "active",
+                                G_SETTINGS_BIND_DEFAULT,
+                                sync_frequency_get_mapping,
+                                sync_frequency_set_mapping,
+                                GINT_TO_POINTER (15),
+                                NULL);
+  g_settings_bind_with_mapping (sync_settings,
+                                EPHY_PREFS_SYNC_FREQUENCY,
+                                dialog->sync_frequency_30_min_radiobutton,
+                                "active",
+                                G_SETTINGS_BIND_DEFAULT,
+                                sync_frequency_get_mapping,
+                                sync_frequency_set_mapping,
+                                GINT_TO_POINTER (30),
+                                NULL);
+  g_settings_bind_with_mapping (sync_settings,
+                                EPHY_PREFS_SYNC_FREQUENCY,
+                                dialog->sync_frequency_60_min_radiobutton,
+                                "active",
+                                G_SETTINGS_BIND_DEFAULT,
+                                sync_frequency_get_mapping,
+                                sync_frequency_set_mapping,
+                                GINT_TO_POINTER (60),
+                                NULL);
+
+  dialog->sync_frequency = g_settings_get_uint (EPHY_SETTINGS_SYNC,
+                                                EPHY_PREFS_SYNC_FREQUENCY);
 }
 
 static void
diff --git a/src/profile-migrator/Makefile.am b/src/profile-migrator/Makefile.am
index 8cf59bd..24a2709 100644
--- a/src/profile-migrator/Makefile.am
+++ b/src/profile-migrator/Makefile.am
@@ -13,6 +13,7 @@ ephy_profile_migrator_CPPFLAGS = \
        -I$(top_srcdir)/lib/history                     \
        -I$(top_srcdir)/src/                            \
        -I$(top_srcdir)/src/bookmarks                   \
+       -I$(top_srcdir)/src/sync                        \
        -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\"        \
        -DLOCALEDIR=\"$(localedir)\"                    \
        $(GLIB_CFLAGS)                                  \
diff --git a/src/profile-migrator/ephy-profile-migrator.c b/src/profile-migrator/ephy-profile-migrator.c
index 94a8eb4..9bdff27 100644
--- a/src/profile-migrator/ephy-profile-migrator.c
+++ b/src/profile-migrator/ephy-profile-migrator.c
@@ -41,6 +41,7 @@
 #include "ephy-search-engine-manager.h"
 #include "ephy-settings.h"
 #include "ephy-sqlite-connection.h"
+#include "ephy-sync-crypto.h"
 #include "ephy-uri-tester-shared.h"
 #include "ephy-web-app-utils.h"
 
@@ -641,11 +642,15 @@ parse_rdf_item (EphyBookmarksManager *manager,
 
   if (link) {
     EphyBookmark *bookmark;
+    char *id;
 
     g_sequence_sort (tags, (GCompareDataFunc)ephy_bookmark_tags_compare, NULL);
-    bookmark = ephy_bookmark_new ((const char *)link, (const char *)title, tags);
+    id = ephy_sync_crypto_get_random_sync_id ();
+    bookmark = ephy_bookmark_new ((const char *)link, (const char *)title, tags, id);
     ephy_bookmarks_manager_add_bookmark (manager, bookmark);
+
     g_object_unref (bookmark);
+    g_free (id);
   } else {
     g_sequence_free (tags);
   }
diff --git a/src/resources/gtk/prefs-dialog.ui b/src/resources/gtk/prefs-dialog.ui
index fabf0cb..09c5ac9 100644
--- a/src/resources/gtk/prefs-dialog.ui
+++ b/src/resources/gtk/prefs-dialog.ui
@@ -770,11 +770,11 @@
               </packing>
             </child>
             <child>
-              <object class="GtkBox">
+              <object class="GtkBox" id="sync_page_box">
                 <property name="visible">True</property>
                 <property name="border-width">12</property>
                 <property name="orientation">vertical</property>
-                <property name="spacing">8</property>
+                <property name="spacing">18</property>
                 <child>
                   <object class="GtkBox">
                     <property name="visible">True</property>
@@ -794,69 +794,188 @@
                       <object class="GtkBox">
                         <property name="visible">True</property>
                         <property name="orientation">vertical</property>
+                        <property name="spacing">6</property>
                         <property name="margin-start">12</property>
-                        <property name="spacing">8</property>
                         <child>
                           <object class="GtkLabel">
                             <property name="visible">True</property>
                             <property name="halign">start</property>
-                            <property name="use-markup">True</property>
                             <property name="max-width-chars">60</property>
                             <property name="wrap">True</property>
-                            <property name="label" translatable="yes">Sign in with your Firefox account to 
sync your data with Web on other computers. Web is not Firefox and cannot sync with Firefox. Web is not 
produced or endorsed by Mozilla.</property>
+                            <property name="label" translatable="yes">Sign in with your Firefox account to 
sync your data with Web and Firefox on other computers. Web is not produced or endorsed by Mozilla.</property>
                           </object>
                         </child>
                       </object>
                     </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox" id="sync_firefox_iframe_box">
+                    <property name="visible">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
+                    <property name="halign">center</property>
+                    <child>
+                      <object class="GtkLabel" id="sync_firefox_iframe_label">
+                        <property name="visible">False</property>
+                        <property name="halign">start</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox" id="sync_firefox_account_box">
+                    <property name="visible">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">Firefox Account</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                    </child>
                     <child>
-                      <object class="GtkBox" id="sync_authenticate_box">
+                      <object class="GtkBox">
                         <property name="visible">True</property>
                         <property name="orientation">vertical</property>
+                        <property name="spacing">6</property>
                         <property name="margin-start">12</property>
                         <child>
-                          <object class="GtkBox" id="sync_sign_in_box">
+                          <object class="GtkLabel" id="sync_firefox_account_label">
                             <property name="visible">True</property>
-                            <property name="orientation">vertical</property>
-                            <property name="halign">center</property>
-                            <property name="spacing">12</property>
-                            <child>
-                              <object class="GtkLabel" id="sync_sign_in_details">
-                                <property name="visible">False</property>
-                                <property name="halign">start</property>
-                              </object>
-                            </child>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes"/>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="sync_sign_out_button">
+                            <property name="label" translatable="yes">Sign _out</property>
+                            <property name="visible">True</property>
+                            <property name="use-underline">True</property>
+                            <property name="halign">start</property>
+                            <signal name="clicked" handler="on_sync_sign_out_button_clicked"/>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox" id="sync_options_box">
+                    <property name="visible">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">Sync Options</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">6</property>
+                        <property name="margin-start">12</property>
+                        <child>
+                          <object class="GtkCheckButton" id="sync_with_firefox_checkbutton">
+                            <property name="label" translatable="yes">Sync with _Firefox</property>
+                            <property name="visible">True</property>
+                            <property name="use-underline">True</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">Collections</property>
+                            <attributes>
+                              <attribute name="weight" value="bold"/>
+                            </attributes>
                           </object>
                         </child>
                         <child>
-                          <object class="GtkBox" id="sync_sign_out_box">
+                          <object class="GtkBox">
                             <property name="visible">True</property>
                             <property name="orientation">vertical</property>
-                            <property name="spacing">8</property>
+                            <property name="spacing">6</property>
+                            <property name="margin-start">12</property>
                             <child>
-                              <object class="GtkLabel" id="sync_sign_out_details">
+                              <object class="GtkCheckButton" id="sync_bookmarks_checkbutton">
+                                <property name="label" translatable="yes">_Bookmarks</property>
                                 <property name="visible">True</property>
-                                <property name="halign">start</property>
-                                <property name="label" translatable="yes"></property>
+                                <property name="use-underline">True</property>
                               </object>
                             </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">Frequency</property>
+                            <attributes>
+                              <attribute name="weight" value="bold"/>
+                            </attributes>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">6</property>
+                            <property name="margin-start">12</property>
                             <child>
-                              <object class="GtkLabel">
+                              <object class="GtkBox">
                                 <property name="visible">True</property>
-                                <property name="halign">start</property>
-                                <property name="label" translatable="yes">Sign out if you wish to stop 
syncing.</property>
+                                <child>
+                                  <object class="GtkRadioButton" id="sync_frequency_5_min_radiobutton">
+                                    <property name="label" translatable="yes">_5 min</property>
+                                    <property name="visible">True</property>
+                                    <property name="use-underline">True</property>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkRadioButton" id="sync_frequency_15_min_radiobutton">
+                                    <property name="label" translatable="yes">_15 min</property>
+                                    <property name="visible">True</property>
+                                    <property name="use-underline">True</property>
+                                    <property name="group">sync_frequency_5_min_radiobutton</property>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkRadioButton" id="sync_frequency_30_min_radiobutton">
+                                    <property name="label" translatable="yes">_30 min</property>
+                                    <property name="visible">True</property>
+                                    <property name="use-underline">True</property>
+                                    <property name="group">sync_frequency_5_min_radiobutton</property>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkRadioButton" id="sync_frequency_60_min_radiobutton">
+                                    <property name="label" translatable="yes">_60 min</property>
+                                    <property name="visible">True</property>
+                                    <property name="use-underline">True</property>
+                                    <property name="group">sync_frequency_5_min_radiobutton</property>
+                                  </object>
+                                </child>
                               </object>
                             </child>
                             <child>
-                              <object class="GtkButton" id="sync_sign_out_button">
-                                <property name="label" translatable="yes">Sign _out</property>
+                              <object class="GtkButton" id="sync_now_button">
+                                <property name="label" translatable="yes">Sync _now</property>
                                 <property name="visible">True</property>
                                 <property name="use-underline">True</property>
                                 <property name="halign">start</property>
-                                <property name="width-request">100</property>
-                                <style>
-                                  <class name="destructive-action"/>
-                                  <class name="text-button"/>
-                                </style>
+                                <signal name="clicked" handler="on_sync_sync_now_button_clicked"/>
                               </object>
                             </child>
                           </object>
diff --git a/src/sync/ephy-sync-crypto.c b/src/sync/ephy-sync-crypto.c
index 0e109f7..24d818c 100644
--- a/src/sync/ephy-sync-crypto.c
+++ b/src/sync/ephy-sync-crypto.c
@@ -21,20 +21,21 @@
 #include "config.h"
 #include "ephy-sync-crypto.h"
 
-#include "ephy-sync-utils.h"
-
 #include <glib/gstdio.h>
 #include <inttypes.h>
 #include <libsoup/soup.h>
+#include <nettle/cbc.h>
 #include <nettle/aes.h>
 #include <string.h>
 
 #define HAWK_VERSION  1
 #define NONCE_LEN     6
+#define IV_LEN        16
+#define SYNC_ID_LEN   12
 
 static const char hex_digits[] = "0123456789abcdef";
 
-EphySyncCryptoHawkOptions *
+SyncCryptoHawkOptions *
 ephy_sync_crypto_hawk_options_new (const char *app,
                                    const char *dlg,
                                    const char *ext,
@@ -45,9 +46,9 @@ ephy_sync_crypto_hawk_options_new (const char *app,
                                    const char *payload,
                                    const char *timestamp)
 {
-  EphySyncCryptoHawkOptions *options;
+  SyncCryptoHawkOptions *options;
 
-  options = g_slice_new (EphySyncCryptoHawkOptions);
+  options = g_slice_new (SyncCryptoHawkOptions);
   options->app = g_strdup (app);
   options->dlg = g_strdup (dlg);
   options->ext = g_strdup (ext);
@@ -62,9 +63,9 @@ ephy_sync_crypto_hawk_options_new (const char *app,
 }
 
 void
-ephy_sync_crypto_hawk_options_free (EphySyncCryptoHawkOptions *options)
+ephy_sync_crypto_hawk_options_free (SyncCryptoHawkOptions *options)
 {
-  g_return_if_fail (options != NULL);
+  g_return_if_fail (options);
 
   g_free (options->app);
   g_free (options->dlg);
@@ -76,10 +77,10 @@ ephy_sync_crypto_hawk_options_free (EphySyncCryptoHawkOptions *options)
   g_free (options->payload);
   g_free (options->timestamp);
 
-  g_slice_free (EphySyncCryptoHawkOptions, options);
+  g_slice_free (SyncCryptoHawkOptions, options);
 }
 
-static EphySyncCryptoHawkArtifacts *
+static SyncCryptoHawkArtifacts *
 ephy_sync_crypto_hawk_artifacts_new (const char *app,
                                      const char *dlg,
                                      const char *ext,
@@ -91,9 +92,9 @@ ephy_sync_crypto_hawk_artifacts_new (const char *app,
                                      const char *resource,
                                      gint64      ts)
 {
-  EphySyncCryptoHawkArtifacts *artifacts;
+  SyncCryptoHawkArtifacts *artifacts;
 
-  artifacts = g_slice_new (EphySyncCryptoHawkArtifacts);
+  artifacts = g_slice_new (SyncCryptoHawkArtifacts);
   artifacts->app = g_strdup (app);
   artifacts->dlg = g_strdup (dlg);
   artifacts->ext = g_strdup (ext);
@@ -109,9 +110,9 @@ ephy_sync_crypto_hawk_artifacts_new (const char *app,
 }
 
 static void
-ephy_sync_crypto_hawk_artifacts_free (EphySyncCryptoHawkArtifacts *artifacts)
+ephy_sync_crypto_hawk_artifacts_free (SyncCryptoHawkArtifacts *artifacts)
 {
-  g_assert (artifacts != NULL);
+  g_assert (artifacts);
 
   g_free (artifacts->app);
   g_free (artifacts->dlg);
@@ -124,40 +125,40 @@ ephy_sync_crypto_hawk_artifacts_free (EphySyncCryptoHawkArtifacts *artifacts)
   g_free (artifacts->resource);
   g_free (artifacts->ts);
 
-  g_slice_free (EphySyncCryptoHawkArtifacts, artifacts);
+  g_slice_free (SyncCryptoHawkArtifacts, artifacts);
 }
 
-static EphySyncCryptoHawkHeader *
-ephy_sync_crypto_hawk_header_new (char                        *header,
-                                  EphySyncCryptoHawkArtifacts *artifacts)
+static SyncCryptoHawkHeader *
+ephy_sync_crypto_hawk_header_new (const char              *header,
+                                  SyncCryptoHawkArtifacts *artifacts)
 {
-  EphySyncCryptoHawkHeader *hheader;
+  SyncCryptoHawkHeader *hheader;
 
-  hheader = g_slice_new (EphySyncCryptoHawkHeader);
-  hheader->header = header;
+  hheader = g_slice_new (SyncCryptoHawkHeader);
+  hheader->header = g_strdup (header);
   hheader->artifacts = artifacts;
 
   return hheader;
 }
 
 void
-ephy_sync_crypto_hawk_header_free (EphySyncCryptoHawkHeader *hheader)
+ephy_sync_crypto_hawk_header_free (SyncCryptoHawkHeader *hheader)
 {
-  g_return_if_fail (hheader != NULL);
+  g_return_if_fail (hheader);
 
   g_free (hheader->header);
   ephy_sync_crypto_hawk_artifacts_free (hheader->artifacts);
 
-  g_slice_free (EphySyncCryptoHawkHeader, hheader);
+  g_slice_free (SyncCryptoHawkHeader, hheader);
 }
 
-static EphySyncCryptoRSAKeyPair *
+static SyncCryptoRSAKeyPair *
 ephy_sync_crypto_rsa_key_pair_new (struct rsa_public_key  public,
                                    struct rsa_private_key private)
 {
-  EphySyncCryptoRSAKeyPair *keypair;
+  SyncCryptoRSAKeyPair *keypair;
 
-  keypair = g_slice_new (EphySyncCryptoRSAKeyPair);
+  keypair = g_slice_new (SyncCryptoRSAKeyPair);
   keypair->public = public;
   keypair->private = private;
 
@@ -165,20 +166,71 @@ ephy_sync_crypto_rsa_key_pair_new (struct rsa_public_key  public,
 }
 
 void
-ephy_sync_crypto_rsa_key_pair_free (EphySyncCryptoRSAKeyPair *keypair)
+ephy_sync_crypto_rsa_key_pair_free (SyncCryptoRSAKeyPair *keypair)
 {
-  g_return_if_fail (keypair != NULL);
+  g_return_if_fail (keypair);
 
   rsa_public_key_clear (&keypair->public);
   rsa_private_key_clear (&keypair->private);
 
-  g_slice_free (EphySyncCryptoRSAKeyPair, keypair);
+  g_slice_free (SyncCryptoRSAKeyPair, keypair);
+}
+
+static SyncCryptoKeyBundle *
+ephy_sync_crypto_key_bundle_new (const char *aes_key_hex,
+                                 const char *hmac_key_hex)
+{
+  SyncCryptoKeyBundle *bundle;
+
+  bundle = g_slice_new (SyncCryptoKeyBundle);
+  bundle->aes_key_hex = g_strdup (aes_key_hex);
+  bundle->hmac_key_hex = g_strdup (hmac_key_hex);
+
+  return bundle;
+}
+
+SyncCryptoKeyBundle *
+ephy_sync_crypto_key_bundle_from_array (JsonArray *array)
+{
+  SyncCryptoKeyBundle *bundle;
+  char *aes_key_hex;
+  char *hmac_key_hex;
+  guint8 *aes_key;
+  guint8 *hmac_key;
+  gsize len;
+
+  g_return_val_if_fail (array, NULL);
+  g_return_val_if_fail (json_array_get_length (array) == 2, NULL);
+
+  aes_key = g_base64_decode (json_array_get_string_element (array, 0), &len);
+  hmac_key = g_base64_decode (json_array_get_string_element (array, 1), &len);
+  aes_key_hex = ephy_sync_crypto_encode_hex (aes_key, 32);
+  hmac_key_hex = ephy_sync_crypto_encode_hex (hmac_key, 32);
+  bundle = ephy_sync_crypto_key_bundle_new (aes_key_hex, hmac_key_hex);
+
+  g_free (aes_key);
+  g_free (hmac_key);
+  g_free (aes_key_hex);
+  g_free (hmac_key_hex);
+
+  return bundle;
+}
+
+void
+ephy_sync_crypto_key_bundle_free (SyncCryptoKeyBundle *bundle)
+{
+  g_return_if_fail (bundle);
+
+  g_free (bundle->aes_key_hex);
+  g_free (bundle->hmac_key_hex);
+
+  g_slice_free (SyncCryptoKeyBundle, bundle);
 }
 
 static char *
 ephy_sync_crypto_kw (const char *name)
 {
-  g_assert (name != NULL);
+  g_assert (name);
 
   /* Concatenate the given name to the Mozilla prefix.
    * See https://raw.githubusercontent.com/wiki/mozilla/fxa-auth-server/images/onepw-create.png
@@ -187,14 +239,14 @@ ephy_sync_crypto_kw (const char *name)
 }
 
 static guint8 *
-ephy_sync_crypto_xor (guint8 *a,
-                      guint8 *b,
-                      gsize   length)
+ephy_sync_crypto_xor (const guint8 *a,
+                      const guint8 *b,
+                      gsize         length)
 {
   guint8 *xored;
 
-  g_assert (a != NULL);
-  g_assert (b != NULL);
+  g_assert (a);
+  g_assert (b);
 
   xored = g_malloc (length);
   for (gsize i = 0; i < length; i++)
@@ -204,12 +256,12 @@ ephy_sync_crypto_xor (guint8 *a,
 }
 
 static gboolean
-ephy_sync_crypto_equals (guint8 *a,
-                         guint8 *b,
-                         gsize   length)
+ephy_sync_crypto_equals (const guint8 *a,
+                         const guint8 *b,
+                         gsize         length)
 {
-  g_assert (a != NULL);
-  g_assert (b != NULL);
+  g_assert (a);
+  g_assert (b);
 
   for (gsize i = 0; i < length; i++)
     if (a[i] != b[i])
@@ -219,8 +271,45 @@ ephy_sync_crypto_equals (guint8 *a,
 }
 
 static char *
-ephy_sync_crypto_normalize_string (const char                  *type,
-                                   EphySyncCryptoHawkArtifacts *artifacts)
+ephy_sync_crypto_find_and_replace (const char *where,
+                                   const char *to_find,
+                                   const char *to_repl)
+{
+  const char *haystack = where;
+  const char *needle = NULL;
+  char *out;
+  gsize haystack_len;
+  gsize to_find_len;
+  gsize to_repl_len;
+  gsize new_len = 0;
+  gsize skip_len = 0;
+
+  g_assert (where);
+  g_assert (to_find);
+  g_assert (to_repl);
+
+  haystack_len = strlen (where);
+  to_find_len = strlen (to_find);
+  to_repl_len = strlen (to_repl);
+  out = g_malloc (haystack_len + 1);
+
+  while ((needle = g_strstr_len (haystack, -1, to_find)) != NULL) {
+    haystack_len += to_find_len - to_repl_len;
+    out = g_realloc (out, haystack_len + 1);
+    skip_len = needle - haystack;
+    memcpy (out + new_len, haystack, skip_len);
+    memcpy (out + new_len + skip_len, to_repl, to_repl_len);
+    new_len += skip_len + to_repl_len;
+    haystack = needle + to_find_len;
+  }
+  strcpy (out + new_len, haystack);
+
+  return out;
+}
+
+static char *
+ephy_sync_crypto_normalize_string (const char              *type,
+                                   SyncCryptoHawkArtifacts *artifacts)
 {
   char *host;
   char *info;
@@ -229,8 +318,8 @@ ephy_sync_crypto_normalize_string (const char                  *type,
   char *normalized;
   char *tmp;
 
-  g_assert (type != NULL);
-  g_assert (artifacts != NULL);
+  g_assert (type);
+  g_assert (artifacts);
 
   info = g_strdup_printf ("hawk.%d.%s", HAWK_VERSION, type);
   method = g_ascii_strup (artifacts->method, -1);
@@ -243,8 +332,8 @@ ephy_sync_crypto_normalize_string (const char                  *type,
                           NULL);
 
   if (artifacts->ext && strlen (artifacts->ext) > 0) {
-    tmp = ephy_sync_utils_find_and_replace (artifacts->ext, "\\", "\\\\");
-    n_ext = ephy_sync_utils_find_and_replace (tmp, "\n", "\\n");
+    tmp = ephy_sync_crypto_find_and_replace (artifacts->ext, "\\", "\\\\");
+    n_ext = ephy_sync_crypto_find_and_replace (tmp, "\n", "\\n");
     g_free (tmp);
   }
 
@@ -272,7 +361,7 @@ ephy_sync_crypto_parse_content_type (const char *content_type)
   char **tokens;
   char *retval;
 
-  g_assert (content_type != NULL);
+  g_assert (content_type);
 
   tokens = g_strsplit (content_type, ";", -1);
   retval = g_ascii_strdown (g_strstrip (tokens[0]), -1);
@@ -291,8 +380,8 @@ ephy_sync_crypto_calculate_payload_hash (const char *payload,
   char *update;
   char *hash;
 
-  g_assert (payload != NULL);
-  g_assert (content_type != NULL);
+  g_assert (payload);
+  g_assert (content_type);
 
   content = ephy_sync_crypto_parse_content_type (content_type);
   update = g_strdup_printf ("hawk.%d.payload\n%s\n%s\n",
@@ -311,19 +400,19 @@ ephy_sync_crypto_calculate_payload_hash (const char *payload,
 }
 
 static char *
-ephy_sync_crypto_calculate_mac (const char                  *type,
-                                guint8                      *key,
-                                gsize                        key_len,
-                                EphySyncCryptoHawkArtifacts *artifacts)
+ephy_sync_crypto_calculate_mac (const char              *type,
+                                const guint8            *key,
+                                gsize                    key_len,
+                                SyncCryptoHawkArtifacts *artifacts)
 {
   guint8 *digest;
   char *digest_hex;
   char *normalized;
   char *mac;
 
-  g_assert (type != NULL);
-  g_assert (key != NULL);
-  g_assert (artifacts != NULL);
+  g_assert (type);
+  g_assert (key);
+  g_assert (artifacts);
 
   /* Serialize the mac type and artifacts into a HAWK string. */
   normalized = ephy_sync_crypto_normalize_string (type, artifacts);
@@ -341,14 +430,14 @@ ephy_sync_crypto_calculate_mac (const char                  *type,
 static char *
 ephy_sync_crypto_append_to_header (char       *header,
                                    const char *name,
-                                   char       *value)
+                                   const char *value)
 {
   char *new_header;
   char *tmp;
 
-  g_assert (header != NULL);
-  g_assert (name != NULL);
-  g_assert (value != NULL);
+  g_assert (header);
+  g_assert (name);
+  g_assert (value);
 
   tmp = header;
   new_header = g_strconcat (header, ", ", name, "=\"", value, "\"", NULL);
@@ -357,15 +446,43 @@ ephy_sync_crypto_append_to_header (char       *header,
   return new_header;
 }
 
+static guint8 *
+ephy_sync_crypto_concat_bytes (const guint8 *bytes,
+                               gsize         bytes_len,
+                               ...)
+{
+  va_list args;
+  guint8 *next;
+  guint8 *out;
+  gsize next_len;
+  gsize out_len;
+
+  out_len = bytes_len;
+  out = g_malloc (out_len);
+  memcpy (out, bytes, out_len);
+
+  va_start (args, bytes_len);
+  while ((next = va_arg (args, guint8 *)) != NULL) {
+    next_len = va_arg (args, gsize);
+    out = g_realloc (out, out_len + next_len);
+    memcpy (out + out_len, next, next_len);
+    out_len += next_len;
+  }
+
+  va_end (args);
+
+  return out;
+}
+
 static void
-ephy_sync_crypto_hkdf (guint8 *in,
-                       gsize   in_len,
-                       guint8 *salt,
-                       gsize   salt_len,
-                       guint8 *info,
-                       gsize   info_len,
-                       guint8 *out,
-                       gsize   out_len)
+ephy_sync_crypto_hkdf (const guint8 *in,
+                       gsize         in_len,
+                       guint8       *salt,
+                       gsize         salt_len,
+                       const guint8 *info,
+                       gsize         info_len,
+                       guint8       *out,
+                       gsize         out_len)
 {
   char *prk_hex;
   char *tmp_hex;
@@ -378,9 +495,9 @@ ephy_sync_crypto_hkdf (guint8 *in,
   gsize data_len;
   gsize n;
 
-  g_assert (in != NULL);
-  g_assert (info != NULL);
-  g_assert (out != NULL);
+  g_assert (in);
+  g_assert (info);
+  g_assert (out);
 
   hash_len = g_checksum_type_get_length (G_CHECKSUM_SHA256);
   g_assert (out_len <= hash_len * 255);
@@ -389,7 +506,7 @@ ephy_sync_crypto_hkdf (guint8 *in,
    * See https://tools.ietf.org/html/rfc5869 */
 
   /* If salt value was not provided, use an array of hash_len zeros. */
-  if (salt == NULL) {
+  if (!salt) {
     salt = g_malloc0 (hash_len);
     salt_len = hash_len;
   }
@@ -405,12 +522,12 @@ ephy_sync_crypto_hkdf (guint8 *in,
 
   for (gsize i = 0; i < n; i++, counter++) {
     if (i == 0) {
-      data = ephy_sync_utils_concatenate_bytes (info, info_len, &counter, 1, NULL);
+      data = ephy_sync_crypto_concat_bytes (info, info_len, &counter, 1, NULL);
       data_len = info_len + 1;
     } else {
-      data = ephy_sync_utils_concatenate_bytes (out_full + (i - 1) * hash_len, hash_len,
-                                                info, info_len, &counter, 1,
-                                                NULL);
+      data = ephy_sync_crypto_concat_bytes (out_full + (i - 1) * hash_len, hash_len,
+                                            info, info_len, &counter, 1,
+                                            NULL);
       data_len = hash_len + info_len + 1;
     }
 
@@ -434,7 +551,7 @@ ephy_sync_crypto_hkdf (guint8 *in,
 static void
 ephy_sync_crypto_b64_to_b64_urlsafe (char *text)
 {
-  g_assert (text != NULL);
+  g_assert (text);
 
   /* Replace '+' with '-' and '/' with '_' */
   g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=/", '-');
@@ -444,19 +561,164 @@ ephy_sync_crypto_b64_to_b64_urlsafe (char *text)
 static void
 ephy_sync_crypto_b64_urlsafe_to_b64 (char *text)
 {
-  g_assert (text != NULL);
+  g_assert (text);
 
   /* Replace '-' with '+' and '_' with '/' */
   g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=_", '+');
   g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=+", '/');
 }
 
+static guint8 *
+ephy_sync_crypto_pad (const char *text,
+                      gsize       block_len,
+                      gsize      *out_len)
+{
+  guint8 *out;
+  gsize text_len = strlen (text);
+
+  g_assert (text);
+  g_assert (out_len);
+
+  if (text_len % block_len == 0)
+    *out_len = text_len;
+  else
+    *out_len = text_len + block_len - text_len % block_len;
+
+  out = g_malloc (*out_len);
+
+  if (text_len % block_len != 0)
+    memset (out, block_len - text_len % block_len, *out_len);
+
+  memcpy (out, text, text_len);
+
+  return out;
+}
+
+static guint8 *
+ephy_sync_crypto_aes_256_encrypt (const char   *text,
+                                  const guint8 *key,
+                                  const guint8 *iv,
+                                  gsize        *out_len)
+{
+  guint8 *padded;
+  guint8 *encrypted;
+  gsize padded_len;
+  struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
+
+  g_assert (text);
+  g_assert (key);
+  g_assert (iv);
+  g_assert (out_len);
+
+  padded = ephy_sync_crypto_pad (text, AES_BLOCK_SIZE, &padded_len);
+  encrypted = g_malloc (padded_len);
+
+  aes256_set_encrypt_key(&ctx.ctx, key);
+  CBC_SET_IV(&ctx, iv);
+  CBC_ENCRYPT(&ctx, aes256_encrypt, padded_len, encrypted, padded);
+
+  *out_len = padded_len;
+  g_free (padded);
+
+  return encrypted;
+}
+
+static char *
+ephy_sync_crypto_unpad (const guint8 *data,
+                        gsize         data_len,
+                        gsize         block_len)
+{
+  char *out;
+  gsize out_len;
+  gsize padding = data[data_len - 1];
+
+  g_assert (data);
+
+  if (padding >= 1 && padding <= block_len)
+    out_len = data_len - padding;
+  else
+    out_len = data_len;
+
+  out = g_malloc0 (out_len + 1);
+  memcpy (out, data, out_len);
+
+  return out;
+}
+
+static char *
+ephy_sync_crypto_aes_256_decrypt (const guint8 *data,
+                                  gsize         data_len,
+                                  const guint8 *key,
+                                  const guint8 *iv)
+{
+  guint8 *decrypted;
+  char *unpadded;
+  struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
+
+  g_assert (data);
+  g_assert (key);
+  g_assert (iv);
+
+  decrypted = g_malloc (data_len);
+
+  aes256_set_decrypt_key (&ctx.ctx, key);
+  CBC_SET_IV (&ctx, iv);
+  CBC_DECRYPT (&ctx, aes256_decrypt, data_len, decrypted, data);
+
+  unpadded = ephy_sync_crypto_unpad (decrypted, data_len, AES_BLOCK_SIZE);
+  g_free (decrypted);
+
+  return unpadded;
+}
+
+static gboolean
+ephy_sync_crypto_hmac_is_valid (const char   *text,
+                                const guint8 *key,
+                                const char   *expected)
+{
+  char *hmac;
+  gboolean retval;
+
+  g_assert (text);
+  g_assert (key);
+  g_assert (expected);
+
+  /* SHA256 expects a 32 bytes key. */
+  hmac = g_compute_hmac_for_string (G_CHECKSUM_SHA256, key, 32, text, -1);
+  retval = g_strcmp0 (hmac, expected) == 0;
+  g_free (hmac);
+
+  return retval;
+}
+
+/*
+ * This function is required by Nettle's RSA support.
+ * From Nettle's documentation: random_ctx and random is a randomness generator.
+ * random(random_ctx, length, dst) should generate length random octets and store them at dst.
+ * We don't really use random_ctx, since we have /dev/urandom available.
+ */
+static void
+ephy_sync_crypto_random_bytes_gen (void   *random_ctx,
+                                   gsize   length,
+                                   guint8 *dst)
+{
+  FILE *fp;
+
+  g_assert (length > 0);
+  g_assert (dst);
+
+  fp = fopen ("/dev/urandom", "r");
+  fread (dst, sizeof (guint8), length, fp);
+  fclose (fp);
+}
+
 void
 ephy_sync_crypto_process_key_fetch_token (const char  *keyFetchToken,
                                           guint8     **tokenID,
                                           guint8     **reqHMACkey,
                                           guint8     **respHMACkey,
-                                          guint8     **respXORkey)
+                                          guint8     **respXORkey,
+                                          gsize        token_len)
 {
   guint8 *kft;
   guint8 *out1;
@@ -465,41 +727,41 @@ ephy_sync_crypto_process_key_fetch_token (const char  *keyFetchToken,
   char *info_kft;
   char *info_keys;
 
-  g_return_if_fail (keyFetchToken != NULL);
-  g_return_if_fail (tokenID != NULL);
-  g_return_if_fail (reqHMACkey != NULL);
-  g_return_if_fail (respHMACkey != NULL);
-  g_return_if_fail (respXORkey != NULL);
+  g_return_if_fail (keyFetchToken);
+  g_return_if_fail (tokenID);
+  g_return_if_fail (reqHMACkey);
+  g_return_if_fail (respHMACkey);
+  g_return_if_fail (respXORkey);
 
   kft = ephy_sync_crypto_decode_hex (keyFetchToken);
   info_kft = ephy_sync_crypto_kw ("keyFetchToken");
   info_keys = ephy_sync_crypto_kw ("account/keys");
-  out1 = g_malloc (3 * EPHY_SYNC_TOKEN_LENGTH);
-  out2 = g_malloc (3 * EPHY_SYNC_TOKEN_LENGTH);
+  out1 = g_malloc (3 * token_len);
+  out2 = g_malloc (3 * token_len);
 
   /* Use the keyFetchToken to derive tokenID, reqHMACkey and keyRequestKey. */
-  ephy_sync_crypto_hkdf (kft, EPHY_SYNC_TOKEN_LENGTH,
+  ephy_sync_crypto_hkdf (kft, token_len,
                          NULL, 0,
                          (guint8 *)info_kft, strlen (info_kft),
-                         out1, 3 * EPHY_SYNC_TOKEN_LENGTH);
+                         out1, 3 * token_len);
 
-  *tokenID = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  *reqHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  keyRequestKey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (*tokenID, out1, EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (*reqHMACkey, out1 + EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (keyRequestKey, out1 + 2 * EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
+  *tokenID = g_malloc (token_len);
+  *reqHMACkey = g_malloc (token_len);
+  keyRequestKey = g_malloc (token_len);
+  memcpy (*tokenID, out1, token_len);
+  memcpy (*reqHMACkey, out1 + token_len, token_len);
+  memcpy (keyRequestKey, out1 + 2 * token_len, token_len);
 
   /* Use the keyRequestKey to derive respHMACkey and respXORkey. */
-  ephy_sync_crypto_hkdf (keyRequestKey, EPHY_SYNC_TOKEN_LENGTH,
+  ephy_sync_crypto_hkdf (keyRequestKey, token_len,
                          NULL, 0,
                          (guint8 *)info_keys, strlen (info_keys),
-                         out2, 3 * EPHY_SYNC_TOKEN_LENGTH);
+                         out2, 3 * token_len);
 
-  *respHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  *respXORkey = g_malloc (2 * EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (*respHMACkey, out2, EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (*respXORkey, out2 + EPHY_SYNC_TOKEN_LENGTH, 2 * EPHY_SYNC_TOKEN_LENGTH);
+  *respHMACkey = g_malloc (token_len);
+  *respXORkey = g_malloc (2 * token_len);
+  memcpy (*respHMACkey, out2, token_len);
+  memcpy (*respXORkey, out2 + token_len, 2 * token_len);
 
   g_free (kft);
   g_free (out1);
@@ -513,104 +775,331 @@ void
 ephy_sync_crypto_process_session_token (const char  *sessionToken,
                                         guint8     **tokenID,
                                         guint8     **reqHMACkey,
-                                        guint8     **requestKey)
+                                        guint8     **requestKey,
+                                        gsize        token_len)
 {
   guint8 *st;
   guint8 *out;
   char *info;
 
-  g_return_if_fail (sessionToken != NULL);
-  g_return_if_fail (tokenID != NULL);
-  g_return_if_fail (reqHMACkey != NULL);
-  g_return_if_fail (requestKey != NULL);
+  g_return_if_fail (sessionToken);
+  g_return_if_fail (tokenID);
+  g_return_if_fail (reqHMACkey);
+  g_return_if_fail (requestKey);
 
   st = ephy_sync_crypto_decode_hex (sessionToken);
   info = ephy_sync_crypto_kw ("sessionToken");
-  out = g_malloc (3 * EPHY_SYNC_TOKEN_LENGTH);
+  out = g_malloc (3 * token_len);
 
   /* Use the sessionToken to derive tokenID, reqHMACkey and requestKey. */
-  ephy_sync_crypto_hkdf (st, EPHY_SYNC_TOKEN_LENGTH,
+  ephy_sync_crypto_hkdf (st, token_len,
                          NULL, 0,
                          (guint8 *)info, strlen (info),
-                         out, 3 * EPHY_SYNC_TOKEN_LENGTH);
+                         out, 3 * token_len);
 
-  *tokenID = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  *reqHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  *requestKey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (*tokenID, out, EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (*reqHMACkey, out + EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (*requestKey, out + 2 * EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
+  *tokenID = g_malloc (token_len);
+  *reqHMACkey = g_malloc (token_len);
+  *requestKey = g_malloc (token_len);
+  memcpy (*tokenID, out, token_len);
+  memcpy (*reqHMACkey, out + token_len, token_len);
+  memcpy (*requestKey, out + 2 * token_len, token_len);
 
   g_free (st);
   g_free (out);
   g_free (info);
 }
 
-void
-ephy_sync_crypto_compute_sync_keys (const char  *bundle,
-                                    guint8      *respHMACkey,
-                                    guint8      *respXORkey,
-                                    guint8      *unwrapBKey,
-                                    guint8     **kA,
-                                    guint8     **kB)
-{
-  guint8 *bdl;
+gboolean
+ephy_sync_crypto_compute_sync_keys (const char    *bundle_hex,
+                                    const guint8  *respHMACkey,
+                                    const guint8  *respXORkey,
+                                    const guint8  *unwrapBKey,
+                                    guint8       **kA,
+                                    guint8       **kB,
+                                    gsize          key_len)
+{
+  guint8 *bundle;
   guint8 *ciphertext;
   guint8 *respMAC;
   guint8 *respMAC2;
   guint8 *xored;
   guint8 *wrapKB;
   char *respMAC2_hex;
+  gboolean retval = TRUE;
 
-  g_return_if_fail (bundle != NULL);
-  g_return_if_fail (respHMACkey != NULL);
-  g_return_if_fail (respXORkey != NULL);
-  g_return_if_fail (unwrapBKey != NULL);
-  g_return_if_fail (kA != NULL);
-  g_return_if_fail (kB != NULL);
+  g_return_val_if_fail (bundle_hex, FALSE);
+  g_return_val_if_fail (respHMACkey, FALSE);
+  g_return_val_if_fail (respXORkey, FALSE);
+  g_return_val_if_fail (unwrapBKey, FALSE);
+  g_return_val_if_fail (kA, FALSE);
+  g_return_val_if_fail (kB, FALSE);
 
-  bdl = ephy_sync_crypto_decode_hex (bundle);
-  ciphertext = g_malloc (2 * EPHY_SYNC_TOKEN_LENGTH);
-  respMAC = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  wrapKB = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
-  *kA = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
+  bundle = ephy_sync_crypto_decode_hex (bundle_hex);
+  ciphertext = g_malloc (2 * key_len);
+  respMAC = g_malloc (key_len);
 
   /* Compute the MAC and compare it to the expected value. */
-  memcpy (ciphertext, bdl, 2 * EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (respMAC, bdl + 2 * EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
+  memcpy (ciphertext, bundle, 2 * key_len);
+  memcpy (respMAC, bundle + 2 * key_len, key_len);
   respMAC2_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
-                                          respHMACkey, EPHY_SYNC_TOKEN_LENGTH,
-                                          ciphertext, 2 * EPHY_SYNC_TOKEN_LENGTH);
+                                          respHMACkey, key_len,
+                                          ciphertext, 2 * key_len);
   respMAC2 = ephy_sync_crypto_decode_hex (respMAC2_hex);
-  g_assert (ephy_sync_crypto_equals (respMAC, respMAC2, EPHY_SYNC_TOKEN_LENGTH) == TRUE);
+  if (!ephy_sync_crypto_equals (respMAC, respMAC2, key_len)) {
+    g_warning ("HMAC values differs from the one expected");
+    retval = FALSE;
+    goto out;
+  }
 
   /* XOR the extracted ciphertext with the respXORkey, then split in into the
    * separate kA and wrap(kB) values. */
-  xored = ephy_sync_crypto_xor (ciphertext, respXORkey, 2 * EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (*kA, xored, EPHY_SYNC_TOKEN_LENGTH);
-  memcpy (wrapKB, xored + EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
-
+  xored = ephy_sync_crypto_xor (ciphertext, respXORkey, 2 * key_len);
+  *kA = g_malloc (key_len);
+  memcpy (*kA, xored, key_len);
+  wrapKB = g_malloc (key_len);
+  memcpy (wrapKB, xored + key_len, key_len);
   /* Finally, XOR wrap(kB) with unwrapBKey to obtain kB. There is no MAC on wrap(kB). */
-  *kB = ephy_sync_crypto_xor (unwrapBKey, wrapKB, EPHY_SYNC_TOKEN_LENGTH);
+  *kB = ephy_sync_crypto_xor (unwrapBKey, wrapKB, key_len);
 
-  g_free (bdl);
-  g_free (ciphertext);
-  g_free (respMAC);
-  g_free (respMAC2);
-  g_free (xored);
   g_free (wrapKB);
+  g_free (xored);
+out:
+  g_free (respMAC2);
   g_free (respMAC2_hex);
+  g_free (respMAC);
+  g_free (ciphertext);
+  g_free (bundle);
+
+  return retval;
+}
+
+SyncCryptoKeyBundle *
+ephy_sync_crypto_derive_key_bundle (const guint8 *key,
+                                    gsize         key_len)
+{
+  SyncCryptoKeyBundle *bundle;
+  guint8 *salt;
+  guint8 *prk;
+  guint8 *tmp;
+  guint8 *aes_key;
+  char *prk_hex;
+  char *aes_key_hex;
+  char *hmac_key_hex;
+  const char *info = "identity.mozilla.com/picl/v1/oldsync";
+
+  g_return_val_if_fail (key, NULL);
+  g_return_val_if_fail (key_len > 0, NULL);
+
+  /* Perform a two step HKDF with an all-zeros salt.
+   * T(1) will represent the AES key, T(2) will represent the HMAC key. */
+
+  salt = g_malloc0 (key_len);
+  prk_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+                                     salt, key_len,
+                                     key, key_len);
+  prk = ephy_sync_crypto_decode_hex (prk_hex);
+  tmp = ephy_sync_crypto_concat_bytes ((guint8 *)info, strlen (info),
+                                       "\x01", 1,
+                                       NULL);
+  aes_key_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+                                         prk, key_len,
+                                         tmp, strlen (info) + 1);
+  aes_key = ephy_sync_crypto_decode_hex (aes_key_hex);
+  g_free (tmp);
+  tmp = ephy_sync_crypto_concat_bytes (aes_key, key_len,
+                                       (guint8 *)info, strlen (info),
+                                       "\x02", 1,
+                                       NULL);
+  hmac_key_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+                                          prk, key_len,
+                                          tmp, key_len + strlen (info) + 1);
+  bundle = ephy_sync_crypto_key_bundle_new (aes_key_hex, hmac_key_hex);
+
+  g_free (hmac_key_hex);
+  g_free (tmp);
+  g_free (aes_key_hex);
+  g_free (prk);
+  g_free (prk_hex);
+  g_free (salt);
+
+  return bundle;
+}
+
+char *
+ephy_sync_crypto_generate_crypto_keys (gsize key_len)
+{
+  JsonNode *node;
+  JsonObject *object;
+  JsonArray *array;
+  guint8 *aes_key;
+  guint8 *hmac_key;
+  char *aes_key_b64;
+  char *hmac_key_b64;
+  char *payload;
+
+  aes_key = g_malloc (key_len);
+  ephy_sync_crypto_random_bytes_gen (NULL, key_len, aes_key);
+  aes_key_b64 = g_base64_encode (aes_key, key_len);
+  hmac_key = g_malloc (key_len);
+  ephy_sync_crypto_random_bytes_gen (NULL, key_len, hmac_key);
+  hmac_key_b64 = g_base64_encode (hmac_key, key_len);
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  object = json_object_new ();
+  array = json_array_new ();
+  json_array_add_string_element (array, aes_key_b64);
+  json_array_add_string_element (array, hmac_key_b64);
+  json_object_set_array_member (object, "default", array);
+  json_object_set_object_member (object, "collections", json_object_new ());
+  json_object_set_string_member (object, "collection", "crypto");
+  json_object_set_string_member (object, "id", "keys");
+  json_node_set_object (node, object);
+  payload = json_to_string (node, FALSE);
+
+  json_object_unref (object);
+  json_node_unref (node);
+  g_free (hmac_key_b64);
+  g_free (hmac_key);
+  g_free (aes_key_b64);
+  g_free (aes_key);
+
+  return payload;
+}
+
+char *
+ephy_sync_crypto_decrypt_record (const char          *payload,
+                                 SyncCryptoKeyBundle *bundle)
+{
+  JsonNode *node = NULL;
+  JsonObject *json = NULL;
+  GError *error = NULL;
+  guint8 *aes_key = NULL;
+  guint8 *hmac_key = NULL;
+  guint8 *ciphertext = NULL;
+  guint8 *iv = NULL;
+  char *cleartext = NULL;
+  const char *ciphertext_b64;
+  const char *iv_b64;
+  const char *hmac;
+  gsize ciphertext_len;
+  gsize iv_len;
+
+  g_return_val_if_fail (payload, NULL);
+  g_return_val_if_fail (bundle, NULL);
+
+  /* Extract ciphertext, iv and hmac from payload. */
+  node = json_from_string (payload, &error);
+  if (error) {
+    g_warning ("Payload is not a valid JSON: %s", error->message);
+    goto out;
+  }
+  json = json_node_get_object (node);
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out;
+  }
+  ciphertext_b64 = json_object_get_string_member (json, "ciphertext");
+  iv_b64 = json_object_get_string_member (json, "IV");
+  hmac = json_object_get_string_member (json, "hmac");
+  if (!ciphertext_b64 || !iv_b64 || !hmac) {
+    g_warning ("JSON object has missing or invalid members");
+    goto out;
+  }
+
+  /* Get the encryption key and the HMAC key. */
+  aes_key = ephy_sync_crypto_decode_hex (bundle->aes_key_hex);
+  hmac_key = ephy_sync_crypto_decode_hex (bundle->hmac_key_hex);
+
+  /* Under no circumstances should a client try to decrypt a record
+   * if the HMAC verification fails. */
+  if (!ephy_sync_crypto_hmac_is_valid (ciphertext_b64, hmac_key, hmac)) {
+    g_warning ("Incorrect HMAC value");
+    goto out;
+  }
+
+  /* Finally, decrypt the record. */
+  ciphertext = g_base64_decode (ciphertext_b64, &ciphertext_len);
+  iv = g_base64_decode (iv_b64, &iv_len);
+  cleartext = ephy_sync_crypto_aes_256_decrypt (ciphertext, ciphertext_len, aes_key, iv);
+
+out:
+  g_free (ciphertext);
+  g_free (iv);
+  g_free (aes_key);
+  g_free (hmac_key);
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
+
+  return cleartext;
+}
+
+char *
+ephy_sync_crypto_encrypt_record (const char          *cleartext,
+                                 SyncCryptoKeyBundle *bundle)
+{
+  JsonNode *node;
+  JsonObject *object;
+  char *payload;
+  char *iv_b64;
+  char *ciphertext_b64;
+  char *hmac;
+  guint8 *aes_key;
+  guint8 *hmac_key;
+  guint8 *ciphertext;
+  guint8 *iv;
+  gsize ciphertext_len;
+
+  g_return_val_if_fail (cleartext, NULL);
+  g_return_val_if_fail (bundle, NULL);
+
+  /* Get the encryption key and the HMAC key. */
+  aes_key = ephy_sync_crypto_decode_hex (bundle->aes_key_hex);
+  hmac_key = ephy_sync_crypto_decode_hex (bundle->hmac_key_hex);
+
+  /* Generate a random 16 bytes initialization vector. */
+  iv = g_malloc (IV_LEN);
+  ephy_sync_crypto_random_bytes_gen (NULL, IV_LEN, iv);
+
+  /* Encrypt the record using the AES key. */
+  ciphertext = ephy_sync_crypto_aes_256_encrypt (cleartext, aes_key, iv, &ciphertext_len);
+  ciphertext_b64 = g_base64_encode (ciphertext, ciphertext_len);
+  iv_b64 = g_base64_encode (iv, IV_LEN);
+  /* SHA256 expects a 32 bytes key. */
+  hmac = g_compute_hmac_for_string (G_CHECKSUM_SHA256, hmac_key, 32, ciphertext_b64, -1);
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  object = json_object_new ();
+  json_object_set_string_member (object, "ciphertext", ciphertext_b64);
+  json_object_set_string_member (object, "IV", iv_b64);
+  json_object_set_string_member (object, "hmac", hmac);
+  json_node_set_object (node, object);
+  payload = json_to_string (node, FALSE);
+
+  json_object_unref (object);
+  json_node_unref (node);
+  g_free (hmac);
+  g_free (iv_b64);
+  g_free (ciphertext_b64);
+  g_free (ciphertext);
+  g_free (iv);
+  g_free (aes_key);
+  g_free (hmac_key);
+
+  return payload;
 }
 
-EphySyncCryptoHawkHeader *
-ephy_sync_crypto_compute_hawk_header (const char                *url,
-                                      const char                *method,
-                                      const char                *id,
-                                      guint8                    *key,
-                                      gsize                      key_len,
-                                      EphySyncCryptoHawkOptions *options)
+SyncCryptoHawkHeader *
+ephy_sync_crypto_compute_hawk_header (const char            *url,
+                                      const char            *method,
+                                      const char            *id,
+                                      const guint8          *key,
+                                      gsize                  key_len,
+                                      SyncCryptoHawkOptions *options)
 {
-  EphySyncCryptoHawkArtifacts *artifacts;
+  SyncCryptoHawkHeader *hheader;
+  SyncCryptoHawkArtifacts *artifacts;
   SoupURI *uri;
   char *resource;
   char *hash;
@@ -619,32 +1108,35 @@ ephy_sync_crypto_compute_hawk_header (const char                *url,
   char *nonce;
   char *payload;
   char *timestamp;
+  guint8 *bytes;
   gint64 ts;
 
-  g_return_val_if_fail (url != NULL, NULL);
-  g_return_val_if_fail (method != NULL, NULL);
-  g_return_val_if_fail (id != NULL, NULL);
-  g_return_val_if_fail (key != NULL, NULL);
+  g_return_val_if_fail (url, NULL);
+  g_return_val_if_fail (method, NULL);
+  g_return_val_if_fail (id, NULL);
+  g_return_val_if_fail (key, NULL);
 
-  ts = ephy_sync_utils_current_time_seconds ();
+  ts = g_get_real_time () / 1000000;
   hash = options ? g_strdup (options->hash) : NULL;
   payload = options ? options->payload : NULL;
   timestamp = options ? options->timestamp : NULL;
   uri = soup_uri_new (url);
-  resource = soup_uri_get_query (uri) == NULL ? g_strdup (soup_uri_get_path (uri))
-                                              : g_strconcat (soup_uri_get_path (uri),
-                                                             "?",
-                                                             soup_uri_get_query (uri),
-                                                             NULL);
+  resource = !soup_uri_get_query (uri) ? g_strdup (soup_uri_get_path (uri))
+                                       : g_strconcat (soup_uri_get_path (uri),
+                                                      "?",
+                                                      soup_uri_get_query (uri),
+                                                      NULL);
 
-  if (options != NULL && options->nonce != NULL) {
+  if (options && options->nonce) {
     nonce = g_strdup (options->nonce);
   } else {
-    nonce = g_malloc0 (NONCE_LEN + 1);
-    ephy_sync_crypto_random_hex_gen (NULL, NONCE_LEN, (guint8 *)nonce);
+    bytes = g_malloc (NONCE_LEN / 2);
+    ephy_sync_crypto_random_bytes_gen (NULL, NONCE_LEN / 2, bytes);
+    nonce = ephy_sync_crypto_encode_hex (bytes, NONCE_LEN / 2);
+    g_free (bytes);
   }
 
-  if (timestamp != NULL) {
+  if (timestamp) {
     char *local_time_offset;
     gint64 offset;
 
@@ -653,7 +1145,7 @@ ephy_sync_crypto_compute_hawk_header (const char                *url,
     ts = g_ascii_strtoll (timestamp, NULL, 10) + offset;
   }
 
-  if (hash == NULL && payload != NULL) {
+  if (!hash && payload) {
     const char *content_type = options ? options->content_type : "text/plain";
 
     /* Calculate the hash for the given payload. */
@@ -678,16 +1170,16 @@ ephy_sync_crypto_compute_hawk_header (const char                *url,
                         NULL);
 
   /* Append pre-calculated payload hash if any. */
-  if (artifacts->hash != NULL && strlen (artifacts->hash) > 0)
+  if (artifacts->hash && strlen (artifacts->hash) > 0)
     header = ephy_sync_crypto_append_to_header (header, "hash", artifacts->hash);
 
   /* Append the application specific data if any. */
-  if (artifacts->ext != NULL && strlen (artifacts->ext) > 0) {
+  if (artifacts->ext && strlen (artifacts->ext) > 0) {
     char *h_ext;
     char *tmp_ext;
 
-    tmp_ext = ephy_sync_utils_find_and_replace (artifacts->ext, "\\", "\\\\");
-    h_ext = ephy_sync_utils_find_and_replace (tmp_ext, "\n", "\\n");
+    tmp_ext = ephy_sync_crypto_find_and_replace (artifacts->ext, "\\", "\\\\");
+    h_ext = ephy_sync_crypto_find_and_replace (tmp_ext, "\n", "\\n");
     header = ephy_sync_crypto_append_to_header (header, "ext", h_ext);
 
     g_free (h_ext);
@@ -699,29 +1191,32 @@ ephy_sync_crypto_compute_hawk_header (const char                *url,
   header = ephy_sync_crypto_append_to_header (header, "mac", mac);
 
   /* Append the Oz application id if any. */
-  if (artifacts->app != NULL) {
+  if (artifacts->app) {
     header = ephy_sync_crypto_append_to_header (header, "app", artifacts->app);
 
     /* Append the Oz delegated-by application id if any. */
-    if (artifacts->dlg != NULL)
+    if (artifacts->dlg)
       header = ephy_sync_crypto_append_to_header (header, "dlg", artifacts->dlg);
   }
 
+  hheader = ephy_sync_crypto_hawk_header_new (header, artifacts);
+
   soup_uri_free (uri);
   g_free (hash);
   g_free (mac);
   g_free (nonce);
   g_free (resource);
+  g_free (header);
 
-  return ephy_sync_crypto_hawk_header_new (header, artifacts);
+  return hheader;
 }
 
-EphySyncCryptoRSAKeyPair *
+SyncCryptoRSAKeyPair *
 ephy_sync_crypto_generate_rsa_key_pair (void)
 {
   struct rsa_public_key public;
   struct rsa_private_key private;
-  int retval;
+  int success;
 
   rsa_public_key_init (&public);
   rsa_private_key_init (&private);
@@ -730,24 +1225,20 @@ ephy_sync_crypto_generate_rsa_key_pair (void)
   mpz_set_ui (public.e, 65537);
 
   /* Key sizes below 2048 are considered breakable and should not be used. */
-  retval = rsa_generate_keypair (&public, &private,
-                                 NULL, ephy_sync_crypto_random_hex_gen,
-                                 NULL, NULL, 2048, 0);
-  if (retval == 0) {
-    g_warning ("Failed to generate RSA key pair");
-    rsa_public_key_clear (&public);
-    rsa_private_key_clear (&private);
-    return NULL;
-  }
+  success = rsa_generate_keypair (&public, &private,
+                                  NULL, ephy_sync_crypto_random_bytes_gen,
+                                  NULL, NULL, 2048, 0);
+  /* Given correct parameters, this never fails. */
+  g_assert (success);
 
   return ephy_sync_crypto_rsa_key_pair_new (public, private);
 }
 
 char *
-ephy_sync_crypto_create_assertion (const char               *certificate,
-                                   const char               *audience,
-                                   guint64                   duration,
-                                   EphySyncCryptoRSAKeyPair *keypair)
+ephy_sync_crypto_create_assertion (const char           *certificate,
+                                   const char           *audience,
+                                   guint64               duration,
+                                   SyncCryptoRSAKeyPair *keypair)
 {
   mpz_t signature;
   const char *header = "{\"alg\": \"RS256\"}";
@@ -755,18 +1246,19 @@ ephy_sync_crypto_create_assertion (const char               *certificate,
   char *body_b64;
   char *header_b64;
   char *to_sign;
-  char *sig_b64 = NULL;
-  char *assertion = NULL;
+  char *sig_b64;
+  char *assertion;
   char *digest_hex;
   guint8 *digest;
-  guint8 *sig = NULL;
+  guint8 *sig;
   guint64 expires_at;
   gsize expected_size;
   gsize count;
+  int success;
 
-  g_return_val_if_fail (certificate != NULL, NULL);
-  g_return_val_if_fail (audience != NULL, NULL);
-  g_return_val_if_fail (keypair != NULL, NULL);
+  g_return_val_if_fail (certificate, NULL);
+  g_return_val_if_fail (audience, NULL);
+  g_return_val_if_fail (keypair, NULL);
 
   /* Encode the header and body to base64 url safe and join them. */
   expires_at = g_get_real_time () / 1000 + duration * 1000;
@@ -781,27 +1273,22 @@ ephy_sync_crypto_create_assertion (const char               *certificate,
 
   /* Use the provided key pair to RSA sign the message. */
   mpz_init (signature);
-  if (rsa_sha256_sign_digest_tr (&keypair->public, &keypair->private,
-                                 NULL, ephy_sync_crypto_random_hex_gen,
-                                 digest, signature) == 0) {
-    g_warning ("Failed to sign the message. Giving up.");
-    goto out;
-  }
+  success = rsa_sha256_sign_digest_tr (&keypair->public, &keypair->private,
+                                       NULL, ephy_sync_crypto_random_bytes_gen,
+                                       digest, signature);
+  /* Given correct parameters, this never fails. */
+  g_assert (success);
 
   expected_size = (mpz_sizeinbase (signature, 2) + 7) / 8;
   sig = g_malloc (expected_size);
   mpz_export (sig, &count, 1, sizeof (guint8), 0, 0, signature);
-
-  if (count != expected_size) {
-    g_warning ("Expected %lu bytes, got %lu. Giving up.", count, expected_size);
-    goto out;
-  }
+  /* Given correct parameters, this never fails. */
+  g_assert (count == expected_size);
 
   /* Finally, join certificate, header, body and signed message to create the assertion. */
   sig_b64 = ephy_sync_crypto_base64_urlsafe_encode (sig, count, TRUE);
   assertion = g_strdup_printf ("%s~%s.%s.%s", certificate, header_b64, body_b64, sig_b64);
 
-out:
   g_free (body);
   g_free (body_b64);
   g_free (header_b64);
@@ -815,49 +1302,23 @@ out:
   return assertion;
 }
 
-void
-ephy_sync_crypto_random_hex_gen (void   *ctx,
-                                 gsize   length,
-                                 guint8 *dst)
-{
-  FILE *fp;
-  gsize num_bytes;
-  guint8 *bytes;
-  char *hex;
-
-  g_assert (length > 0);
-  num_bytes = (length + 1) / 2;
-  bytes = g_malloc (num_bytes);
-
-  fp = fopen ("/dev/urandom", "r");
-  fread (bytes, sizeof (guint8), num_bytes, fp);
-  hex = ephy_sync_crypto_encode_hex (bytes, num_bytes);
-
-  for (gsize i = 0; i < length; i++)
-    dst[i] = hex[i];
-
-  g_free (bytes);
-  g_free (hex);
-  fclose (fp);
-}
-
 char *
-ephy_sync_crypto_base64_urlsafe_encode (guint8   *data,
-                                        gsize     data_len,
-                                        gboolean  strip)
+ephy_sync_crypto_base64_urlsafe_encode (const guint8 *data,
+                                        gsize         data_len,
+                                        gboolean      strip)
 {
   char *base64;
   char *out;
   gsize start = 0;
   gssize end;
 
-  g_return_val_if_fail (data != NULL, NULL);
+  g_return_val_if_fail (data, NULL);
 
   base64 = g_base64_encode (data, data_len);
   end = strlen (base64) - 1;
 
   /* Strip the data of any leading or trailing '=' characters. */
-  if (strip == TRUE) {
+  if (strip) {
     while (start < strlen (base64) && base64[start] == '=')
       start++;
 
@@ -882,11 +1343,11 @@ ephy_sync_crypto_base64_urlsafe_decode (const char  *text,
   char *to_decode;
   char *suffix = NULL;
 
-  g_return_val_if_fail (text != NULL, NULL);
-  g_return_val_if_fail (out_len != NULL, NULL);
+  g_return_val_if_fail (text, NULL);
+  g_return_val_if_fail (out_len, NULL);
 
   /* Fill the text with trailing '=' characters up to the proper length. */
-  if (fill == TRUE)
+  if (fill)
     suffix = g_strnfill ((4 - strlen (text) % 4) % 4, '=');
 
   to_decode = g_strconcat (text, suffix, NULL);
@@ -899,71 +1360,22 @@ ephy_sync_crypto_base64_urlsafe_decode (const char  *text,
   return out;
 }
 
-guint8 *
-ephy_sync_crypto_aes_256 (EphySyncCryptoAES256Mode  mode,
-                          const guint8             *key,
-                          const guint8             *data,
-                          gsize                     data_len,
-                          gsize                    *out_len)
-{
-  struct aes256_ctx aes;
-  gsize padded_len = data_len;
-  guint8 *padded_data;
-  guint8 *out;
-
-  g_return_val_if_fail (key != NULL, NULL);
-  g_return_val_if_fail (data != NULL, NULL);
-
-  /* Since Nettle enforces the length of the data to be a multiple of
-   * AES_BLOCK_SIZE, the data needs to be padded accordingly. Because any
-   * data that is decrypted has to be encrypted first, crash if the length
-   * is incorrect at decryption.
-   */
-  if (mode == AES_256_MODE_ENCRYPT)
-    padded_len = data_len + (AES_BLOCK_SIZE - data_len % AES_BLOCK_SIZE);
-  else if (mode == AES_256_MODE_DECRYPT)
-    g_assert (data_len % AES_BLOCK_SIZE == 0);
-
-  out = g_malloc0 (padded_len);
-  padded_data = g_malloc0 (padded_len);
-  memcpy (padded_data, data, data_len);
-
-  if (mode == AES_256_MODE_ENCRYPT) {
-    aes256_set_encrypt_key (&aes, key);
-    aes256_encrypt (&aes, padded_len, out, padded_data);
-  } else if (mode == AES_256_MODE_DECRYPT) {
-    aes256_set_decrypt_key (&aes, key);
-    aes256_decrypt (&aes, padded_len, out, padded_data);
-  }
-
-  if (out_len != NULL)
-    *out_len = padded_len;
-
-  g_free (padded_data);
-
-  return out;
-}
-
 char *
-ephy_sync_crypto_encode_hex (guint8 *data,
-                             gsize   data_len)
+ephy_sync_crypto_encode_hex (const guint8 *data,
+                             gsize         data_len)
 {
   char *retval;
-  gsize length;
-
-  g_return_val_if_fail (data != NULL, NULL);
 
-  length = data_len == 0 ? EPHY_SYNC_TOKEN_LENGTH : data_len;
-  retval = g_malloc (length * 2 + 1);
+  g_return_val_if_fail (data, NULL);
 
-  for (gsize i = 0; i < length; i++) {
+  retval = g_malloc (data_len * 2 + 1);
+  for (gsize i = 0; i < data_len; i++) {
     guint8 byte = data[i];
 
     retval[2 * i] = hex_digits[byte >> 4];
     retval[2 * i + 1] = hex_digits[byte & 0xf];
   }
-
-  retval[length * 2] = 0;
+  retval[data_len * 2] = 0;
 
   return retval;
 }
@@ -972,14 +1384,35 @@ guint8 *
 ephy_sync_crypto_decode_hex (const char *hex)
 {
   guint8 *retval;
-  gsize hex_len = strlen (hex);
 
-  g_return_val_if_fail (hex != NULL, NULL);
-  g_return_val_if_fail (hex_len % 2 == 0, NULL);
+  g_return_val_if_fail (hex, NULL);
 
-  retval = g_malloc (hex_len / 2);
-  for (gsize i = 0, j = 0; i < hex_len; i += 2, j++)
-    sscanf(hex + i, "%2hhx", retval + j);
+  retval = g_malloc (strlen (hex) / 2);
+  for (gsize i = 0, j = 0; i < strlen (hex); i += 2, j++)
+    sscanf (hex + i, "%2hhx", retval + j);
 
   return retval;
 }
+
+char *
+ephy_sync_crypto_get_random_sync_id (void)
+{
+  char *id;
+  char *base64;
+  guint8 *bytes;
+  gsize bytes_len;
+
+  /* The sync id is a base64-urlsafe string. Base64 uses 4 chars to represent 3 bytes,
+   * therefore we need ceil(len * 3 / 4) bytes to cover the requested length. */
+  bytes_len = (SYNC_ID_LEN + 3) / 4 * 3;
+  bytes = g_malloc (bytes_len);
+
+  ephy_sync_crypto_random_bytes_gen (NULL, bytes_len, bytes);
+  base64 = ephy_sync_crypto_base64_urlsafe_encode (bytes, bytes_len, FALSE);
+  id = g_strndup (base64, SYNC_ID_LEN);
+
+  g_free (base64);
+  g_free (bytes);
+
+  return id;
+}
diff --git a/src/sync/ephy-sync-crypto.h b/src/sync/ephy-sync-crypto.h
index 18368c4..1038d9e 100644
--- a/src/sync/ephy-sync-crypto.h
+++ b/src/sync/ephy-sync-crypto.h
@@ -21,17 +21,11 @@
 #pragma once
 
 #include <glib-object.h>
+#include <json-glib/json-glib.h>
 #include <nettle/rsa.h>
 
 G_BEGIN_DECLS
 
-#define EPHY_SYNC_TOKEN_LENGTH 32
-
-typedef enum {
-  AES_256_MODE_ENCRYPT,
-  AES_256_MODE_DECRYPT
-} EphySyncCryptoAES256Mode;
-
 typedef struct {
   char *app;
   char *dlg;
@@ -42,7 +36,7 @@ typedef struct {
   char *nonce;
   char *payload;
   char *timestamp;
-} EphySyncCryptoHawkOptions;
+} SyncCryptoHawkOptions;
 
 typedef struct {
   char *app;
@@ -55,72 +49,82 @@ typedef struct {
   char *port;
   char *resource;
   char *ts;
-} EphySyncCryptoHawkArtifacts;
+} SyncCryptoHawkArtifacts;
 
 typedef struct {
   char *header;
-  EphySyncCryptoHawkArtifacts *artifacts;
-} EphySyncCryptoHawkHeader;
+  SyncCryptoHawkArtifacts *artifacts;
+} SyncCryptoHawkHeader;
 
 typedef struct {
   struct rsa_public_key public;
   struct rsa_private_key private;
-} EphySyncCryptoRSAKeyPair;
+} SyncCryptoRSAKeyPair;
+
+typedef struct {
+  char *aes_key_hex;
+  char *hmac_key_hex;
+} SyncCryptoKeyBundle;
 
-EphySyncCryptoHawkOptions *ephy_sync_crypto_hawk_options_new        (const char *app,
-                                                                     const char *dlg,
-                                                                     const char *ext,
-                                                                     const char *content_type,
-                                                                     const char *hash,
-                                                                     const char *local_time_offset,
-                                                                     const char *nonce,
-                                                                     const char *payload,
-                                                                     const char *timestamp);
-void                       ephy_sync_crypto_hawk_options_free       (EphySyncCryptoHawkOptions *options);
-void                       ephy_sync_crypto_hawk_header_free        (EphySyncCryptoHawkHeader *header);
-void                       ephy_sync_crypto_rsa_key_pair_free       (EphySyncCryptoRSAKeyPair *keypair);
-void                       ephy_sync_crypto_process_key_fetch_token (const char  *keyFetchToken,
-                                                                     guint8     **tokenID,
-                                                                     guint8     **reqHMACkey,
-                                                                     guint8     **respHMACkey,
-                                                                     guint8     **respXORkey);
-void                       ephy_sync_crypto_process_session_token   (const char  *sessionToken,
-                                                                     guint8     **tokenID,
-                                                                     guint8     **reqHMACkey,
-                                                                     guint8     **requestKey);
-void                       ephy_sync_crypto_compute_sync_keys       (const char  *bundle,
-                                                                     guint8      *respHMACkey,
-                                                                     guint8      *respXORkey,
-                                                                     guint8      *unwrapBKey,
-                                                                     guint8     **kA,
-                                                                     guint8     **kB);
-EphySyncCryptoHawkHeader  *ephy_sync_crypto_compute_hawk_header     (const char                *url,
-                                                                     const char                *method,
-                                                                     const char                *id,
-                                                                     guint8                    *key,
-                                                                     gsize                      key_len,
-                                                                     EphySyncCryptoHawkOptions *options);
-EphySyncCryptoRSAKeyPair  *ephy_sync_crypto_generate_rsa_key_pair   (void);
-char                      *ephy_sync_crypto_create_assertion        (const char               *certificate,
-                                                                     const char               *audience,
-                                                                     guint64                   duration,
-                                                                     EphySyncCryptoRSAKeyPair *keypair);
-void                       ephy_sync_crypto_random_hex_gen          (void   *ctx,
-                                                                     gsize   length,
-                                                                     guint8 *dst);
-char                      *ephy_sync_crypto_base64_urlsafe_encode   (guint8   *data,
-                                                                     gsize     data_len,
-                                                                     gboolean  strip);
-guint8                    *ephy_sync_crypto_base64_urlsafe_decode   (const char *text,
-                                                                     gsize      *out_len,
-                                                                     gboolean    fill);
-guint8                    *ephy_sync_crypto_aes_256                 (EphySyncCryptoAES256Mode  mode,
-                                                                     const guint8             *key,
-                                                                     const guint8             *data,
-                                                                     gsize                     data_len,
-                                                                     gsize                    *out_len);
-char                      *ephy_sync_crypto_encode_hex              (guint8 *data,
-                                                                     gsize   data_len);
-guint8                    *ephy_sync_crypto_decode_hex              (const char *hex);
+SyncCryptoHawkOptions  *ephy_sync_crypto_hawk_options_new         (const char *app,
+                                                                   const char *dlg,
+                                                                   const char *ext,
+                                                                   const char *content_type,
+                                                                   const char *hash,
+                                                                   const char *local_time_offset,
+                                                                   const char *nonce,
+                                                                   const char *payload,
+                                                                   const char *timestamp);
+void                    ephy_sync_crypto_hawk_options_free        (SyncCryptoHawkOptions *options);
+void                    ephy_sync_crypto_hawk_header_free         (SyncCryptoHawkHeader *header);
+void                    ephy_sync_crypto_rsa_key_pair_free        (SyncCryptoRSAKeyPair *keypair);
+SyncCryptoKeyBundle    *ephy_sync_crypto_key_bundle_from_array    (JsonArray *array);
+void                    ephy_sync_crypto_key_bundle_free          (SyncCryptoKeyBundle *bundle);
+void                    ephy_sync_crypto_process_key_fetch_token  (const char  *keyFetchToken,
+                                                                   guint8     **tokenID,
+                                                                   guint8     **reqHMACkey,
+                                                                   guint8     **respHMACkey,
+                                                                   guint8     **respXORkey,
+                                                                   gsize        token_len);
+void                    ephy_sync_crypto_process_session_token    (const char  *sessionToken,
+                                                                   guint8     **tokenID,
+                                                                   guint8     **reqHMACkey,
+                                                                   guint8     **requestKey,
+                                                                   gsize        token_len);
+gboolean                ephy_sync_crypto_compute_sync_keys        (const char    *bundle_hex,
+                                                                   const guint8  *respHMACkey,
+                                                                   const guint8  *respXORkey,
+                                                                   const guint8  *unwrapBKey,
+                                                                   guint8       **kA,
+                                                                   guint8       **kB,
+                                                                   gsize          key_len);
+SyncCryptoKeyBundle    *ephy_sync_crypto_derive_key_bundle        (const guint8 *key,
+                                                                   gsize         key_len);
+char                   *ephy_sync_crypto_generate_crypto_keys     (gsize key_len);
+char                   *ephy_sync_crypto_decrypt_record           (const char          *payload,
+                                                                   SyncCryptoKeyBundle *bundle);
+char                   *ephy_sync_crypto_encrypt_record           (const char          *cleartext,
+                                                                   SyncCryptoKeyBundle *bundle);
+SyncCryptoHawkHeader   *ephy_sync_crypto_compute_hawk_header      (const char            *url,
+                                                                   const char            *method,
+                                                                   const char            *id,
+                                                                   const guint8          *key,
+                                                                   gsize                  key_len,
+                                                                   SyncCryptoHawkOptions *options);
+SyncCryptoRSAKeyPair   *ephy_sync_crypto_generate_rsa_key_pair    (void);
+char                   *ephy_sync_crypto_create_assertion         (const char           *certificate,
+                                                                   const char           *audience,
+                                                                   guint64               duration,
+                                                                   SyncCryptoRSAKeyPair *keypair);
+char                   *ephy_sync_crypto_base64_urlsafe_encode    (const guint8 *data,
+                                                                   gsize         data_len,
+                                                                   gboolean      strip);
+guint8                 *ephy_sync_crypto_base64_urlsafe_decode    (const char *text,
+                                                                   gsize      *out_len,
+                                                                   gboolean    fill);
+char                   *ephy_sync_crypto_encode_hex               (const guint8 *data,
+                                                                   gsize         data_len);
+guint8                 *ephy_sync_crypto_decode_hex               (const char *hex);
+char                   *ephy_sync_crypto_get_random_sync_id       (void);
 
 G_END_DECLS
diff --git a/src/sync/ephy-sync-secret.c b/src/sync/ephy-sync-secret.c
index b4269e9..3d4c9e3 100644
--- a/src/sync/ephy-sync-secret.c
+++ b/src/sync/ephy-sync-secret.c
@@ -33,10 +33,10 @@ enum {
 };
 
 const SecretSchema *
-ephy_sync_secret_get_token_schema (void)
+ephy_sync_secret_get_secret_schema (void)
 {
   static const SecretSchema schema = {
-    "org.epiphany.SyncTokens", SECRET_SCHEMA_NONE,
+    "org.epiphany.SyncSecrets", SECRET_SCHEMA_NONE,
     {
       { EMAIL_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
       { "NULL", 0 },
@@ -47,60 +47,59 @@ ephy_sync_secret_get_token_schema (void)
 }
 
 static void
-forget_tokens_cb (SecretService *service,
-                  GAsyncResult  *result,
-                  gpointer       user_data)
+forget_secrets_cb (SecretService *service,
+                   GAsyncResult  *result,
+                   gpointer       user_data)
 {
   GError *error = NULL;
 
   secret_service_clear_finish (service, result, &error);
 
-  if (error != NULL) {
+  if (error) {
     g_warning ("sync-secret: Failed to clear the secret schema: %s", error->message);
     g_error_free (error);
   }
 }
 
 void
-ephy_sync_secret_forget_tokens (void)
+ephy_sync_secret_forget_secrets (void)
 {
   GHashTable *attributes;
 
-  attributes = secret_attributes_build (EPHY_SYNC_TOKEN_SCHEMA, NULL);
-  secret_service_clear (NULL, EPHY_SYNC_TOKEN_SCHEMA, attributes,
-                        NULL, (GAsyncReadyCallback)forget_tokens_cb, NULL);
+  attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA, NULL);
+  secret_service_clear (NULL, EPHY_SYNC_SECRET_SCHEMA, attributes,
+                        NULL, (GAsyncReadyCallback)forget_secrets_cb, NULL);
 
   g_hash_table_unref (attributes);
 }
 
 static void
-load_tokens_cb (SecretService *service,
-                GAsyncResult  *result,
-                gpointer       user_data)
+load_secrets_cb (SecretService *service,
+                 GAsyncResult  *result,
+                 gpointer       user_data)
 {
   EphySyncService *sync_service = EPHY_SYNC_SERVICE (user_data);
   SecretItem *item;
-  GHashTable *attributes;
+  GHashTable *attributes = NULL;
   SecretValue *value = NULL;
-  JsonParser *parser = NULL;
+  JsonNode *node = NULL;
   JsonObject *json;
   GList *matches = NULL;
   GList *members = NULL;
   GError *error = NULL;
   GError *ret_error = NULL;
-  const char *tokens;
   const char *email;
-  char *user_email;
+  const char *user_email;
 
   matches = secret_service_search_finish (service, result, &error);
 
-  if (error != NULL || matches == NULL) {
+  if (error || !matches) {
     g_set_error (&ret_error,
                  SYNC_SECRET_ERROR,
                  SYNC_SECRET_ERROR_LOAD,
-                 _("The sync tokens could not be found."));
-    if (error != NULL)
-      g_warning ("sync-secret: Failed to find the tokens: %s", error->message);
+                 _("The sync secrets could not be found."));
+    if (error)
+      g_warning ("sync-secret: Failed to find sync secrets: %s", error->message);
     goto out;
   }
 
@@ -108,7 +107,7 @@ load_tokens_cb (SecretService *service,
     g_set_error (&ret_error,
                  SYNC_SECRET_ERROR,
                  SYNC_SECRET_ERROR_LOAD,
-                 _("Found more than one set of sync tokens."));
+                 _("Found more than one set of sync secrets."));
     g_warning ("sync-secret: Was expecting exactly one match, found more.");
     goto out;
   }
@@ -118,83 +117,78 @@ load_tokens_cb (SecretService *service,
   email = g_hash_table_lookup (attributes, EMAIL_KEY);
   user_email = ephy_sync_service_get_user_email (sync_service);
 
-  if (email == NULL || g_strcmp0 (email, user_email) != 0) {
+  if (!email || g_strcmp0 (email, user_email)) {
     g_set_error (&ret_error,
                  SYNC_SECRET_ERROR,
                  SYNC_SECRET_ERROR_LOAD,
-                 _("Could not find the sync tokens for the currently logged-in user."));
+                 _("Could not find the sync secrets for the currently logged-in user."));
     g_warning ("sync-secret: Emails differ: %s vs %s", email, user_email);
     goto out;
   }
 
   value = secret_item_get_secret (item);
-  if (value == NULL) {
+  if (!value) {
     g_set_error (&ret_error,
                  SYNC_SECRET_ERROR,
                  SYNC_SECRET_ERROR_LOAD,
-                 _("Could not get the secret value of the sync tokens."));
-    g_warning ("sync-secret: The secret item of the tokens has a NULL value.");
+                 _("Could not get the secret value of the sync secrets."));
+    g_warning ("sync-secret: The secret item of the secrets has a NULL value.");
     goto out;
   }
 
-  parser = json_parser_new ();
-  tokens = secret_value_get_text (value);
-  json_parser_load_from_data (parser, tokens, -1, &error);
-
-  if (error != NULL) {
+  node = json_from_string (secret_value_get_text (value), &error);
+  if (error) {
     g_set_error (&ret_error,
                  SYNC_SECRET_ERROR,
                  SYNC_SECRET_ERROR_LOAD,
-                 _("The sync tokens are not a valid JSON."));
+                 _("The sync secrets are not a valid JSON."));
     g_warning ("sync-secret: Failed to load JSON from data: %s", error->message);
     goto out;
   }
 
-  json = json_node_get_object (json_parser_get_root (parser));
+  json = json_node_get_object (node);
   members = json_object_get_members (json);
 
-  /* Set the tokens. */
   for (GList *m = members; m != NULL; m = m->next) {
-    ephy_sync_service_set_token (sync_service,
-                                 json_object_get_string_member (json, m->data),
-                                 ephy_sync_utils_token_type_from_name (m->data));
+    ephy_sync_service_set_secret (sync_service,
+                                  (const char *)m->data,
+                                  json_object_get_string_member (json,
+                                                                 (const char *)m->data));
   }
 
 out:
-  /* Notify whether the tokens were successfully loaded. */
-  g_signal_emit_by_name (sync_service, "sync-tokens-load-finished", ret_error);
+  /* Notify whether the secrets were successfully loaded. */
+  g_signal_emit_by_name (sync_service, "sync-secrets-load-finished", ret_error);
 
-  if (error != NULL)
+  if (error)
     g_error_free (error);
-
-  if (ret_error != NULL)
+  if (ret_error)
     g_error_free (ret_error);
-
-  if (value != NULL)
+  if (value)
     secret_value_unref (value);
-
-  if (parser != NULL)
-    g_object_unref (parser);
-
+  if (node)
+    json_node_unref (node);
+  if (attributes)
+    g_hash_table_unref (attributes);
   g_list_free (members);
   g_list_free_full (matches, g_object_unref);
 }
 
 void
-ephy_sync_secret_load_tokens (EphySyncService *service)
+ephy_sync_secret_load_secrets (EphySyncService *service)
 {
   GHashTable *attributes;
 
-  attributes = secret_attributes_build (EPHY_SYNC_TOKEN_SCHEMA, NULL);
-  secret_service_search (NULL, EPHY_SYNC_TOKEN_SCHEMA, attributes,
+  attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA, NULL);
+  secret_service_search (NULL, EPHY_SYNC_SECRET_SCHEMA, attributes,
                          SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
-                         NULL, (GAsyncReadyCallback)load_tokens_cb, service);
+                         NULL, (GAsyncReadyCallback)load_secrets_cb, service);
 
   g_hash_table_unref (attributes);
 }
 
 static void
-store_tokens_cb (SecretService *service,
+store_secrets_cb (SecretService *service,
                  GAsyncResult  *result,
                  gpointer       user_data)
 {
@@ -203,56 +197,55 @@ store_tokens_cb (SecretService *service,
 
   secret_service_store_finish (service, result, &error);
 
-  /* Notify whether the tokens were successfully stored. */
-  g_signal_emit_by_name (sync_service, "sync-tokens-store-finished", error);
+  /* Notify whether the secrets were successfully stored. */
+  g_signal_emit_by_name (sync_service, "sync-secrets-store-finished", error);
 
-  if (error != NULL)
+  if (error)
     g_error_free (error);
 }
 
 void
-ephy_sync_secret_store_tokens (EphySyncService *service,
-                               const char      *email,
-                               const char      *uid,
-                               const char      *sessionToken,
-                               const char      *keyFetchToken,
-                               const char      *unwrapBKey,
-                               const char      *kA,
-                               const char      *kB)
+ephy_sync_secret_store_secrets (EphySyncService *service)
 {
-  SecretValue *value;
+  JsonNode *node;
+  JsonObject *object;
+  SecretValue *secret_value;
   GHashTable *attributes;
-  char *tokens;
+  GHashTable *secrets;
+  GHashTableIter iter;
+  gpointer key;
+  gpointer value;
+  char *to_store;
   char *label;
 
-  g_return_if_fail (email != NULL);
-  g_return_if_fail (uid != NULL);
-  g_return_if_fail (sessionToken != NULL);
-  g_return_if_fail (keyFetchToken != NULL);
-  g_return_if_fail (unwrapBKey != NULL);
-  g_return_if_fail (kA != NULL);
-  g_return_if_fail (kB != NULL);
-
-  tokens = ephy_sync_utils_build_json_string ("uid", uid,
-                                              "sessionToken", sessionToken,
-                                              "keyFetchToken", keyFetchToken,
-                                              "unwrapBKey", unwrapBKey,
-                                              "kA", kA,
-                                              "kB", kB,
-                                              NULL);
-  value = secret_value_new (tokens, -1, "text/plain");
-  attributes = secret_attributes_build (EPHY_SYNC_TOKEN_SCHEMA,
-                                        EMAIL_KEY, email,
+  g_return_if_fail (EPHY_IS_SYNC_SERVICE (service));
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  object = json_object_new ();
+
+  secrets = ephy_sync_service_get_secrets (service);
+  g_hash_table_iter_init (&iter, secrets);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    json_object_set_string_member (object, (const char *)key, (const char *)value);
+
+  json_node_set_object (node, object);
+  to_store = json_to_string (node, FALSE);
+  secret_value = secret_value_new (to_store, -1, "text/plain");
+  attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA, EMAIL_KEY,
+                                        ephy_sync_service_get_user_email (service),
                                         NULL);
   /* Translators: The %s represents the email of the user. */
-  label = g_strdup_printf (_("The sync tokens of %s"), email);
+  label = g_strdup_printf (_("The sync secrets of %s"),
+                           ephy_sync_service_get_user_email (service));
 
-  secret_service_store (NULL, EPHY_SYNC_TOKEN_SCHEMA, attributes,
-                        NULL, label, value, NULL,
-                        (GAsyncReadyCallback)store_tokens_cb, service);
+  secret_service_store (NULL, EPHY_SYNC_SECRET_SCHEMA, attributes,
+                        NULL, label, secret_value, NULL,
+                        (GAsyncReadyCallback)store_secrets_cb, service);
 
-  g_free (tokens);
   g_free (label);
-  secret_value_unref (value);
   g_hash_table_unref (attributes);
+  secret_value_unref (secret_value);
+  g_free (to_store);
+  json_object_unref (object);
+  json_node_unref (node);
 }
diff --git a/src/sync/ephy-sync-secret.h b/src/sync/ephy-sync-secret.h
index f540c8e..ea168fb 100644
--- a/src/sync/ephy-sync-secret.h
+++ b/src/sync/ephy-sync-secret.h
@@ -27,23 +27,14 @@
 
 G_BEGIN_DECLS
 
-const SecretSchema *ephy_sync_secret_get_token_schema (void) G_GNUC_CONST;
-
-#define EMAIL_KEY       "email_utf8"
-#define TOKEN_TYPE_KEY  "token_type"
-#define TOKEN_NAME_KEY  "token_name"
-
-#define EPHY_SYNC_TOKEN_SCHEMA (ephy_sync_secret_get_token_schema ())
-
-void ephy_sync_secret_forget_tokens (void);
-void ephy_sync_secret_load_tokens   (EphySyncService *service);
-void ephy_sync_secret_store_tokens  (EphySyncService *service,
-                                     const char      *email,
-                                     const char      *uid,
-                                     const char      *sessionToken,
-                                     const char      *keyFetchToken,
-                                     const char      *unwrapBKey,
-                                     const char      *kA,
-                                     const char      *kB);
+const SecretSchema *ephy_sync_secret_get_secret_schema (void) G_GNUC_CONST;
+
+#define EMAIL_KEY "email_utf8"
+
+#define EPHY_SYNC_SECRET_SCHEMA (ephy_sync_secret_get_secret_schema ())
+
+void ephy_sync_secret_forget_secrets (void);
+void ephy_sync_secret_load_secrets   (EphySyncService *service);
+void ephy_sync_secret_store_secrets  (EphySyncService *service);
 
 G_END_DECLS
diff --git a/src/sync/ephy-sync-service.c b/src/sync/ephy-sync-service.c
index 13d2eff..b5ed7c3 100644
--- a/src/sync/ephy-sync-service.c
+++ b/src/sync/ephy-sync-service.c
@@ -21,13 +21,10 @@
 #include "config.h"
 #include "ephy-sync-service.h"
 
-#include "ephy-bookmark.h"
-#include "ephy-bookmarks-manager.h"
 #include "ephy-debug.h"
 #include "ephy-embed-prefs.h"
 #include "ephy-notification.h"
 #include "ephy-settings.h"
-#include "ephy-shell.h"
 #include "ephy-sync-crypto.h"
 #include "ephy-sync-secret.h"
 
@@ -37,8 +34,10 @@
 
 #define MOZILLA_TOKEN_SERVER_URL  "https://token.services.mozilla.com/1.0/sync/1.5";
 #define MOZILLA_FXA_SERVER_URL    "https://api.accounts.firefox.com/v1/";
-#define EPHY_BOOKMARKS_COLLECTION "ephy-bookmarks"
-#define SYNC_FREQUENCY            (15 * 60) /* seconds */
+#define CERTIFICATE_DURATION      (60 * 60 * 1000) /* milliseconds, limited to 24 hours */
+#define ASSERTION_DURATION        (5 * 60)         /* seconds */
+#define STORAGE_VERSION           5
+#define TOKEN_LENGTH              32
 
 struct _EphySyncService {
   GObject      parent_instance;
@@ -46,16 +45,9 @@ struct _EphySyncService {
   SoupSession *session;
   guint        source_id;
 
-  char        *uid;
-  char        *sessionToken;
-  char        *keyFetchToken;
-  char        *unwrapBKey;
-  char        *kA;
-  char        *kB;
-
   char        *user_email;
-  double       sync_time;
-  gint64       auth_at;
+  GHashTable  *secrets;
+  GHashTable  *managers;
 
   gboolean     locked;
   char        *storage_endpoint;
@@ -64,47 +56,87 @@ struct _EphySyncService {
   gint64       storage_credentials_expiry_time;
   GQueue      *storage_queue;
 
-  char                     *certificate;
-  EphySyncCryptoRSAKeyPair *keypair;
+  char                 *certificate;
+  SyncCryptoRSAKeyPair *keypair;
 };
 
 G_DEFINE_TYPE (EphySyncService, ephy_sync_service, G_TYPE_OBJECT);
 
 enum {
+  UID,
+  SESSION_TOKEN,
+  MASTER_KEY,
+  CRYPTO_KEYS,
+  LAST_SECRET
+};
+
+static const char * const secrets[LAST_SECRET] = {
+  "uid",
+  "session_token",
+  "master_key",
+  "crypto_keys"
+};
+
+enum {
   STORE_FINISHED,
   LOAD_FINISHED,
+  SIGN_IN_ERROR,
+  SYNC_FREQUENCY_CHANGED,
+  SYNC_FINISHED,
   LAST_SIGNAL
 };
 
 static guint signals[LAST_SIGNAL];
 
 typedef struct {
-  EphySyncService     *service;
   char                *endpoint;
-  const char          *method;
+  char                *method;
   char                *request_body;
   double               modified_since;
   double               unmodified_since;
   SoupSessionCallback  callback;
   gpointer             user_data;
-} StorageServerRequestAsyncData;
+} StorageRequestAsyncData;
 
-static StorageServerRequestAsyncData *
-storage_server_request_async_data_new (EphySyncService     *service,
-                                       char                *endpoint,
-                                       const char          *method,
-                                       char                *request_body,
-                                       double               modified_since,
-                                       double               unmodified_since,
-                                       SoupSessionCallback  callback,
-                                       gpointer             user_data)
+typedef struct {
+  EphySyncService *service;
+  char            *email;
+  char            *uid;
+  char            *sessionToken;
+  char            *unwrapBKey;
+  char            *tokenID_hex;
+  guint8          *reqHMACkey;
+  guint8          *respHMACkey;
+  guint8          *respXORkey;
+} SignInAsyncData;
+
+typedef struct {
+  EphySyncService           *service;
+  EphySynchronizableManager *manager;
+  gboolean                   is_initial;
+  gboolean                   is_last;
+} SyncCollectionAsyncData;
+
+typedef struct {
+  EphySyncService           *service;
+  EphySynchronizableManager *manager;
+  EphySynchronizable        *synchronizable;
+} SyncAsyncData;
+
+static StorageRequestAsyncData *
+storage_request_async_data_new (const char          *endpoint,
+                                const char          *method,
+                                const char          *request_body,
+                                double               modified_since,
+                                double               unmodified_since,
+                                SoupSessionCallback  callback,
+                                gpointer             user_data)
 {
-  StorageServerRequestAsyncData *data;
+  StorageRequestAsyncData *data;
 
-  data = g_slice_new (StorageServerRequestAsyncData);
-  data->service = g_object_ref (service);
+  data = g_slice_new (StorageRequestAsyncData);
   data->endpoint = g_strdup (endpoint);
-  data->method = method;
+  data->method = g_strdup (method);
   data->request_body = g_strdup (request_body);
   data->modified_since = modified_since;
   data->unmodified_since = unmodified_since;
@@ -115,53 +147,171 @@ storage_server_request_async_data_new (EphySyncService     *service,
 }
 
 static void
-storage_server_request_async_data_free (StorageServerRequestAsyncData *data)
+storage_request_async_data_free (StorageRequestAsyncData *data)
 {
-  g_assert (data != NULL);
+  g_assert (data);
 
-  g_object_unref (data->service);
   g_free (data->endpoint);
+  g_free (data->method);
   g_free (data->request_body);
-  g_slice_free (StorageServerRequestAsyncData, data);
+  g_slice_free (StorageRequestAsyncData, data);
+}
+
+static SignInAsyncData *
+sign_in_async_data_new (EphySyncService *service,
+                        const char      *email,
+                        const char      *uid,
+                        const char      *sessionToken,
+                        const char      *unwrapBKey,
+                        const char      *tokenID_hex,
+                        const guint8    *reqHMACkey,
+                        const guint8    *respHMACkey,
+                        const guint8    *respXORkey)
+{
+  SignInAsyncData *data;
+
+  data = g_slice_new (SignInAsyncData);
+  data->service = g_object_ref (service);
+  data->email = g_strdup (email);
+  data->uid = g_strdup (uid);
+  data->sessionToken = g_strdup (sessionToken);
+  data->unwrapBKey = g_strdup (unwrapBKey);
+  data->tokenID_hex = g_strdup (tokenID_hex);
+  data->reqHMACkey = g_malloc (TOKEN_LENGTH);
+  memcpy (data->reqHMACkey, reqHMACkey, TOKEN_LENGTH);
+  data->respHMACkey = g_malloc (TOKEN_LENGTH);
+  memcpy (data->respHMACkey, respHMACkey, TOKEN_LENGTH);
+  data->respXORkey = g_malloc (2 * TOKEN_LENGTH);
+  memcpy (data->respXORkey, respXORkey, 2 * TOKEN_LENGTH);
+
+  return data;
 }
 
 static void
-destroy_session_response_cb (SoupSession *session,
-                             SoupMessage *msg,
-                             gpointer     user_data)
+sign_in_async_data_free (SignInAsyncData *data)
 {
-  JsonParser *parser;
+  g_assert (data);
+
+  g_object_unref (data->service);
+  g_free (data->email);
+  g_free (data->uid);
+  g_free (data->sessionToken);
+  g_free (data->unwrapBKey);
+  g_free (data->tokenID_hex);
+  g_free (data->reqHMACkey);
+  g_free (data->respHMACkey);
+  g_free (data->respXORkey);
+  g_slice_free (SignInAsyncData, data);
+}
+
+static SyncCollectionAsyncData *
+sync_collection_async_data_new (EphySyncService           *service,
+                                EphySynchronizableManager *manager,
+                                gboolean                   is_initial,
+                                gboolean                   is_last)
+{
+  SyncCollectionAsyncData *data;
+
+  data = g_slice_new (SyncCollectionAsyncData);
+  data->service = g_object_ref (service);
+  data->manager = g_object_ref (manager);
+  data->is_initial = is_initial;
+  data->is_last = is_last;
+
+  return data;
+}
+
+static void
+sync_collection_async_data_free (SyncCollectionAsyncData *data)
+{
+  g_assert (data);
+
+  g_object_unref (data->service);
+  g_object_unref (data->manager);
+  g_slice_free (SyncCollectionAsyncData, data);
+}
+
+static SyncAsyncData *
+sync_async_data_new (EphySyncService           *service,
+                     EphySynchronizableManager *manager,
+                     EphySynchronizable        *synchronizable)
+{
+  SyncAsyncData *data;
+
+  data = g_slice_new (SyncAsyncData);
+  data->service = g_object_ref (service);
+  data->manager = g_object_ref (manager);
+  data->synchronizable = g_object_ref (synchronizable);
+
+  return data;
+}
+
+static void
+sync_async_data_free (SyncAsyncData *data)
+{
+  g_assert (data);
+
+  g_object_unref (data->service);
+  g_object_unref (data->manager);
+  g_object_unref (data->synchronizable);
+  g_slice_free (SyncAsyncData, data);
+}
+
+static SyncCryptoKeyBundle *
+ephy_sync_service_get_key_bundle (EphySyncService *self,
+                                  const char      *collection)
+{
+  SyncCryptoKeyBundle *bundle = NULL;
+  JsonNode *node;
   JsonObject *json;
+  JsonObject *collections;
+  JsonArray *array;
+  GError *error = NULL;
+  const char *crypto_keys;
 
-  if (msg->status_code == 200) {
-    LOG ("Session destroyed");
-    return;
-  }
+  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
+  g_return_val_if_fail (collection, NULL);
 
-  parser = json_parser_new ();
-  json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
-  json = json_node_get_object (json_parser_get_root (parser));
+  crypto_keys = ephy_sync_service_get_secret (self, secrets[CRYPTO_KEYS]);
+  node = json_from_string (crypto_keys, &error);
+  g_assert (!error);
+  json = json_node_get_object (node);
+  collections = json_object_get_object_member (json, "collections");
+  array = json_object_has_member (collections, collection) ?
+          json_object_get_array_member (collections, collection) :
+          json_object_get_array_member (json, "default");
+  bundle = ephy_sync_crypto_key_bundle_from_array (array);
 
-  g_warning ("Failed to destroy session: errno: %ld, errmsg: %s",
-             json_object_get_int_member (json, "errno"),
-             json_object_get_string_member (json, "message"));
+  json_node_unref (node);
 
-  g_object_unref (parser);
+  return bundle;
+}
+
+static void
+ephy_sync_service_clear_storage_credentials (EphySyncService *self)
+{
+  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+
+  g_clear_pointer (&self->certificate, g_free);
+  g_clear_pointer (&self->storage_endpoint, g_free);
+  g_clear_pointer (&self->storage_credentials_id, g_free);
+  g_clear_pointer (&self->storage_credentials_key, g_free);
+  self->storage_credentials_expiry_time = 0;
 }
 
 static gboolean
 ephy_sync_service_storage_credentials_is_expired (EphySyncService *self)
 {
-  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), TRUE);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-  if (self->storage_credentials_id == NULL || self->storage_credentials_key == NULL)
+  if (!self->storage_credentials_id || !self->storage_credentials_key)
     return TRUE;
 
   if (self->storage_credentials_expiry_time == 0)
     return TRUE;
 
   /* Consider a 60 seconds safety interval. */
-  return self->storage_credentials_expiry_time < ephy_sync_utils_current_time_seconds () - 60;
+  return self->storage_credentials_expiry_time < g_get_real_time () / 1000000 - 60;
 }
 
 static void
@@ -174,17 +324,17 @@ ephy_sync_service_fxa_hawk_post_async (EphySyncService     *self,
                                        SoupSessionCallback  callback,
                                        gpointer             user_data)
 {
-  EphySyncCryptoHawkOptions *hoptions;
-  EphySyncCryptoHawkHeader *hheader;
+  SyncCryptoHawkOptions *hoptions;
+  SyncCryptoHawkHeader *hheader;
   SoupMessage *msg;
   char *url;
   const char *content_type = "application/json";
 
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (endpoint != NULL);
-  g_return_if_fail (id != NULL);
-  g_return_if_fail (key != NULL);
-  g_return_if_fail (request_body != NULL);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (endpoint);
+  g_assert (id);
+  g_assert (key);
+  g_assert (request_body);
 
   url = g_strdup_printf ("%s%s", MOZILLA_FXA_SERVER_URL, endpoint);
   msg = soup_message_new (SOUP_METHOD_POST, url);
@@ -203,73 +353,60 @@ ephy_sync_service_fxa_hawk_post_async (EphySyncService     *self,
   ephy_sync_crypto_hawk_header_free (hheader);
 }
 
-static guint
-ephy_sync_service_fxa_hawk_get_sync (EphySyncService  *self,
-                                     const char       *endpoint,
-                                     const char       *id,
-                                     guint8           *key,
-                                     gsize             key_length,
-                                     JsonNode        **node)
+static void
+ephy_sync_service_fxa_hawk_get_async (EphySyncService     *self,
+                                      const char          *endpoint,
+                                      const char          *id,
+                                      guint8              *key,
+                                      gsize                key_length,
+                                      SoupSessionCallback  callback,
+                                      gpointer             user_data)
 {
-  EphySyncCryptoHawkHeader *hheader;
+  SyncCryptoHawkHeader *hheader;
   SoupMessage *msg;
-  JsonParser *parser;
   char *url;
-  guint retval;
 
-  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), 0);
-  g_return_val_if_fail (endpoint != NULL, 0);
-  g_return_val_if_fail (id != NULL, 0);
-  g_return_val_if_fail (key != NULL, 0);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (endpoint);
+  g_assert (id);
+  g_assert (key);
 
   url = g_strdup_printf ("%s%s", MOZILLA_FXA_SERVER_URL, endpoint);
   msg = soup_message_new (SOUP_METHOD_GET, url);
   hheader = ephy_sync_crypto_compute_hawk_header (url, "GET", id, key, key_length, NULL);
   soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
-  soup_session_send_message (self->session, msg);
-
-  if (node != NULL) {
-    parser = json_parser_new ();
-    json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
-    *node = json_node_copy (json_parser_get_root (parser));
-    g_object_unref (parser);
-  }
-
-  retval = msg->status_code;
+  soup_session_queue_message (self->session, msg, callback, user_data);
 
   g_free (url);
-  g_object_unref (msg);
   ephy_sync_crypto_hawk_header_free (hheader);
-
-  return retval;
 }
 
 static void
-ephy_sync_service_send_storage_request (EphySyncService               *self,
-                                        StorageServerRequestAsyncData *data)
+ephy_sync_service_send_storage_request (EphySyncService         *self,
+                                        StorageRequestAsyncData *data)
 {
-  EphySyncCryptoHawkOptions *hoptions = NULL;
-  EphySyncCryptoHawkHeader *hheader;
+  SyncCryptoHawkOptions *hoptions = NULL;
+  SyncCryptoHawkHeader *hheader;
   SoupMessage *msg;
   char *url;
   char *if_modified_since = NULL;
   char *if_unmodified_since = NULL;
   const char *content_type = "application/json";
 
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (data != NULL);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (data);
 
   url = g_strdup_printf ("%s/%s", self->storage_endpoint, data->endpoint);
   msg = soup_message_new (data->method, url);
 
-  if (data->request_body != NULL) {
+  if (data->request_body) {
     hoptions = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
                                                   NULL, NULL, NULL, data->request_body, NULL);
     soup_message_set_request (msg, content_type, SOUP_MEMORY_COPY,
                               data->request_body, strlen (data->request_body));
   }
 
-  if (g_strcmp0 (data->method, SOUP_METHOD_POST) == 0)
+  if (!g_strcmp0 (data->method, SOUP_METHOD_POST))
     soup_message_headers_append (msg->request_headers, "content-type", content_type);
 
   if (data->modified_since >= 0) {
@@ -283,20 +420,20 @@ ephy_sync_service_send_storage_request (EphySyncService               *self,
   }
 
   hheader = ephy_sync_crypto_compute_hawk_header (url, data->method, self->storage_credentials_id,
-                                                 (guint8 *)self->storage_credentials_key,
-                                                 strlen (self->storage_credentials_key),
-                                                 hoptions);
+                                                  (guint8 *)self->storage_credentials_key,
+                                                  strlen (self->storage_credentials_key),
+                                                  hoptions);
   soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
   soup_session_queue_message (self->session, msg, data->callback, data->user_data);
 
-  if (hoptions != NULL)
+  if (hoptions)
     ephy_sync_crypto_hawk_options_free (hoptions);
 
   g_free (url);
   g_free (if_modified_since);
   g_free (if_unmodified_since);
   ephy_sync_crypto_hawk_header_free (hheader);
-  storage_server_request_async_data_free (data);
+  storage_request_async_data_free (data);
 }
 
 static gboolean
@@ -306,110 +443,227 @@ ephy_sync_service_certificate_is_valid (EphySyncService *self,
   JsonParser *parser;
   JsonObject *json;
   JsonObject *principal;
+  GError *error = NULL;
   SoupURI *uri;
   char **pieces;
   char *header;
   char *payload;
-  char *uid_email = NULL;
+  char *expected = NULL;
   const char *alg;
   const char *email;
   gsize len;
   gboolean retval = FALSE;
 
-  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), FALSE);
-  g_return_val_if_fail (certificate != NULL, FALSE);
-
-  /* Check if the certificate is something that we were expecting, i.e.
-   * if the algorithm and email fields match the expected values. */
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (ephy_sync_service_get_secret (self, secrets[UID]));
+  g_assert (certificate);
 
-  uri = soup_uri_new (MOZILLA_FXA_SERVER_URL);
   pieces = g_strsplit (certificate, ".", 0);
   header = (char *)ephy_sync_crypto_base64_urlsafe_decode (pieces[0], &len, TRUE);
   payload = (char *)ephy_sync_crypto_base64_urlsafe_decode (pieces[1], &len, TRUE);
-
   parser = json_parser_new ();
-  json_parser_load_from_data (parser, header, -1, NULL);
+
+  json_parser_load_from_data (parser, header, -1, &error);
+  if (error) {
+    g_warning ("Header is not a valid JSON: %s", error->message);
+    goto out;
+  }
   json = json_node_get_object (json_parser_get_root (parser));
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out;
+  }
   alg = json_object_get_string_member (json, "alg");
-
-  if (g_strcmp0 (alg, "RS256") != 0) {
-    g_warning ("Expected algorithm RS256, found %s. Giving up.", alg);
+  if (!alg) {
+    g_warning ("JSON object has missing or invalid 'alg' member");
+    goto out;
+  }
+  if (g_strcmp0 (alg, "RS256")) {
+    g_warning ("Expected algorithm RS256, found %s", alg);
+    goto out;
+  }
+  json_parser_load_from_data (parser, payload, -1, &error);
+  if (error) {
+    g_warning ("Payload is not a valid JSON: %s", error->message);
     goto out;
   }
-
-  json_parser_load_from_data (parser, payload, -1, NULL);
   json = json_node_get_object (json_parser_get_root (parser));
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out;
+  }
   principal = json_object_get_object_member (json, "principal");
+  if (!principal) {
+    g_warning ("JSON object has missing or invalid 'principal' member");
+    goto out;
+  }
   email = json_object_get_string_member (principal, "email");
-  uid_email = g_strdup_printf ("%s@%s", self->uid, soup_uri_get_host (uri));
-
-  if (g_strcmp0 (uid_email, email) != 0) {
-    g_warning ("Expected email %s, found %s. Giving up.", uid_email, email);
+  if (!email) {
+    g_warning ("JSON object has missing or invalid 'email' member");
     goto out;
   }
-
-  self->auth_at = json_object_get_int_member (json, "fxa-lastAuthAt");
-  retval = TRUE;
+  uri = soup_uri_new (MOZILLA_FXA_SERVER_URL);
+  expected = g_strdup_printf ("%s@%s",
+                              ephy_sync_service_get_secret (self, secrets[UID]),
+                              soup_uri_get_host (uri));
+  retval = g_strcmp0 (email, expected) == 0;
 
 out:
-  g_free (header);
+  g_free (expected);
+  g_object_unref (parser);
   g_free (payload);
-  g_free (uid_email);
+  g_free (header);
   g_strfreev (pieces);
-  g_object_unref (parser);
-  soup_uri_free (uri);
+  if (uri)
+    soup_uri_free (uri);
+  if (error)
+    g_error_free (error);
 
   return retval;
 }
 
 static void
-obtain_storage_credentials_response_cb (SoupSession *session,
-                                        SoupMessage *msg,
-                                        gpointer     user_data)
+destroy_session_cb (SoupSession *session,
+                    SoupMessage *msg,
+                    gpointer     user_data)
 {
-  StorageServerRequestAsyncData *data;
-  EphySyncService *service;
-  JsonParser *parser;
-  JsonObject *json;
-  JsonObject *errors;
-  JsonArray *array;
+  if (msg->status_code != 200)
+    g_warning ("Failed to destroy session. Status code: %u, response: %s",
+               msg->status_code, msg->response_body->data);
+  else
+    LOG ("Successfully destroyed session");
+}
 
-  data = (StorageServerRequestAsyncData *)user_data;
-  service = EPHY_SYNC_SERVICE (data->service);
+static void
+ephy_sync_service_destroy_session (EphySyncService *self,
+                                   const char      *sessionToken)
+{
+  SyncCryptoHawkOptions *hoptions;
+  SyncCryptoHawkHeader *hheader;
+  SoupMessage *msg;
+  guint8 *tokenID;
+  guint8 *reqHMACkey;
+  guint8 *requestKey;
+  char *tokenID_hex;
+  char *url;
+  const char *content_type = "application/json";
+  const char *request_body = "{}";
 
-  parser = json_parser_new ();
-  json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
-  json = json_node_get_object (json_parser_get_root (parser));
+  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  if (!sessionToken)
+    sessionToken = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
+  g_return_if_fail (sessionToken);
 
-  if (msg->status_code == 200) {
-    service->storage_endpoint = g_strdup (json_object_get_string_member (json, "api_endpoint"));
-    service->storage_credentials_id = g_strdup (json_object_get_string_member (json, "id"));
-    service->storage_credentials_key = g_strdup (json_object_get_string_member (json, "key"));
-    service->storage_credentials_expiry_time = json_object_get_int_member (json, "duration") +
-                                               ephy_sync_utils_current_time_seconds ();
-    ephy_sync_service_send_storage_request (service, data);
-  } else if (msg->status_code == 401) {
-    array = json_object_get_array_member (json, "errors");
-    errors = json_node_get_object (json_array_get_element (array, 0));
-    g_warning ("Failed to talk to the Token Server: %s: %s",
-               json_object_get_string_member (json, "status"),
-               json_object_get_string_member (errors, "description"));
-    storage_server_request_async_data_free (data);
-    service->locked = FALSE;
-  } else {
-    g_warning ("Failed to talk to the Token Server, status code %u. "
-               "See https://docs.services.mozilla.com/token/apis.html#error-responses";,
-               msg->status_code);
-    storage_server_request_async_data_free (data);
-    service->locked = FALSE;
+  url = g_strdup_printf ("%ssession/destroy", MOZILLA_FXA_SERVER_URL);
+  ephy_sync_crypto_process_session_token (ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]),
+                                          &tokenID, &reqHMACkey, &requestKey, TOKEN_LENGTH);
+  tokenID_hex = ephy_sync_crypto_encode_hex (tokenID, TOKEN_LENGTH);
+
+  msg = soup_message_new (SOUP_METHOD_POST, url);
+  soup_message_set_request (msg, content_type, SOUP_MEMORY_STATIC,
+                            request_body, strlen (request_body));
+  hoptions = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
+                                                NULL, NULL, NULL, request_body, NULL);
+  hheader = ephy_sync_crypto_compute_hawk_header (url, "POST", tokenID_hex,
+                                                  reqHMACkey, TOKEN_LENGTH,
+                                                  hoptions);
+  soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
+  soup_message_headers_append (msg->request_headers, "content-type", content_type);
+  soup_session_queue_message (self->session, msg, destroy_session_cb, NULL);
+
+  ephy_sync_crypto_hawk_options_free (hoptions);
+  ephy_sync_crypto_hawk_header_free (hheader);
+  g_free (tokenID_hex);
+  g_free (tokenID);
+  g_free (reqHMACkey);
+  g_free (requestKey);
+  g_free (url);
+}
+
+static void
+obtain_storage_credentials_cb (SoupSession *session,
+                               SoupMessage *msg,
+                               gpointer     user_data)
+{
+  EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+  JsonNode *node = NULL;
+  JsonObject *json = NULL;
+  GError *error = NULL;
+  const char *api_endpoint;
+  const char *id;
+  const char *key;
+  int duration;
+
+  if (msg->status_code != 200) {
+    g_warning ("Failed to obtain storage credentials. Status code: %u, response: %s",
+               msg->status_code, msg->response_body->data);
+    goto out;
+  }
+  node = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    g_warning ("Response is not a valid JSON: %s", error->message);
+    goto out;
+  }
+  json = json_node_get_object (node);
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out;
+  }
+  api_endpoint = json_object_get_string_member (json, "api_endpoint");
+  id = json_object_get_string_member (json, "id");
+  key = json_object_get_string_member (json, "key");
+  duration = json_object_get_int_member (json, "duration");
+  if (!api_endpoint || !id || !key || !duration) {
+    g_warning ("JSON object has missing or invalid members");
+    goto out;
   }
 
-  g_object_unref (parser);
+  self->storage_endpoint = g_strdup (api_endpoint);
+  self->storage_credentials_id = g_strdup (id);
+  self->storage_credentials_key = g_strdup (key);
+  self->storage_credentials_expiry_time = duration + g_get_real_time () / 1000000;
+
+  while (!g_queue_is_empty (self->storage_queue))
+    ephy_sync_service_send_storage_request (self, g_queue_pop_head (self->storage_queue));
+
+out:
+  self->locked = FALSE;
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
+}
+
+static char *
+get_audience (const char *url)
+{
+  SoupURI *uri;
+  const char *scheme;
+  const char *host;
+  char *audience;
+  char *port;
+
+  g_return_val_if_fail (url, NULL);
+
+  uri = soup_uri_new (url);
+  scheme = soup_uri_get_scheme (uri);
+  host = soup_uri_get_host (uri);
+  /* soup_uri_get_port returns the default port if URI does not have any port. */
+  port = g_strdup_printf (":%u", soup_uri_get_port (uri));
+
+  if (g_strstr_len (url, -1, port))
+    audience = g_strdup_printf ("%s://%s%s", scheme, host, port);
+  else
+    audience = g_strdup_printf ("%s://%s", scheme, host);
+
+  g_free (port);
+  soup_uri_free (uri);
+
+  return audience;
 }
 
 static void
-ephy_sync_service_obtain_storage_credentials (EphySyncService *self,
-                                              gpointer         user_data)
+ephy_sync_service_obtain_storage_credentials (EphySyncService *self)
 {
   SoupMessage *msg;
   guint8 *kB;
@@ -419,17 +673,16 @@ ephy_sync_service_obtain_storage_credentials (EphySyncService *self,
   char *assertion;
   char *authorization;
 
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (self->certificate != NULL);
-  g_return_if_fail (self->keypair != NULL);
-
-  audience = ephy_sync_utils_make_audience (MOZILLA_TOKEN_SERVER_URL);
-  assertion = ephy_sync_crypto_create_assertion (self->certificate, audience, 300, self->keypair);
-  g_return_if_fail (assertion != NULL);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (self->certificate);
+  g_assert (self->keypair);
 
-  kB = ephy_sync_crypto_decode_hex (self->kB);
-  hashed_kB = g_compute_checksum_for_data (G_CHECKSUM_SHA256, kB, EPHY_SYNC_TOKEN_LENGTH);
-  client_state = g_strndup (hashed_kB, EPHY_SYNC_TOKEN_LENGTH);
+  audience = get_audience (MOZILLA_TOKEN_SERVER_URL);
+  assertion = ephy_sync_crypto_create_assertion (self->certificate, audience,
+                                                 ASSERTION_DURATION, self->keypair);
+  kB = ephy_sync_crypto_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+  hashed_kB = g_compute_checksum_for_data (G_CHECKSUM_SHA256, kB, TOKEN_LENGTH);
+  client_state = g_strndup (hashed_kB, TOKEN_LENGTH);
   authorization = g_strdup_printf ("BrowserID %s", assertion);
 
   msg = soup_message_new (SOUP_METHOD_GET, MOZILLA_TOKEN_SERVER_URL);
@@ -437,7 +690,7 @@ ephy_sync_service_obtain_storage_credentials (EphySyncService *self,
    * recognize accounts that were previously used to sync Firefox data too. */
   soup_message_headers_append (msg->request_headers, "X-Client-State", client_state);
   soup_message_headers_append (msg->request_headers, "authorization", authorization);
-  soup_session_queue_message (self->session, msg, obtain_storage_credentials_response_cb, user_data);
+  soup_session_queue_message (self->session, msg, obtain_storage_credentials_cb, self);
 
   g_free (kB);
   g_free (hashed_kB);
@@ -448,138 +701,575 @@ ephy_sync_service_obtain_storage_credentials (EphySyncService *self,
 }
 
 static void
-obtain_signed_certificate_response_cb (SoupSession *session,
-                                       SoupMessage *msg,
-                                       gpointer     user_data)
+obtain_signed_certificate_cb (SoupSession *session,
+                              SoupMessage *msg,
+                              gpointer     user_data)
 {
-  StorageServerRequestAsyncData *data;
-  EphySyncService *service;
-  JsonParser *parser;
-  JsonObject *json;
-  const char *certificate;
-
-  data = (StorageServerRequestAsyncData *)user_data;
-  service = EPHY_SYNC_SERVICE (data->service);
+  EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+  JsonNode *node = NULL;
+  JsonObject *json = NULL;
+  GError *error = NULL;
+  const char *suggestion = NULL;
+  const char *message = NULL;
+  const char *certificate = NULL;
+
+  node = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    g_warning ("Response is not a valid JSON: %s", error->message);
+    goto out_error;
+  }
+  json = json_node_get_object (node);
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out_error;
+  }
 
-  parser = json_parser_new ();
-  json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
-  json = json_node_get_object (json_parser_get_root (parser));
+  if (msg->status_code == 200) {
+    certificate = json_object_get_string_member (json, "cert");
+    if (!certificate) {
+      g_warning ("JSON object has missing or invalid 'cert' member");
+      goto out_error;
+    }
+    if (!ephy_sync_service_certificate_is_valid (self, certificate)) {
+      g_warning ("Invalid certificate");
+      ephy_sync_crypto_rsa_key_pair_free (self->keypair);
+      goto out_error;
+    }
+    self->certificate = g_strdup (certificate);
+    ephy_sync_service_obtain_storage_credentials (self);
+    goto out_no_error;
+  }
 
   /* Since a new Firefox Account password implies new tokens, this will fail
    * with an error code 110 (Invalid authentication token in request signature)
    * if the user has changed his password since the last time he signed in.
    * When this happens, notify the user to sign in with the new password. */
-  if (msg->status_code == 401 && json_object_get_int_member (json, "errno") == 110) {
-    char *error = g_strdup_printf (_("The password of your Firefox account %s "
-                                     "seems to have been changed."),
-                                   ephy_sync_service_get_user_email (service));
-    const char *suggestion = _("Please visit Preferences and sign in with "
-                               "the new password to continue the sync process.");
-
-    ephy_notification_show (ephy_notification_new (error, suggestion));
-
-    storage_server_request_async_data_free (data);
-    g_free (error);
-    service->locked = FALSE;
-    goto out;
-  }
-
-  if (msg->status_code != 200) {
-    g_warning ("FxA server errno: %ld, errmsg: %s",
-               json_object_get_int_member (json, "errno"),
-               json_object_get_string_member (json, "message"));
-    storage_server_request_async_data_free (data);
-    service->locked = FALSE;
-    goto out;
-  }
-
-  certificate = json_object_get_string_member (json, "cert");
-
-  if (ephy_sync_service_certificate_is_valid (service, certificate) == FALSE) {
-    ephy_sync_crypto_rsa_key_pair_free (service->keypair);
-    storage_server_request_async_data_free (data);
-    service->locked = FALSE;
-    goto out;
+  if (json_object_get_int_member (json, "errno") == 110) {
+    message = _("The password of your Firefox account seems to have been changed.");
+    suggestion = _("Please visit Preferences and sign in with the new password to continue syncing.");
   }
 
-  service->certificate = g_strdup (certificate);
-
-  /* See the comment in ephy_sync_service_send_storage_message(). */
-  ephy_sync_service_obtain_storage_credentials (service, user_data);
-
-out:
-  g_object_unref (parser);
+  g_warning ("Failed to sign certificate. Status code: %u, response: %s",
+             msg->status_code, msg->response_body->data);
+
+out_error:
+  message = message ? message : _("Something went wrong while syncing.");
+  suggestion = suggestion ? suggestion : _("Please visit Preferences and sign in again.");
+  ephy_notification_show (ephy_notification_new (message, suggestion));
+  self->locked = FALSE;
+out_no_error:
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
 }
 
 static void
-ephy_sync_service_obtain_signed_certificate (EphySyncService *self,
-                                             gpointer         user_data)
+ephy_sync_service_obtain_signed_certificate (EphySyncService *self)
 {
+  JsonNode *node;
+  JsonObject *object_key;
+  JsonObject *object_body;
   guint8 *tokenID;
   guint8 *reqHMACkey;
   guint8 *requestKey;
   char *tokenID_hex;
-  char *public_key_json;
   char *request_body;
   char *n;
   char *e;
 
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (self->sessionToken != NULL);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-  /* Generate a new RSA key pair that is going to be used to sign the new certificate. */
-  if (self->keypair != NULL)
+  /* Generate a new RSA key pair to sign the new certificate. */
+  if (self->keypair)
     ephy_sync_crypto_rsa_key_pair_free (self->keypair);
-
   self->keypair = ephy_sync_crypto_generate_rsa_key_pair ();
-  g_return_if_fail (self->keypair != NULL);
 
   /* Derive tokenID, reqHMACkey and requestKey from the sessionToken. */
-  ephy_sync_crypto_process_session_token (self->sessionToken, &tokenID, &reqHMACkey, &requestKey);
-  tokenID_hex = ephy_sync_crypto_encode_hex (tokenID, 0);
+  ephy_sync_crypto_process_session_token (ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]),
+                                          &tokenID, &reqHMACkey, &requestKey, TOKEN_LENGTH);
+  tokenID_hex = ephy_sync_crypto_encode_hex (tokenID, TOKEN_LENGTH);
 
   n = mpz_get_str (NULL, 10, self->keypair->public.n);
   e = mpz_get_str (NULL, 10, self->keypair->public.e);
-  public_key_json = ephy_sync_utils_build_json_string ("algorithm", "RS", "n", n, "e", e, NULL);
-  /* Duration is the lifetime of the certificate in milliseconds. The FxA server
-   * limits the duration to 24 hours. For our purposes, a duration of 30 minutes
-   * will suffice. */
-  request_body = g_strdup_printf ("{\"publicKey\": %s, \"duration\": %d}",
-                                  public_key_json, 30 * 60 * 1000);
+  node = json_node_new (JSON_NODE_OBJECT);
+  object_body = json_object_new ();
+  json_object_set_int_member (object_body, "duration", CERTIFICATE_DURATION);
+  object_key = json_object_new ();
+  json_object_set_string_member (object_key, "algorithm", "RS");
+  json_object_set_string_member (object_key, "n", n);
+  json_object_set_string_member (object_key, "e", e);
+  json_object_set_object_member (object_body, "publicKey", object_key);
+  json_node_set_object (node, object_body);
+  request_body = json_to_string (node, FALSE);
   ephy_sync_service_fxa_hawk_post_async (self, "certificate/sign", tokenID_hex,
-                                         reqHMACkey, EPHY_SYNC_TOKEN_LENGTH, request_body,
-                                         obtain_signed_certificate_response_cb, user_data);
+                                         reqHMACkey, TOKEN_LENGTH, request_body,
+                                         obtain_signed_certificate_cb, self);
 
-  g_free (tokenID);
-  g_free (reqHMACkey);
-  g_free (requestKey);
-  g_free (tokenID_hex);
-  g_free (public_key_json);
   g_free (request_body);
-  g_free (n);
+  json_object_unref (object_body);
+  json_node_unref (node);
   g_free (e);
+  g_free (n);
+  g_free (tokenID_hex);
+  g_free (requestKey);
+  g_free (reqHMACkey);
+  g_free (tokenID);
 }
 
 static void
-ephy_sync_service_issue_storage_request (EphySyncService               *self,
-                                         StorageServerRequestAsyncData *data)
+ephy_sync_service_queue_storage_request (EphySyncService     *self,
+                                         const char          *endpoint,
+                                         const char          *method,
+                                         const char          *request_body,
+                                         double               modified_since,
+                                         double               unmodified_since,
+                                         SoupSessionCallback  callback,
+                                         gpointer             user_data)
 {
+  StorageRequestAsyncData *data;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (endpoint);
+  g_assert (method);
+
+  data = storage_request_async_data_new (endpoint, method, request_body,
+                                         modified_since, unmodified_since,
+                                         callback, user_data);
+
+  /* If the storage credentials are valid, then directly send the request.
+   * Otherwise, the request will remain queued and scheduled to be sent when
+   * the new credentials are obtained. */
+  if (!ephy_sync_service_storage_credentials_is_expired (self)) {
+    ephy_sync_service_send_storage_request (self, data);
+  } else {
+    g_queue_push_tail (self->storage_queue, data);
+    if (!self->locked) {
+      /* Mark as locked so other requests won't lead to conflicts while obtaining
+       * new storage credentials. */
+      self->locked = TRUE;
+      ephy_sync_service_clear_storage_credentials (self);
+      ephy_sync_service_obtain_signed_certificate (self);
+    }
+  }
+}
+
+static void
+download_synchronizable_cb (SoupSession *session,
+                            SoupMessage *msg,
+                            gpointer     user_data)
+{
+  SyncAsyncData *data = (SyncAsyncData *)user_data;
+  EphySynchronizable *synchronizable;
+  SyncCryptoKeyBundle *bundle = NULL;
+  JsonNode *node = NULL;
+  GError *error = NULL;
+  GType type;
+  const char *collection;
+  gboolean is_deleted;
+
+  if (msg->status_code != 200) {
+    g_warning ("Failed to download object. Status code: %u, response: %s",
+               msg->status_code, msg->response_body->data);
+    goto out;
+  }
+  node = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    g_warning ("Response is not a valid JSON");
+    goto out;
+  }
+  type = ephy_synchronizable_manager_get_synchronizable_type (data->manager);
+  collection = ephy_synchronizable_manager_get_collection_name (data->manager);
+  bundle = ephy_sync_service_get_key_bundle (data->service, collection);
+  synchronizable = EPHY_SYNCHRONIZABLE (ephy_synchronizable_from_bso (node, type, bundle, &is_deleted));
+  if (!synchronizable) {
+    g_warning ("Failed to create synchronizable object from BSO");
+    goto out;
+  }
+
+  /* Delete the local object and add the remote one if it is not marked as deleted. */
+  ephy_synchronizable_manager_remove (data->manager, data->synchronizable);
+  if (!is_deleted) {
+    ephy_synchronizable_set_is_uploaded (synchronizable, TRUE);
+    ephy_synchronizable_manager_add (data->manager, synchronizable);
+    LOG ("Successfully downloaded from server");
+  }
+
+  g_object_unref (synchronizable);
+out:
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
+  if (bundle)
+    ephy_sync_crypto_key_bundle_free (bundle);
+  sync_async_data_free (data);
+}
+
+static void
+ephy_sync_service_download_synchronizable (EphySyncService           *self,
+                                           EphySynchronizableManager *manager,
+                                           EphySynchronizable        *synchronizable)
+{
+  SyncAsyncData *data;
+  char *endpoint;
+  const char *collection;
+  const char *id;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+  id = ephy_synchronizable_get_id (synchronizable);
+  collection = ephy_synchronizable_manager_get_collection_name (manager);
+  endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+  data = sync_async_data_new (self, manager, synchronizable);
+
+  LOG ("Downloading object with id %s...", id);
+  ephy_sync_service_queue_storage_request (self, endpoint,
+                                           SOUP_METHOD_GET, NULL, -1, -1,
+                                           download_synchronizable_cb, data);
+
+  g_free (endpoint);
+}
+
+static void
+upload_synchronizable_cb (SoupSession *session,
+                          SoupMessage *msg,
+                          gpointer     user_data)
+{
+  SyncAsyncData *data = (SyncAsyncData *)user_data;
+  double time_modified;
+
+  /* Code 412 means that there is a more recent version of the object
+   * on the server. Download it. */
+  if (msg->status_code == 412) {
+    LOG ("Found a newer version of the object on the server, downloading it...");
+    ephy_sync_service_download_synchronizable (data->service, data->manager, data->synchronizable);
+  } else if (msg->status_code == 200) {
+    LOG ("Successfully uploaded to server");
+    time_modified = g_ascii_strtod (msg->response_body->data, NULL);
+    /* FIXME: Make sure the synchronizable manager commits this change to file/database. */
+    ephy_synchronizable_set_time_modified (data->synchronizable, time_modified);
+    ephy_synchronizable_set_is_uploaded (data->synchronizable, TRUE);
+  } else {
+    g_warning ("Failed to upload object. Status code: %u, response: %s",
+               msg->status_code, msg->response_body->data);
+  }
+
+  sync_async_data_free (data);
+}
+
+static void
+ephy_sync_service_upload_synchronizable (EphySyncService           *self,
+                                         EphySynchronizableManager *manager,
+                                         EphySynchronizable        *synchronizable)
+{
+  SyncCryptoKeyBundle *bundle;
+  SyncAsyncData *data;
+  JsonNode *bso;
+  char *endpoint;
+  char *body;
+  const char *collection;
+  const char *id;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+  collection = ephy_synchronizable_manager_get_collection_name (manager);
+  bundle = ephy_sync_service_get_key_bundle (self, collection);
+  bso = ephy_synchronizable_to_bso (synchronizable, bundle);
+  id = ephy_synchronizable_get_id (synchronizable);
+  endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+  data = sync_async_data_new (self, manager, synchronizable);
+  body = json_to_string (bso, FALSE);
+
+  LOG ("Uploading object with id %s...", id);
+  ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_PUT, body, -1,
+                                           ephy_synchronizable_get_time_modified (synchronizable),
+                                           upload_synchronizable_cb, data);
+
+  g_free (body);
+  g_free (endpoint);
+  json_node_unref (bso);
+  ephy_sync_crypto_key_bundle_free (bundle);
+}
+
+static void
+sync_collection_cb (SoupSession *session,
+                    SoupMessage *msg,
+                    gpointer     user_data)
+{
+  SyncCollectionAsyncData *data = (SyncCollectionAsyncData *)user_data;
+  EphySynchronizable *remote;
+  SyncCryptoKeyBundle *bundle;
+  JsonNode *node = NULL;
+  JsonArray *array = NULL;
+  GError *error = NULL;
+  GList *remotes_updated = NULL;
+  GList *remotes_deleted = NULL;
+  GList *to_upload = NULL;
+  GType type;
+  const char *collection;
+  const char *timestamp;
+  gboolean is_deleted;
+
+  collection = ephy_synchronizable_manager_get_collection_name (data->manager);
+
+  /* Code 304 means that the collection has not been modified. */
+  if (msg->status_code == 304) {
+    LOG ("There are no new remote objects");
+    goto merge_remotes;
+  }
+  if (msg->status_code != 200) {
+    g_warning ("Failed to get records in collection %s. Status code: %u, response: %s",
+               collection, msg->status_code, msg->response_body->data);
+    goto out;
+  }
+  node = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    g_warning ("Response is not a valid JSON: %s", error->message);
+    goto out;
+  }
+  array = json_node_get_array (node);
+  if (!array) {
+    g_warning ("JSON node does not hold an array");
+    goto out;
+  }
+
+  type = ephy_synchronizable_manager_get_synchronizable_type (data->manager);
+  bundle = ephy_sync_service_get_key_bundle (data->service, collection);
+  for (guint i = 0; i < json_array_get_length (array); i++) {
+    remote = EPHY_SYNCHRONIZABLE (ephy_synchronizable_from_bso (json_array_get_element (array, i),
+                                                                type, bundle, &is_deleted));
+    if (!remote) {
+      g_warning ("Failed to create synchronizable object from BSO, skipping...");
+      continue;
+    }
+    if (is_deleted)
+      remotes_deleted = g_list_prepend (remotes_deleted, remote);
+    else
+      remotes_updated = g_list_prepend (remotes_updated, remote);
+  }
+
+merge_remotes:
+  to_upload = ephy_synchronizable_manager_merge_remotes (data->manager, data->is_initial,
+                                                         remotes_deleted, remotes_updated);
+  for (GList *l = to_upload; l && l->data; l = l->next)
+    ephy_sync_service_upload_synchronizable (data->service, data->manager, EPHY_SYNCHRONIZABLE (l->data));
+
+  /* Update sync time. */
+  timestamp = soup_message_headers_get_one (msg->response_headers, "X-Weave-Timestamp");
+  ephy_synchronizable_manager_set_sync_time (data->manager, g_ascii_strtod (timestamp, NULL));
+  ephy_synchronizable_manager_set_is_initial_sync (data->manager, FALSE);
+
+out:
+  if (data->is_last)
+    g_signal_emit (data->service, signals[SYNC_FINISHED], 0);
+
+  if (to_upload)
+    g_list_free_full (to_upload, g_object_unref);
+  if (remotes_updated)
+    g_list_free_full (remotes_updated, g_object_unref);
+  if (remotes_deleted)
+    g_list_free_full (remotes_deleted, g_object_unref);
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
+  sync_collection_async_data_free (data);
+}
+
+static void
+ephy_sync_service_sync_collection (EphySyncService           *self,
+                                   EphySynchronizableManager *manager,
+                                   gboolean                   is_last)
+{
+  SyncCollectionAsyncData *data;
+  const char *collection;
+  char *endpoint;
+  gboolean is_initial;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+
+  collection = ephy_synchronizable_manager_get_collection_name (manager);
+  endpoint = g_strdup_printf ("storage/%s?full=true", collection);
+  is_initial = ephy_synchronizable_manager_is_initial_sync (manager);
+  data = sync_collection_async_data_new (self, manager, is_initial, is_last);
+
+  LOG ("Syncing %s collection%s...", collection, is_initial ? " first time" : "");
+  ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_GET, NULL,
+                                           is_initial ? -1 : ephy_synchronizable_manager_get_sync_time 
(manager),
+                                           -1, sync_collection_cb, data);
+
+  g_free (endpoint);
+}
+
+static gboolean
+ephy_sync_service_sync (gpointer user_data)
+{
+  EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+  GHashTableIter iter;
+  gpointer key;
+  gpointer value;
+  guint index = 0;
+  guint num_managers = g_hash_table_size (self->managers);
+
+  if (num_managers == 0) {
+    g_signal_emit (self, signals[SYNC_FINISHED], 0);
+    return G_SOURCE_CONTINUE;
+  }
+
+  g_hash_table_iter_init (&iter, self->managers);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    ephy_sync_service_sync_collection (self,
+                                       EPHY_SYNCHRONIZABLE_MANAGER (value),
+                                       ++index == num_managers);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+ephy_sync_service_unregister_client_id (EphySyncService *self)
+{
+  char *client_id;
+  char *endpoint;
+
   g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (data != NULL);
-
-  if (ephy_sync_service_storage_credentials_is_expired (self) == TRUE) {
-    ephy_sync_service_clear_storage_credentials (self);
-
-    /* The only purpose of certificates is to obtain a signed BrowserID that is
-     * needed to talk to the Token Server. From the Token Server we will obtain
-     * the credentials needed to talk to the Storage Server. Since both
-     * ephy_sync_service_obtain_signed_certificate() and
-     * ephy_sync_service_obtain_storage_credentials() complete asynchronously,
-     * we need to entrust them the task of sending the request to the Storage
-     * Server. */
-    ephy_sync_service_obtain_signed_certificate (self, data);
+
+  client_id = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_CLIENT_ID);
+  endpoint = g_strdup_printf ("storage/clients/%s", client_id);
+
+  ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_DELETE,
+                                           NULL, -1, -1, NULL, NULL);
+  g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_CLIENT_ID, "");
+
+  g_free (endpoint);
+  g_free (client_id);
+}
+
+static void
+ephy_sync_service_register_client_id (EphySyncService *self)
+{
+  SyncCryptoKeyBundle *bundle;
+  JsonNode *node;
+  JsonObject *record;
+  JsonObject *payload;
+  JsonArray *array;
+  char *client_id;
+  char *name;
+  char *protocol;
+  char *payload_clear;
+  char *payload_cipher;
+  char *body;
+  char *endpoint;
+
+  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  record = json_object_new ();
+  payload = json_object_new ();
+  array = json_array_new ();
+  protocol = g_strdup_printf ("1.%d", STORAGE_VERSION);
+  json_array_add_string_element (array, protocol);
+  json_object_set_array_member (payload, "protocols", array);
+  client_id = ephy_sync_crypto_get_random_sync_id ();
+  json_object_set_string_member (payload, "id", client_id);
+  name = g_strdup_printf ("%s on Epiphany", client_id);
+  json_object_set_string_member (payload, "name", name);
+  json_object_set_string_member (payload, "type", "desktop");
+  json_object_set_string_member (payload, "os", "Linux");
+  json_object_set_string_member (payload, "application", "Epiphany");
+  json_object_set_string_member (payload, "fxaDeviceId",
+                                 ephy_sync_service_get_secret (self, secrets[UID]));
+  json_node_set_object (node, payload);
+  payload_clear = json_to_string (node, FALSE);
+  bundle = ephy_sync_service_get_key_bundle (self, "clients");
+  payload_cipher = ephy_sync_crypto_encrypt_record (payload_clear, bundle);
+  json_object_set_string_member (record, "id", client_id);
+  json_object_set_string_member (record, "payload", payload_cipher);
+  json_node_set_object (node, record);
+  body = json_to_string (node, FALSE);
+  endpoint = g_strdup_printf ("storage/clients/%s", client_id);
+
+  ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_PUT,
+                                           body, -1, -1, NULL, NULL);
+  g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_CLIENT_ID, client_id);
+
+  g_free (endpoint);
+  g_free (body);
+  g_free (payload_cipher);
+  ephy_sync_crypto_key_bundle_free (bundle);
+  g_free (payload_clear);
+  g_free (name);
+  g_free (client_id);
+  g_free (protocol);
+  json_object_unref(payload);
+  json_object_unref(record);
+  json_node_unref (node);
+}
+
+static void
+ephy_sync_service_stop_periodical_sync (EphySyncService *self)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+  if (self->source_id != 0) {
+    g_source_remove (self->source_id);
+    self->source_id = 0;
+  }
+}
+
+static void
+ephy_sync_service_schedule_periodical_sync (EphySyncService *self)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+  self->source_id = g_timeout_add_seconds (g_settings_get_uint (EPHY_SETTINGS_SYNC,
+                                                                EPHY_PREFS_SYNC_FREQUENCY) * 60,
+                                           ephy_sync_service_sync,
+                                           self);
+  LOG ("Scheduled new sync with frequency %u mins",
+       g_settings_get_uint (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_FREQUENCY));
+}
+
+static void
+sync_frequency_changed_cb (EphySyncService *self)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+  ephy_sync_service_stop_periodical_sync (self);
+  ephy_sync_service_schedule_periodical_sync (self);
+}
+
+static void
+sync_secrets_store_finished_cb (EphySyncService *self,
+                                GError          *error)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+  if (error) {
+    ephy_sync_service_destroy_session (self, NULL);
+    g_clear_pointer (&self->user_email, g_free);
+    g_hash_table_remove_all (self->secrets);
   } else {
-    ephy_sync_service_send_storage_request (self, data);
+    ephy_sync_service_register_client_id (self);
+  }
+}
+
+static void
+sync_secrets_load_finished_cb (EphySyncService *self,
+                               GError          *error)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+  if (error) {
+    const char *text = _("Please visit Preferences and sign in again to continue syncing.");
+    ephy_notification_show (ephy_notification_new (error->message, text));
+  } else {
+    ephy_sync_service_start_periodical_sync (self);
   }
 }
 
@@ -588,10 +1278,12 @@ ephy_sync_service_finalize (GObject *object)
 {
   EphySyncService *self = EPHY_SYNC_SERVICE (object);
 
-  if (self->keypair != NULL)
+  if (self->keypair)
     ephy_sync_crypto_rsa_key_pair_free (self->keypair);
 
-  g_queue_free_full (self->storage_queue, (GDestroyNotify) storage_server_request_async_data_free);
+  g_queue_free_full (self->storage_queue, (GDestroyNotify)storage_request_async_data_free);
+  g_hash_table_destroy (self->secrets);
+  g_hash_table_destroy (self->managers);
 
   G_OBJECT_CLASS (ephy_sync_service_parent_class)->finalize (object);
 }
@@ -601,9 +1293,10 @@ ephy_sync_service_dispose (GObject *object)
 {
   EphySyncService *self = EPHY_SYNC_SERVICE (object);
 
-  ephy_sync_service_stop_periodical_sync (self);
+  if (ephy_sync_service_is_signed_in (self))
+    ephy_sync_service_stop_periodical_sync (self);
+
   ephy_sync_service_clear_storage_credentials (self);
-  ephy_sync_service_clear_tokens (self);
   g_clear_pointer (&self->user_email, g_free);
   g_clear_object (&self->session);
 
@@ -619,7 +1312,7 @@ ephy_sync_service_class_init (EphySyncServiceClass *klass)
   object_class->dispose = ephy_sync_service_dispose;
 
   signals[STORE_FINISHED] =
-    g_signal_new ("sync-tokens-store-finished",
+    g_signal_new ("sync-secrets-store-finished",
                   EPHY_TYPE_SYNC_SERVICE,
                   G_SIGNAL_RUN_LAST,
                   0, NULL, NULL, NULL,
@@ -627,12 +1320,34 @@ ephy_sync_service_class_init (EphySyncServiceClass *klass)
                   G_TYPE_ERROR);
 
   signals[LOAD_FINISHED] =
-    g_signal_new ("sync-tokens-load-finished",
+    g_signal_new ("sync-secrets-load-finished",
                   EPHY_TYPE_SYNC_SERVICE,
                   G_SIGNAL_RUN_LAST,
                   0, NULL, NULL, NULL,
                   G_TYPE_NONE, 1,
                   G_TYPE_ERROR);
+
+  signals[SIGN_IN_ERROR] =
+    g_signal_new ("sync-sign-in-error",
+                  EPHY_TYPE_SYNC_SERVICE,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 1,
+                  G_TYPE_STRING);
+
+  signals[SYNC_FREQUENCY_CHANGED] =
+    g_signal_new ("sync-frequency-changed",
+                  EPHY_TYPE_SYNC_SERVICE,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+
+  signals[SYNC_FINISHED] =
+    g_signal_new ("sync-finished",
+                  EPHY_TYPE_SYNC_SERVICE,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
 }
 
 static void
@@ -644,18 +1359,30 @@ ephy_sync_service_init (EphySyncService *self)
 
   self->session = soup_session_new ();
   self->storage_queue = g_queue_new ();
+  self->secrets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+  self->managers = g_hash_table_new (g_direct_hash, g_direct_equal);
 
   settings = ephy_embed_prefs_get_settings ();
   user_agent = webkit_settings_get_user_agent (settings);
   g_object_set (self->session, "user-agent", user_agent, NULL);
 
-  email = g_settings_get_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER);
+  email = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER);
 
-  if (g_strcmp0 (email, "") != 0) {
-    ephy_sync_service_set_user_email (self, email);
-    ephy_sync_secret_load_tokens (self);
+  if (g_strcmp0 (email, "")) {
+    self->user_email = g_strdup (email);
+    ephy_sync_secret_load_secrets (self);
   }
 
+  g_signal_connect (self, "sync-secrets-store-finished",
+                    G_CALLBACK (sync_secrets_store_finished_cb),
+                    NULL);
+  g_signal_connect (self, "sync-secrets-load-finished",
+                    G_CALLBACK (sync_secrets_load_finished_cb),
+                    NULL);
+  g_signal_connect (self, "sync-frequency-changed",
+                    G_CALLBACK (sync_frequency_changed_cb),
+                    NULL);
+
   g_free (email);
 }
 
@@ -673,7 +1400,7 @@ ephy_sync_service_is_signed_in (EphySyncService *self)
   return self->user_email != NULL;
 }
 
-char *
+const char *
 ephy_sync_service_get_user_email (EphySyncService *self)
 {
   g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
@@ -681,776 +1408,590 @@ ephy_sync_service_get_user_email (EphySyncService *self)
   return self->user_email;
 }
 
-void
-ephy_sync_service_set_user_email (EphySyncService *self,
-                                  const char      *email)
+GHashTable *
+ephy_sync_service_get_secrets (EphySyncService *self)
 {
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-
-  g_free (self->user_email);
-  self->user_email = g_strdup (email);
-}
-
-double
-ephy_sync_service_get_sync_time (EphySyncService *self)
-{
-  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), 0);
-
-  if (self->sync_time != 0)
-    return self->sync_time;
+  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
 
-  self->sync_time = g_settings_get_double (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_TIME);
-  return self->sync_time;
+  return self->secrets;
 }
 
 
-void
-ephy_sync_service_set_sync_time (EphySyncService *self,
-                                 double           time)
-{
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-
-  self->sync_time = time;
-  g_settings_set_double (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_TIME, time);
-}
-
-char *
-ephy_sync_service_get_token (EphySyncService   *self,
-                             EphySyncTokenType  type)
+const char *
+ephy_sync_service_get_secret (EphySyncService *self,
+                              const char      *name)
 {
   g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
+  g_return_val_if_fail (name, NULL);
 
-  switch (type) {
-    case TOKEN_UID:
-      return self->uid;
-    case TOKEN_SESSIONTOKEN:
-      return self->sessionToken;
-    case TOKEN_KEYFETCHTOKEN:
-      return self->keyFetchToken;
-    case TOKEN_UNWRAPBKEY:
-      return self->unwrapBKey;
-    case TOKEN_KA:
-      return self->kA;
-    case TOKEN_KB:
-      return self->kB;
-    default:
-      g_assert_not_reached ();
-  }
+  return g_hash_table_lookup (self->secrets, name);
 }
 
 void
-ephy_sync_service_set_token (EphySyncService   *self,
-                             const char        *value,
-                             EphySyncTokenType  type)
+ephy_sync_service_set_secret (EphySyncService *self,
+                              const char      *name,
+                              const char      *value)
 {
   g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (value != NULL);
-
-  switch (type) {
-    case TOKEN_UID:
-      g_free (self->uid);
-      self->uid = g_strdup (value);
-      break;
-    case TOKEN_SESSIONTOKEN:
-      g_free (self->sessionToken);
-      self->sessionToken = g_strdup (value);
-      break;
-    case TOKEN_KEYFETCHTOKEN:
-      g_free (self->keyFetchToken);
-      self->keyFetchToken = g_strdup (value);
-      break;
-    case TOKEN_UNWRAPBKEY:
-      g_free (self->unwrapBKey);
-      self->unwrapBKey = g_strdup (value);
-      break;
-    case TOKEN_KA:
-      g_free (self->kA);
-      self->kA = g_strdup (value);
-      break;
-    case TOKEN_KB:
-      g_free (self->kB);
-      self->kB = g_strdup (value);
-      break;
-    default:
-      g_assert_not_reached ();
-  }
-}
+  g_return_if_fail (name);
+  g_return_if_fail (value);
 
-void
-ephy_sync_service_clear_storage_credentials (EphySyncService *self)
-{
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-
-  g_clear_pointer (&self->certificate, g_free);
-  g_clear_pointer (&self->storage_endpoint, g_free);
-  g_clear_pointer (&self->storage_credentials_id, g_free);
-  g_clear_pointer (&self->storage_credentials_key, g_free);
-  self->storage_credentials_expiry_time = 0;
+  g_hash_table_replace (self->secrets, g_strdup (name), g_strdup (value));
 }
 
 void
-ephy_sync_service_clear_tokens (EphySyncService *self)
+ephy_sync_service_register_manager (EphySyncService           *self,
+                                    EphySynchronizableManager *manager)
 {
   g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
 
-  g_clear_pointer (&self->uid, g_free);
-  g_clear_pointer (&self->sessionToken, g_free);
-  g_clear_pointer (&self->keyFetchToken, g_free);
-  g_clear_pointer (&self->unwrapBKey, g_free);
-  g_clear_pointer (&self->kA, g_free);
-  g_clear_pointer (&self->kB, g_free);
+  g_hash_table_add (self->managers, manager);
 }
 
 void
-ephy_sync_service_destroy_session (EphySyncService *self,
-                                   const char      *sessionToken)
+ephy_sync_service_unregister_manager (EphySyncService           *self,
+                                      EphySynchronizableManager *manager)
 {
-  EphySyncCryptoHawkOptions *hoptions;
-  EphySyncCryptoHawkHeader *hheader;
-  SoupMessage *msg;
-  guint8 *tokenID;
-  guint8 *reqHMACkey;
-  guint8 *requestKey;
-  char *tokenID_hex;
-  char *url;
-  const char *content_type = "application/json";
-  const char *endpoint = "session/destroy";
-  const char *request_body = "{}";
-
   g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
 
-  if (sessionToken == NULL)
-    sessionToken = ephy_sync_service_get_token (self, TOKEN_SESSIONTOKEN);
-  g_return_if_fail (sessionToken != NULL);
-
-  url = g_strdup_printf ("%s%s", MOZILLA_FXA_SERVER_URL, endpoint);
-  ephy_sync_crypto_process_session_token (sessionToken, &tokenID, &reqHMACkey, &requestKey);
-  tokenID_hex = ephy_sync_crypto_encode_hex (tokenID, 0);
-
-  msg = soup_message_new (SOUP_METHOD_POST, url);
-  soup_message_set_request (msg, content_type, SOUP_MEMORY_STATIC,
-                            request_body, strlen (request_body));
-  hoptions = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
-                                                NULL, NULL, NULL, request_body, NULL);
-  hheader = ephy_sync_crypto_compute_hawk_header (url, "POST", tokenID_hex,
-                                                  reqHMACkey, EPHY_SYNC_TOKEN_LENGTH,
-                                                  hoptions);
-  soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
-  soup_message_headers_append (msg->request_headers, "content-type", content_type);
-  soup_session_queue_message (self->session, msg, destroy_session_response_cb, NULL);
-
-  ephy_sync_crypto_hawk_options_free (hoptions);
-  ephy_sync_crypto_hawk_header_free (hheader);
-  g_free (tokenID_hex);
-  g_free (tokenID);
-  g_free (reqHMACkey);
-  g_free (requestKey);
-  g_free (url);
+  g_hash_table_remove (self->managers, manager);
 }
 
-char *
-ephy_sync_service_start_sign_in (EphySyncService  *self,
-                                 guint8           *tokenID,
-                                 guint8           *reqHMACkey)
+static void
+ephy_sync_service_report_sign_in_error (EphySyncService *self,
+                                        const char      *message,
+                                        const char      *sessionToken,
+                                        gboolean         clear_secrets)
 {
-  JsonNode *node;
-  JsonObject *json;
-  char *tokenID_hex;
-  char *bundle = NULL;
-  guint status_code;
-
-  /* Retrieve the sync keys bundle from the /account/keys endpoint. */
-  tokenID_hex = ephy_sync_crypto_encode_hex (tokenID, 0);
-  status_code = ephy_sync_service_fxa_hawk_get_sync (self, "account/keys", tokenID_hex,
-                                                     reqHMACkey, EPHY_SYNC_TOKEN_LENGTH,
-                                                     &node);
-  json = json_node_get_object (node);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (message);
 
-  if (status_code == 200) {
-    bundle = g_strdup (json_object_get_string_member (json, "bundle"));
-  } else {
-    LOG ("Failed to retrieve sync keys bundle: code: %ld, errno: %ld, error: '%s', message: '%s'",
-         json_object_get_int_member (json, "code"),
-         json_object_get_int_member (json, "errno"),
-         json_object_get_string_member (json, "error"),
-         json_object_get_string_member (json, "message"));
-  }
+  g_signal_emit (self, signals[SIGN_IN_ERROR], 0, message);
+  ephy_sync_service_destroy_session (self, sessionToken);
 
-  g_free (tokenID_hex);
-  json_node_free (node);
-
-  return bundle;
-}
-
-void
-ephy_sync_service_finish_sign_in (EphySyncService *self,
-                                  const char      *email,
-                                  const char      *uid,
-                                  const char      *sessionToken,
-                                  const char      *keyFetchToken,
-                                  const char      *unwrapBKey,
-                                  char            *bundle,
-                                  guint8          *respHMACkey,
-                                  guint8          *respXORkey)
-{
-  guint8 *unwrapKB;
-  guint8 *kA;
-  guint8 *kB;
-  char *kA_hex;
-  char *kB_hex;
-
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (email != NULL);
-  g_return_if_fail (uid != NULL);
-  g_return_if_fail (sessionToken != NULL);
-  g_return_if_fail (keyFetchToken != NULL);
-  g_return_if_fail (unwrapBKey != NULL);
-  g_return_if_fail (bundle != NULL);
-  g_return_if_fail (respHMACkey != NULL);
-  g_return_if_fail (respXORkey != NULL);
-
-  /* Derive the sync keys form the received key bundle. */
-  unwrapKB = ephy_sync_crypto_decode_hex (unwrapBKey);
-  ephy_sync_crypto_compute_sync_keys (bundle,
-                                      respHMACkey, respXORkey, unwrapKB,
-                                      &kA, &kB);
-  kA_hex = ephy_sync_crypto_encode_hex (kA, 0);
-  kB_hex = ephy_sync_crypto_encode_hex (kB, 0);
-
-  /* Save the email and the tokens. */
-  g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER, email);
-  ephy_sync_service_set_user_email (self, email);
-  ephy_sync_service_set_token (self, uid, TOKEN_UID);
-  ephy_sync_service_set_token (self, sessionToken, TOKEN_SESSIONTOKEN);
-  ephy_sync_service_set_token (self, keyFetchToken, TOKEN_KEYFETCHTOKEN);
-  ephy_sync_service_set_token (self, unwrapBKey, TOKEN_UNWRAPBKEY);
-  ephy_sync_service_set_token (self, kA_hex, TOKEN_KA);
-  ephy_sync_service_set_token (self, kB_hex, TOKEN_KB);
-
-  /* Store the tokens in the secret schema. */
-  ephy_sync_secret_store_tokens (self, email, uid, sessionToken,
-                                 keyFetchToken, unwrapBKey, kA_hex, kB_hex);
-
-  g_free (kA);
-  g_free (kB);
-  g_free (kA_hex);
-  g_free (kB_hex);
-  g_free (unwrapKB);
-}
-
-void
-ephy_sync_service_send_storage_message (EphySyncService     *self,
-                                        char                *endpoint,
-                                        const char          *method,
-                                        char                *request_body,
-                                        double               modified_since,
-                                        double               unmodified_since,
-                                        SoupSessionCallback  callback,
-                                        gpointer             user_data)
-{
-  StorageServerRequestAsyncData *data;
-
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (endpoint != NULL);
-  g_return_if_fail (method != NULL);
-
-  data = storage_server_request_async_data_new (self, endpoint, method, request_body,
-                                                modified_since, unmodified_since,
-                                                callback, user_data);
-
-  /* If there is currently another message being transmitted, then the new
-   * message has to wait in the queue, otherwise, it is free to go. */
-  if (self->locked == FALSE) {
-    self->locked = TRUE;
-    ephy_sync_service_issue_storage_request (self, data);
-  } else {
-    g_queue_push_tail (self->storage_queue, data);
+  if (clear_secrets) {
+    g_clear_pointer (&self->user_email, g_free);
+    g_hash_table_remove_all (self->secrets);
   }
 }
 
-void
-ephy_sync_service_release_next_storage_message (EphySyncService *self)
+static char *
+ephy_sync_service_upload_crypto_keys_record (EphySyncService *self)
 {
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  /* We should never reach this with the service not being locked. */
-  g_assert (self->locked == TRUE);
-
-  /* If there are other messages waiting in the queue, we release the next one
-   * and keep the service locked, else, we mark the service as not locked. */
-  if (g_queue_is_empty (self->storage_queue) == FALSE)
-    ephy_sync_service_issue_storage_request (self, g_queue_pop_head (self->storage_queue));
-  else
-    self->locked = FALSE;
+  SyncCryptoKeyBundle *bundle;
+  JsonNode *node;
+  JsonObject *record;
+  char *payload_clear;
+  char *payload_cipher;
+  char *body;
+  guint8 *master_key;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  record = json_object_new ();
+  payload_clear = ephy_sync_crypto_generate_crypto_keys (TOKEN_LENGTH);
+  master_key = ephy_sync_crypto_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+  bundle = ephy_sync_crypto_derive_key_bundle (master_key, TOKEN_LENGTH);
+  payload_cipher = ephy_sync_crypto_encrypt_record (payload_clear, bundle);
+  json_object_set_string_member (record, "payload", payload_cipher);
+  json_object_set_string_member (record, "id", "keys");
+  json_node_set_object (node, record);
+  body = json_to_string (node, FALSE);
+
+  ephy_sync_service_queue_storage_request (self, "storage/crypto/keys",
+                                           SOUP_METHOD_PUT, body,
+                                           -1, -1, NULL, NULL);
+
+  g_free (body);
+  g_free (payload_cipher);
+  ephy_sync_crypto_key_bundle_free (bundle);
+  g_free (master_key);
+  json_object_unref (record);
+  json_node_unref (node);
+
+  return payload_clear;
 }
 
 static void
-upload_bookmark_response_cb (SoupSession *session,
-                             SoupMessage *msg,
-                             gpointer     user_data)
+obtain_crypto_keys_cb (SoupSession *session,
+                       SoupMessage *msg,
+                       gpointer     user_data)
 {
-  EphySyncService *service;
-  EphyBookmarksManager *manager;
-  EphyBookmark *bookmark;
-  double last_modified;
+  EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+  SyncCryptoKeyBundle *bundle = NULL;
+  JsonNode *node = NULL;
+  JsonObject *json = NULL;
+  GError *error = NULL;
+  const char *payload;
+  char *crypto_keys = NULL;
+  guint8 *kB = NULL;
 
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
-  bookmark = EPHY_BOOKMARK (user_data);
+  if (msg->status_code == 404) {
+    crypto_keys = ephy_sync_service_upload_crypto_keys_record (self);
+    goto store_secrets;
+  }
 
-  if (msg->status_code == 200) {
-    last_modified = g_ascii_strtod (msg->response_body->data, NULL);
-    ephy_bookmark_set_modification_time (bookmark, last_modified);
-    ephy_bookmark_set_is_uploaded (bookmark, TRUE);
-    ephy_bookmarks_manager_save_to_file_async (manager, NULL, NULL, NULL);
+  if (msg->status_code != 200) {
+    g_warning ("Failed to get crypto/keys record. Status code: %u, response: %s",
+               msg->status_code, msg->response_body->data);
+    goto out_error;
+  }
 
-    LOG ("Successfully uploaded to server");
-  } else if (msg->status_code == 412) {
-    ephy_sync_service_download_bookmark (service, bookmark);
-  } else {
-    LOG ("Failed to upload to server. Status code: %u, response: %s",
-         msg->status_code, msg->response_body->data);
+  node = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    g_warning ("Response is not a valid JSON: %s", error->message);
+    goto out_error;
+  }
+  json = json_node_get_object (node);
+  if (!json) {
+    g_warning ("JSON node does not hold an object");
+    goto out_error;
+  }
+  payload = json_object_get_string_member (json, "payload");
+  if (!payload) {
+    g_warning ("JSON object has missing or invalid 'payload' member");
+    goto out_error;
+  }
+  /* Derive the Sync Key bundle from kB. The bundle consists of two 32 bytes keys:
+   * the first one used as a symmetric encryption key (AES) and the second one
+   * used as a HMAC key. */
+  kB = ephy_sync_crypto_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+  bundle = ephy_sync_crypto_derive_key_bundle (kB, TOKEN_LENGTH);
+  crypto_keys = ephy_sync_crypto_decrypt_record (payload, bundle);
+  if (!crypto_keys) {
+    g_warning ("Failed to decrypt crypto/keys record");
+    goto out_error;
   }
 
-  ephy_sync_service_release_next_storage_message (service);
+store_secrets:
+  ephy_sync_service_set_secret (self, secrets[CRYPTO_KEYS], crypto_keys);
+  ephy_sync_secret_store_secrets (self);
+  goto out_no_error;
+out_error:
+  ephy_sync_service_report_sign_in_error (self, _("Failed to retrieve crypto keys."), NULL, TRUE);
+out_no_error:
+  if (bundle)
+    ephy_sync_crypto_key_bundle_free (bundle);
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
+  g_free (crypto_keys);
+  g_free (kB);
 }
 
-void
-ephy_sync_service_upload_bookmark (EphySyncService *self,
-                                   EphyBookmark    *bookmark,
-                                   gboolean         force)
+static void
+ephy_sync_service_obtain_crypto_keys (EphySyncService *self)
 {
-  char *endpoint;
-  char *bso;
-  double modified;
-
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (ephy_sync_service_is_signed_in (self));
-  g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
-
-  endpoint = g_strdup_printf ("storage/%s/%s",
-                              EPHY_BOOKMARKS_COLLECTION,
-                              ephy_bookmark_get_id (bookmark));
-  bso = ephy_bookmark_to_bso (bookmark);
-  modified = ephy_bookmark_get_modification_time (bookmark);
-  ephy_sync_service_send_storage_message (self, endpoint,
-                                          SOUP_METHOD_PUT, bso, -1,
-                                          force ? -1 : modified,
-                                          upload_bookmark_response_cb,
-                                          bookmark);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-  g_free (endpoint);
-  g_free (bso);
+  ephy_sync_service_queue_storage_request (self, "storage/crypto/keys",
+                                           SOUP_METHOD_GET, NULL, -1, -1,
+                                           obtain_crypto_keys_cb, self);
 }
 
-static void
-download_bookmark_response_cb (SoupSession *session,
-                               SoupMessage *msg,
-                               gpointer     user_data)
+static JsonObject *
+make_engine_object (int version)
 {
-  EphySyncService *service;
-  EphyBookmarksManager *manager;
-  EphyBookmark *bookmark;
-  GSequenceIter *iter;
-  JsonParser *parser;
-  JsonObject *bso;
-  const char *id;
+  JsonObject *object;
+  char *sync_id;
 
-  if (msg->status_code != 200) {
-    LOG ("Failed to download from server. Status code: %u, response: %s",
-         msg->status_code, msg->response_body->data);
-    goto out;
-  }
+  object = json_object_new ();
+  sync_id = ephy_sync_crypto_get_random_sync_id ();
+  json_object_set_int_member (object, "version", version);
+  json_object_set_string_member (object, "syncID", sync_id);
 
-  parser = json_parser_new ();
-  json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
-  bso = json_node_get_object (json_parser_get_root (parser));
-  bookmark = ephy_bookmark_from_bso (bso);
-  id = ephy_bookmark_get_id (bookmark);
-
-  /* Overwrite any local bookmark. */
-  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
-  ephy_bookmarks_manager_remove_bookmark (manager,
-                                          ephy_bookmarks_manager_get_bookmark_by_id (manager, id));
-  ephy_bookmarks_manager_add_bookmark (manager, bookmark);
-
-  /* We have to manually add the tags to the bookmarks manager. */
-  for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (bookmark));
-       !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
-    ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
-
-  g_object_unref (bookmark);
-  g_object_unref (parser);
+  g_free (sync_id);
 
-out:
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  ephy_sync_service_release_next_storage_message (service);
+  return object;
 }
 
-void
-ephy_sync_service_download_bookmark (EphySyncService *self,
-                                     EphyBookmark    *bookmark)
+static void
+ephy_sync_service_upload_meta_global_record (EphySyncService *self)
 {
-  char *endpoint;
-
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (ephy_sync_service_is_signed_in (self));
-  g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
-
-  endpoint = g_strdup_printf ("storage/%s/%s",
-                              EPHY_BOOKMARKS_COLLECTION,
-                              ephy_bookmark_get_id (bookmark));
-  ephy_sync_service_send_storage_message (self, endpoint,
-                                          SOUP_METHOD_GET, NULL, -1, -1,
-                                          download_bookmark_response_cb, NULL);
-
-  g_free (endpoint);
+  JsonNode *node;
+  JsonObject *record;
+  JsonObject *payload;
+  JsonObject *engines;
+  JsonArray *declined;
+  char *sync_id;
+  char *payload_str;
+  char *body;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  record = json_object_new ();
+  payload = json_object_new ();
+  engines = json_object_new ();
+  declined = json_array_new ();
+  json_array_add_string_element (declined, "addons");
+  json_array_add_string_element (declined, "prefs");
+  json_array_add_string_element (declined, "tabs");
+  json_object_set_array_member (payload, "declined", declined);
+  json_object_set_object_member (engines, "clients", make_engine_object (1));
+  json_object_set_object_member (engines, "bookmarks", make_engine_object (2));
+  json_object_set_object_member (engines, "history", make_engine_object (1));
+  json_object_set_object_member (engines, "passwords", make_engine_object (1));
+  json_object_set_object_member (engines, "forms", make_engine_object (1));
+  json_object_set_object_member (payload, "engines", engines);
+  json_object_set_int_member (payload, "storageVersion", STORAGE_VERSION);
+  sync_id = ephy_sync_crypto_get_random_sync_id ();
+  json_object_set_string_member (payload, "syncID", sync_id);
+  json_node_set_object (node, payload);
+  payload_str = json_to_string (node, FALSE);
+  json_object_set_string_member (record, "payload", payload_str);
+  json_object_set_string_member (record, "id", "global");
+  json_node_set_object (node, record);
+  body = json_to_string (node, FALSE);
+
+  ephy_sync_service_queue_storage_request (self, "storage/meta/global",
+                                           SOUP_METHOD_PUT, body,
+                                           -1, -1, NULL, NULL);
+
+  g_free (body);
+  g_free (payload_str);
+  g_free (sync_id);
+  json_object_unref (payload);
+  json_object_unref (record);
+  json_node_unref (node);
 }
 
 static void
-delete_bookmark_conditional_response_cb (SoupSession *session,
-                                         SoupMessage *msg,
-                                         gpointer     user_data)
+check_storage_version_cb (SoupSession *session,
+                          SoupMessage *msg,
+                          gpointer     user_data)
 {
-  EphySyncService *service;
-  EphyBookmark *bookmark;
-  EphyBookmarksManager *manager;
-
-  bookmark = EPHY_BOOKMARK (user_data);
-  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
+  EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+  JsonParser *parser = NULL;
+  JsonObject *json = NULL;
+  GError *error = NULL;
+  char *payload = NULL;
+  char *message = NULL;
+  int storage_version;
 
   if (msg->status_code == 404) {
-    ephy_bookmarks_manager_remove_bookmark (manager, bookmark);
-  } else if (msg->status_code == 200) {
-    LOG ("The bookmark still exists on the server, don't delete it");
-  } else {
-    LOG ("Failed to delete conditionally. Status code: %u, response: %s",
-         msg->status_code, msg->response_body->data);
+    ephy_sync_service_upload_meta_global_record (self);
+    goto obtain_crypto_keys;
   }
 
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  ephy_sync_service_release_next_storage_message (service);
-}
-
-static void
-delete_bookmark_response_cb (SoupSession *session,
-                             SoupMessage *msg,
-                             gpointer     user_data)
-{
-  EphySyncService *service;
+  if (msg->status_code != 200) {
+    g_warning ("Failed to get meta/global record. Status code: %u, response: %s",
+               msg->status_code, msg->response_body->data);
+    goto out_error;
+  }
 
-  if (msg->status_code == 200)
-    LOG ("Successfully deleted the bookmark from the server");
-  else
-    LOG ("Failed to delete. Status code: %u, response: %s",
-         msg->status_code, msg->response_body->data);
+  parser = json_parser_new ();
+  json_parser_load_from_data (parser, msg->response_body->data, -1, &error);
+  if (error) {
+    g_warning ("Response is not a valid JSON: %s", error->message);
+    goto out_error;
+  }
+  json = json_node_get_object (json_parser_get_root (parser));
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out_error;
+  }
+  if (!json_object_get_string_member (json, "payload")) {
+    g_warning ("JSON object has missing or invalid 'payload' member");
+    goto out_error;
+  }
+  payload = g_strdup (json_object_get_string_member (json, "payload"));
+  json_parser_load_from_data (parser, payload, -1, &error);
+  if (error) {
+    g_warning ("Payload is not a valid JSON: %s", error->message);
+    goto out_error;
+  }
+  json = json_node_get_object (json_parser_get_root (parser));
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out_error;
+  }
+  if (!json_object_get_int_member (json, "storageVersion")) {
+    g_warning ("JSON object has missing or invalid 'storageVersion' member");
+    goto out_error;
+  }
+  storage_version = json_object_get_int_member (json, "storageVersion");
+  if (storage_version != STORAGE_VERSION) {
+    /* Translators: the %d is the storage version, the \n is a newline character. */
+    message = g_strdup_printf (_("Your Firefox Account uses storage version %d "
+                                 "which Epiphany does not support.\n"
+                                 "Create a new account to use the latest storage version."),
+                               storage_version);
+    goto out_error;
+  }
 
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  ephy_sync_service_release_next_storage_message (service);
+obtain_crypto_keys:
+  ephy_sync_service_obtain_crypto_keys (self);
+  goto out_no_error;
+out_error:
+  message = message ? message : _("Failed to verify storage version.");
+  ephy_sync_service_report_sign_in_error (self, message, NULL, TRUE);
+out_no_error:
+  if (parser)
+    g_object_unref (parser);
+  if (error)
+    g_error_free (error);
+  g_free (payload);
+  g_free (message);
 }
 
-void
-ephy_sync_service_delete_bookmark (EphySyncService *self,
-                                   EphyBookmark    *bookmark,
-                                   gboolean         conditional)
+static void
+ephy_sync_service_check_storage_version (EphySyncService *self)
 {
-  char *endpoint;
-
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
-
-  if (ephy_sync_service_is_signed_in (self) == FALSE)
-    return;
-
-  endpoint = g_strdup_printf ("storage/%s/%s",
-                              EPHY_BOOKMARKS_COLLECTION,
-                              ephy_bookmark_get_id (bookmark));
-
-  /* If the bookmark does not exist on the server, delete it from the local
-   * instance too. */
-  if (conditional == TRUE) {
-    ephy_sync_service_send_storage_message (self, endpoint,
-                                            SOUP_METHOD_GET, NULL, -1, -1,
-                                            delete_bookmark_conditional_response_cb,
-                                            bookmark);
-  } else {
-    ephy_sync_service_send_storage_message (self, endpoint,
-                                            SOUP_METHOD_DELETE, NULL, -1, -1,
-                                            delete_bookmark_response_cb, NULL);
-  }
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-  g_free (endpoint);
+  ephy_sync_service_queue_storage_request (self, "storage/meta/global",
+                                           SOUP_METHOD_GET, NULL, -1, -1,
+                                           check_storage_version_cb, self);
 }
 
 static void
-sync_bookmarks_first_time_response_cb (SoupSession *session,
-                                       SoupMessage *msg,
-                                       gpointer     user_data)
+ephy_sync_service_conclude_sign_in (EphySyncService *self,
+                                    SignInAsyncData *data,
+                                    const char      *bundle)
 {
-  EphySyncService *service;
-  EphyBookmarksManager *manager;
-  GSequence *bookmarks;
-  GSequenceIter *iter;
-  GHashTable *marked;
-  JsonParser *parser;
-  JsonArray *array;
-  const char *timestamp;
-  double server_time;
-
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
-  bookmarks = ephy_bookmarks_manager_get_bookmarks (manager);
-  marked = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
-  parser = json_parser_new ();
-  json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
+  guint8 *unwrapKB;
+  guint8 *kA;
+  guint8 *kB;
+  char *kB_hex;
 
-  if (msg->status_code != 200) {
-    LOG ("Failed to do a first time sync. Status code: %u, response: %s",
-         msg->status_code, msg->response_body->data);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (data);
+  g_assert (bundle);
+
+  /* Derive the master sync keys form the key bundle. */
+  unwrapKB = ephy_sync_crypto_decode_hex (data->unwrapBKey);
+  if (!ephy_sync_crypto_compute_sync_keys (bundle, data->respHMACkey,
+                                           data->respXORkey, unwrapKB,
+                                           &kA, &kB, TOKEN_LENGTH)) {
+    ephy_sync_service_report_sign_in_error (self, _("Failed to retrieve the Sync Key"),
+                                            data->sessionToken, FALSE);
     goto out;
   }
 
-  array = json_node_get_array (json_parser_get_root (parser));
-  for (gsize i = 0; i < json_array_get_length (array); i++) {
-    JsonObject *bso = json_array_get_object_element (array, i);
-    EphyBookmark *remote = ephy_bookmark_from_bso (bso);
-    EphyBookmark *local;
+  /* Save email and tokens. */
+  self->user_email = g_strdup (data->email);
+  ephy_sync_service_set_secret (self, secrets[UID], data->uid);
+  ephy_sync_service_set_secret (self, secrets[SESSION_TOKEN], data->sessionToken);
+  kB_hex = ephy_sync_crypto_encode_hex (kB, TOKEN_LENGTH);
+  ephy_sync_service_set_secret (self, secrets[MASTER_KEY], kB_hex);
 
-    if (remote == NULL)
-      continue;
-
-    local = ephy_bookmarks_manager_get_bookmark_by_id (manager, ephy_bookmark_get_id (remote));
-
-    if (local == NULL) {
-      local = ephy_bookmarks_manager_get_bookmark_by_url (manager, ephy_bookmark_get_url (remote));
-
-      /* If there is no local equivalent of the remote bookmark, then add it to
-       * the local instance together with its tags. */
-      if (local == NULL) {
-        ephy_bookmarks_manager_add_bookmark (manager, remote);
-
-        /* We have to manually add the tags to the bookmarks manager. */
-        for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
-             !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
-          ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
-
-        g_hash_table_add (marked, g_object_ref (remote));
-      }
-      /* If there is a local bookmark with the same url as the remote one, then
-       * merge tags into the local one, keep the remote id and upload it to the
-       * server. */
-      else {
-        for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
-             !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
-          ephy_bookmark_add_tag (local, g_sequence_get (iter));
-          ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
-        }
-
-        ephy_bookmark_set_id (local, ephy_bookmark_get_id (remote));
-        ephy_sync_service_upload_bookmark (service, local, TRUE);
-        g_hash_table_add (marked, g_object_ref (local));
-      }
-    }
-    /* Having a local bookmark with the same id as the remote one means that the
-     * bookmark has been synced before in the past. Keep the one with the most
-     * recent modified timestamp. */
-    else {
-      if (ephy_bookmark_get_modification_time (remote) > ephy_bookmark_get_modification_time (local)) {
-        ephy_bookmarks_manager_remove_bookmark (manager, local);
-        ephy_bookmarks_manager_add_bookmark (manager, remote);
-
-        /* We have to manually add the tags to the bookmarks manager. */
-        for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
-             !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
-          ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
-
-        g_hash_table_add (marked, g_object_ref (remote));
-      } else {
-        if (ephy_bookmark_get_modification_time (local) > ephy_bookmark_get_modification_time (remote))
-          ephy_sync_service_upload_bookmark (service, local, TRUE);
-
-        g_hash_table_add (marked, g_object_ref (local));
-      }
-    }
-
-    g_object_unref (remote);
-  }
-
-  /* Upload the remaining local bookmarks to the server. */
-  for (iter = g_sequence_get_begin_iter (bookmarks);
-       !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
-    EphyBookmark *bookmark = g_sequence_get (iter);
-
-    if (g_hash_table_contains (marked, bookmark) == FALSE)
-      ephy_sync_service_upload_bookmark (service, bookmark, TRUE);
-  }
-
-  /* Save changes to file. */
-  ephy_bookmarks_manager_save_to_file_async (manager, NULL, NULL, NULL);
-
-  /* Set the sync time. */
-  timestamp = soup_message_headers_get_one (msg->response_headers, "X-Weave-Timestamp");
-  server_time = g_ascii_strtod (timestamp, NULL);
-  ephy_sync_service_set_sync_time (service, server_time);
+  ephy_sync_service_check_storage_version (self);
 
+  g_free (kB_hex);
+  g_free (kB);
+  g_free (kA);
 out:
-  g_object_unref (parser);
-  g_hash_table_unref (marked);
-
-  ephy_sync_service_release_next_storage_message (service);
+  g_free (unwrapKB);
+  sign_in_async_data_free (data);
 }
 
 static void
-sync_bookmarks_response_cb (SoupSession *session,
-                            SoupMessage *msg,
-                            gpointer     user_data)
+get_account_keys_cb (SoupSession *session,
+                     SoupMessage *msg,
+                     gpointer     user_data)
 {
-  EphySyncService *service;
-  EphyBookmarksManager *manager;
-  GSequence *bookmarks;
-  GSequenceIter *iter;
-  JsonParser *parser;
-  JsonArray *array;
-  const char *timestamp;
-  double server_time;
-
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
-  bookmarks = ephy_bookmarks_manager_get_bookmarks (manager);
-  parser = json_parser_new ();
-  json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
-
-  /* Code 304 indicates that the resource has not been modified. Therefore,
-   * only upload the local bookmarks that were not uploaded. */
-  if (msg->status_code == 304)
-    goto handle_local_bookmarks;
-
-  if (msg->status_code != 200) {
-    LOG ("Failed to sync bookmarks. Status code: %u, response: %s",
-         msg->status_code, msg->response_body->data);
-    goto out;
+  SignInAsyncData *data = (SignInAsyncData *)user_data;
+  JsonNode *node = NULL;
+  JsonObject *json = NULL;
+  GError *error = NULL;
+  const char *bundle;
+
+  node = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    g_warning ("Response is not a valid JSON: %s", error->message);
+    goto out_error;
+  }
+  json = json_node_get_object (node);
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out_error;
   }
 
-  array = json_node_get_array (json_parser_get_root (parser));
-  for (gsize i = 0; i < json_array_get_length (array); i++) {
-    JsonObject *bso = json_array_get_object_element (array, i);
-    EphyBookmark *remote = ephy_bookmark_from_bso (bso);
-    EphyBookmark *local;
-
-    if (remote == NULL)
-      continue;
-
-    local = ephy_bookmarks_manager_get_bookmark_by_id (manager, ephy_bookmark_get_id (remote));
-
-    if (local == NULL) {
-      ephy_bookmarks_manager_add_bookmark (manager, remote);
-
-      /* We have to manually add the tags to the bookmarks manager. */
-      for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
-           !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
-        ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
-    } else {
-      if (ephy_bookmark_get_modification_time (remote) > ephy_bookmark_get_modification_time (local)) {
-        ephy_bookmarks_manager_remove_bookmark (manager, local);
-        ephy_bookmarks_manager_add_bookmark (manager, remote);
-
-        /* We have to manually add the tags to the bookmarks manager. */
-        for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
-             !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
-          ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
-      } else {
-        if (ephy_bookmark_get_modification_time (local) > ephy_bookmark_get_modification_time (remote))
-          ephy_sync_service_upload_bookmark (service, local, TRUE);
-
-        g_object_unref (remote);
-      }
+  if (msg->status_code == 200) {
+    bundle = json_object_get_string_member (json, "bundle");
+    if (!bundle) {
+      g_warning ("JSON object has invalid or missing 'bundle' member");
+      goto out_error;
     }
+    /* Extract the master sync keys from the bundle and save tokens. */
+    ephy_sync_service_conclude_sign_in (data->service, data, bundle);
+    goto out_no_error;
   }
 
-handle_local_bookmarks:
-  for (iter = g_sequence_get_begin_iter (bookmarks);
-       !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
-    EphyBookmark *bookmark = EPHY_BOOKMARK (g_sequence_get (iter));
-
-    if (ephy_bookmark_is_uploaded (bookmark) == TRUE)
-      ephy_sync_service_delete_bookmark (service, bookmark, TRUE);
-    else
-      ephy_sync_service_upload_bookmark (service, bookmark, FALSE);
+  /* If account in not verified, poll the Firefox Accounts Server until the
+   * verification has completed. */
+  if (json_object_get_int_member (json, "errno") == 104) {
+    LOG ("Account not verified, retrying...");
+    ephy_sync_service_fxa_hawk_get_async (data->service, "account/keys",
+                                          data->tokenID_hex, data->reqHMACkey,
+                                          TOKEN_LENGTH, get_account_keys_cb, data);
+    goto out_no_error;
   }
 
-  /* Save changes to file. */
-  ephy_bookmarks_manager_save_to_file_async (manager, NULL, NULL, NULL);
+  g_warning ("Failed to get /account/keys. Status code: %u, response: %s",
+             msg->status_code, msg->response_body->data);
+
+out_error:
+  ephy_sync_service_report_sign_in_error (data->service,
+                                          _("Failed to retrieve the Sync Key"),
+                                          data->sessionToken, FALSE);
+  sign_in_async_data_free (data);
+out_no_error:
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
+}
 
-  /* Set the sync time. */
-  timestamp = soup_message_headers_get_one (msg->response_headers, "X-Weave-Timestamp");
-  server_time = g_ascii_strtod (timestamp, NULL);
-  ephy_sync_service_set_sync_time (service, server_time);
+void
+ephy_sync_service_do_sign_in (EphySyncService *self,
+                              const char      *email,
+                              const char      *uid,
+                              const char      *sessionToken,
+                              const char      *keyFetchToken,
+                              const char      *unwrapBKey)
+{
+  SignInAsyncData *data;
+  guint8 *tokenID;
+  guint8 *reqHMACkey;
+  guint8 *respHMACkey;
+  guint8 *respXORkey;
+  char *tokenID_hex;
 
-out:
-  g_object_unref (parser);
+  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  g_return_if_fail (email);
+  g_return_if_fail (uid);
+  g_return_if_fail (sessionToken);
+  g_return_if_fail (keyFetchToken);
+  g_return_if_fail (unwrapBKey);
+
+  /* Derive tokenID, reqHMACkey, respHMACkey and respXORkey from keyFetchToken.
+   * tokenID and reqHMACkey are used to sign a HAWK GET requests to the /account/keys
+   * endpoint. The server looks up the stored table entry with tokenID, checks
+   * the request HMAC for validity, then returns the pre-encrypted response.
+   * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#fetching-sync-keys */
+  ephy_sync_crypto_process_key_fetch_token (keyFetchToken, &tokenID, &reqHMACkey,
+                                            &respHMACkey, &respXORkey, TOKEN_LENGTH);
+  tokenID_hex = ephy_sync_crypto_encode_hex (tokenID, TOKEN_LENGTH);
+
+  /* Get the master sync key bundle from the /account/keys endpoint. */
+  data = sign_in_async_data_new (self, email, uid,
+                                 sessionToken, unwrapBKey,
+                                 tokenID_hex, reqHMACkey,
+                                 respHMACkey, respXORkey);
+  ephy_sync_service_fxa_hawk_get_async (self, "account/keys", tokenID_hex,
+                                        reqHMACkey, TOKEN_LENGTH,
+                                        get_account_keys_cb, data);
 
-  ephy_sync_service_release_next_storage_message (service);
+  g_free (tokenID_hex);
+  g_free (tokenID);
+  g_free (reqHMACkey);
+  g_free (respHMACkey);
+  g_free (respXORkey);
 }
 
 void
-ephy_sync_service_sync_bookmarks (EphySyncService *self,
-                                  gboolean         first)
+ephy_sync_service_do_sign_out (EphySyncService *self)
 {
-  char *endpoint;
-
   g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (ephy_sync_service_is_signed_in (self));
 
-  endpoint = g_strdup_printf ("storage/%s?full=true", EPHY_BOOKMARKS_COLLECTION);
+  ephy_sync_service_unregister_client_id (self);
+  ephy_sync_service_stop_periodical_sync (self);
+  ephy_sync_service_destroy_session (self, NULL);
+  ephy_sync_service_clear_storage_credentials (self);
+  ephy_sync_secret_forget_secrets ();
+  g_hash_table_remove_all (self->secrets);
+  g_clear_pointer (&self->user_email, g_free);
+  while (!g_queue_is_empty (self->storage_queue))
+    storage_request_async_data_free (g_queue_pop_head (self->storage_queue));
+
+  g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER, "");
+}
 
-  if (first == TRUE) {
-    ephy_sync_service_send_storage_message (self, endpoint,
-                                            SOUP_METHOD_GET, NULL, -1, -1,
-                                            sync_bookmarks_first_time_response_cb, NULL);
+static void
+delete_synchronizable_cb (SoupSession *session,
+                          SoupMessage *msg,
+                          gpointer     user_data)
+{
+  if (msg->status_code == 200) {
+    LOG ("Successfully deleted from server");
   } else {
-    ephy_sync_service_send_storage_message (self, endpoint,
-                                            SOUP_METHOD_GET, NULL,
-                                            ephy_sync_service_get_sync_time (self), -1,
-                                            sync_bookmarks_response_cb, NULL);
+    g_warning ("Failed to delete object. Status code: %u, response: %s",
+               msg->status_code, msg->response_body->data);
   }
-
-  g_free (endpoint);
 }
 
-static gboolean
-do_periodical_sync (gpointer user_data)
+void
+ephy_sync_service_delete_synchronizable (EphySyncService           *self,
+                                         EphySynchronizableManager *manager,
+                                         EphySynchronizable        *synchronizable)
 {
-  EphySyncService *service = EPHY_SYNC_SERVICE (user_data);
+  JsonNode *node;
+  JsonObject *object;
+  SyncCryptoKeyBundle *bundle;
+  char *endpoint;
+  char *record;
+  char *payload;
+  char *body;
+  const char *collection;
+  const char *id;
 
-  ephy_sync_service_sync_bookmarks (service, FALSE);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+  id = ephy_synchronizable_get_id (synchronizable);
+  collection = ephy_synchronizable_manager_get_collection_name (manager);
+  endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  object = json_object_new ();
+  json_node_set_object (node, object);
+  json_object_set_string_member (object, "id", id);
+  json_object_set_boolean_member (object, "deleted", TRUE);
+  record = json_to_string (node, FALSE);
+  bundle = ephy_sync_service_get_key_bundle (self, collection);
+  payload = ephy_sync_crypto_encrypt_record (record,  bundle);
+  json_object_remove_member (object, "type");
+  json_object_remove_member (object, "deleted");
+  json_object_set_string_member (object, "payload", payload);
+  body = json_to_string (node, FALSE);
+
+  LOG ("Deleting object with id %s...", id);
+  ephy_sync_service_queue_storage_request (self, endpoint,
+                                           SOUP_METHOD_PUT, body, -1, -1,
+                                           delete_synchronizable_cb, NULL);
 
-  return G_SOURCE_CONTINUE;
+  g_free (endpoint);
+  g_free (record);
+  g_free (payload);
+  g_free (body);
+  json_object_unref (object);
+  json_node_unref (node);
+  ephy_sync_crypto_key_bundle_free (bundle);
 }
 
 void
-ephy_sync_service_start_periodical_sync (EphySyncService *self,
-                                         gboolean         now)
+ephy_sync_service_do_sync (EphySyncService *self)
 {
   g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  g_return_if_fail (ephy_sync_service_is_signed_in (self));
 
-  if (ephy_sync_service_is_signed_in (self) == FALSE)
-    return;
-
-  if (now == TRUE)
-    do_periodical_sync (self);
-
-  self->source_id = g_timeout_add_seconds (SYNC_FREQUENCY, do_periodical_sync, self);
+  ephy_sync_service_sync (self);
 }
 
 void
-ephy_sync_service_stop_periodical_sync (EphySyncService *self)
+ephy_sync_service_start_periodical_sync (EphySyncService *self)
 {
   g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  g_return_if_fail (ephy_sync_service_is_signed_in (self));
 
-  if (ephy_sync_service_is_signed_in (self) == FALSE)
-    return;
-
-  if (self->source_id != 0) {
-    g_source_remove (self->source_id);
-    self->source_id = 0;
-  }
+  ephy_sync_service_sync (self);
+  ephy_sync_service_schedule_periodical_sync (self);
 }
diff --git a/src/sync/ephy-sync-service.h b/src/sync/ephy-sync-service.h
index cd1fabb..fc4a4cd 100644
--- a/src/sync/ephy-sync-service.h
+++ b/src/sync/ephy-sync-service.h
@@ -20,8 +20,7 @@
 
 #pragma once
 
-#include "ephy-bookmark.h"
-#include "ephy-sync-utils.h"
+#include "ephy-synchronizable-manager.h"
 
 #include <glib-object.h>
 #include <libsoup/soup.h>
@@ -32,56 +31,30 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphySyncService, ephy_sync_service, EPHY, SYNC_SERVICE, GObject)
 
-EphySyncService *ephy_sync_service_new                          (void);
-gboolean         ephy_sync_service_is_signed_in                 (EphySyncService *self);
-char            *ephy_sync_service_get_user_email               (EphySyncService *self);
-void             ephy_sync_service_set_user_email               (EphySyncService *self,
-                                                                 const char      *email);
-double           ephy_sync_service_get_sync_time                (EphySyncService *self);
-void             ephy_sync_service_set_sync_time                (EphySyncService *self,
-                                                                 double           time);
-char            *ephy_sync_service_get_token                    (EphySyncService   *self,
-                                                                 EphySyncTokenType  type);
-void             ephy_sync_service_set_token                    (EphySyncService   *self,
-                                                                 const char        *value,
-                                                                 EphySyncTokenType  type);
-void             ephy_sync_service_clear_storage_credentials    (EphySyncService *self);
-void             ephy_sync_service_clear_tokens                 (EphySyncService *self);
-void             ephy_sync_service_destroy_session              (EphySyncService *self,
-                                                                 const char      *sessionToken);
-char            *ephy_sync_service_start_sign_in                (EphySyncService  *self,
-                                                                 guint8           *tokenID,
-                                                                 guint8           *reqHMACkey);
-void             ephy_sync_service_finish_sign_in               (EphySyncService *self,
+EphySyncService   *ephy_sync_service_new                        (void);
+gboolean           ephy_sync_service_is_signed_in               (EphySyncService *self);
+const char        *ephy_sync_service_get_user_email             (EphySyncService *self);
+GHashTable        *ephy_sync_service_get_secrets                (EphySyncService *self);
+const char        *ephy_sync_service_get_secret                 (EphySyncService *self,
+                                                                 const char      *name);
+void               ephy_sync_service_set_secret                 (EphySyncService *self,
+                                                                 const char      *name,
+                                                                 const char      *value);
+void               ephy_sync_service_register_manager           (EphySyncService           *self,
+                                                                 EphySynchronizableManager *manager);
+void               ephy_sync_service_unregister_manager         (EphySyncService           *self,
+                                                                 EphySynchronizableManager *manager);
+void               ephy_sync_service_do_sign_in                 (EphySyncService *self,
                                                                  const char      *email,
                                                                  const char      *uid,
                                                                  const char      *sessionToken,
                                                                  const char      *keyFetchToken,
-                                                                 const char      *unwrapBKey,
-                                                                 char            *bundle,
-                                                                 guint8          *respHMACkey,
-                                                                 guint8          *respXORkey);
-void             ephy_sync_service_send_storage_message         (EphySyncService     *self,
-                                                                 char                *endpoint,
-                                                                 const char          *method,
-                                                                 char                *request_body,
-                                                                 double               modified_since,
-                                                                 double               unmodified_since,
-                                                                 SoupSessionCallback  callback,
-                                                                 gpointer             user_data);
-void             ephy_sync_service_release_next_storage_message (EphySyncService *self);
-void             ephy_sync_service_upload_bookmark              (EphySyncService *self,
-                                                                 EphyBookmark    *bookmark,
-                                                                 gboolean         force);
-void             ephy_sync_service_download_bookmark            (EphySyncService *self,
-                                                                 EphyBookmark    *bookmark);
-void             ephy_sync_service_delete_bookmark              (EphySyncService *self,
-                                                                 EphyBookmark    *bookmark,
-                                                                 gboolean         conditional);
-void             ephy_sync_service_sync_bookmarks               (EphySyncService *self,
-                                                                 gboolean         first);
-void             ephy_sync_service_start_periodical_sync        (EphySyncService *self,
-                                                                 gboolean         now);
-void             ephy_sync_service_stop_periodical_sync         (EphySyncService *self);
+                                                                 const char      *unwrapBKey);
+void               ephy_sync_service_do_sign_out                (EphySyncService *self);
+void               ephy_sync_service_do_sync                    (EphySyncService *self);
+void               ephy_sync_service_start_periodical_sync      (EphySyncService *self);
+void               ephy_sync_service_delete_synchronizable      (EphySyncService           *self,
+                                                                 EphySynchronizableManager *manager,
+                                                                 EphySynchronizable        *synchronizable);
 
 G_END_DECLS
diff --git a/src/sync/ephy-synchronizable-manager.c b/src/sync/ephy-synchronizable-manager.c
new file mode 100644
index 0000000..a114359
--- /dev/null
+++ b/src/sync/ephy-synchronizable-manager.c
@@ -0,0 +1,225 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-synchronizable-manager.h"
+
+G_DEFINE_INTERFACE (EphySynchronizableManager, ephy_synchronizable_manager, G_TYPE_OBJECT);
+
+static void
+ephy_synchronizable_manager_default_init (EphySynchronizableManagerInterface *iface)
+{
+  iface->get_collection_name = ephy_synchronizable_manager_get_collection_name;
+  iface->get_synchronizable_type = ephy_synchronizable_manager_get_synchronizable_type;
+  iface->is_initial_sync = ephy_synchronizable_manager_is_initial_sync;
+  iface->set_is_initial_sync = ephy_synchronizable_manager_set_is_initial_sync;
+  iface->get_sync_time = ephy_synchronizable_manager_get_sync_time;
+  iface->set_sync_time = ephy_synchronizable_manager_set_sync_time;
+  iface->add = ephy_synchronizable_manager_add;
+  iface->remove = ephy_synchronizable_manager_remove;
+  iface->merge_remotes = ephy_synchronizable_manager_merge_remotes;
+}
+
+/**
+ * ephy_synchronizable_manager_get_collection_name:
+ * @manager: an #EphySynchronizableManager
+ *
+ * Returns the name of the collection managed by @manager.
+ *
+ * Return value: (transfer none): @manager's collection name
+ **/
+const char *
+ephy_synchronizable_manager_get_collection_name (EphySynchronizableManager *manager)
+{
+  EphySynchronizableManagerInterface *iface;
+
+  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), NULL);
+
+  iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+  return iface->get_collection_name (manager);
+}
+
+/**
+ * ephy_synchronizable_manager_get_synchronizable_type:
+ * @manager: an #EphySynchronizableManager
+ *
+ * Returns the #GType of the #EphySynchronizable objects managed by @manager.
+ *
+ * Return value: the #GType of @manager's objects
+ **/
+GType
+ephy_synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager)
+{
+  EphySynchronizableManagerInterface *iface;
+
+  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), 0);
+
+  iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+  return iface->get_synchronizable_type (manager);
+}
+
+/**
+ * ephy_synchronizable_manager_is_initial_sync:
+ * @manager: an #EphySynchronizableManager
+ *
+ * Returns a boolean saying whether the collection managed by @manager requires
+ * an initial sync (i.e. a first time sync).
+ *
+ * Return value: %TRUE is @manager's collections requires an initial sync, %FALSE otherwise
+ **/
+gboolean
+ephy_synchronizable_manager_is_initial_sync (EphySynchronizableManager *manager)
+{
+  EphySynchronizableManagerInterface *iface;
+
+  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), FALSE);
+
+  iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+  return iface->is_initial_sync (manager);
+}
+
+/**
+ * ephy_synchronizable_manager_set_is_initial_sync:
+ * @manager: an #EphySynchronizableManager
+ * @is_initial: a boolean saying whether the collection managed by @manager
+ *              requires an initial sync (i.e. a first time sync)
+ *
+ * Sets @manager's 'requires initial sync' flag.
+ **/
+void
+ephy_synchronizable_manager_set_is_initial_sync (EphySynchronizableManager *manager,
+                                                 gboolean                   is_initial)
+{
+  EphySynchronizableManagerInterface *iface;
+
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+
+  iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+  iface->set_is_initial_sync (manager, is_initial);
+}
+
+/**
+ * ephy_synchronizable_manager_get_sync_time:
+ * @manager: an #EphySynchronizableManager
+ *
+ * Returns the timestamp at which @manager's collection was last synced,
+ * in seconds since UNIX epoch.
+ *
+ * Return value: the timestamp of @manager's collection last sync.
+ **/
+double
+ephy_synchronizable_manager_get_sync_time (EphySynchronizableManager *manager)
+{
+  EphySynchronizableManagerInterface *iface;
+
+  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), 0);
+
+  iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+  return iface->get_sync_time (manager);
+}
+
+/**
+ * ephy_synchronizable_manager_set_sync_time:
+ * @manager: an #EphySynchronizableManager
+ * @sync_time: the timestamp of the last sync, in seconds since UNIX epoch
+ *
+ * Sets the timestamp at which @manager's collection was last synced.
+ **/
+void
+ephy_synchronizable_manager_set_sync_time (EphySynchronizableManager *manager,
+                                           double                     sync_time)
+{
+  EphySynchronizableManagerInterface *iface;
+
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+
+  iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+  iface->set_sync_time (manager, sync_time);
+}
+
+/**
+ * ephy_synchronizable_manager_add:
+ * @manager: an #EphySynchronizableManager
+ * @synchronizable: (transfer none): an #EphySynchronizable
+ *
+ * Adds @synchronizable to the local instance of the collection managed by @manager.
+ **/
+void
+ephy_synchronizable_manager_add (EphySynchronizableManager *manager,
+                                 EphySynchronizable        *synchronizable)
+{
+  EphySynchronizableManagerInterface *iface;
+
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+  iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+  iface->add (manager, synchronizable);
+}
+
+/**
+ * ephy_synchronizable_manager_remove:
+ * @manager: an #EphySynchronizableManager
+ * @synchronizable: (transfer full): an #EphySynchronizable
+ *
+ * Removes @synchronizable from the local instance of the collection managed by @manager.
+ **/
+void
+ephy_synchronizable_manager_remove (EphySynchronizableManager *manager,
+                                    EphySynchronizable        *synchronizable)
+{
+  EphySynchronizableManagerInterface *iface;
+
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+  iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+  iface->remove (manager, synchronizable);
+}
+
+/**
+ * ephy_synchronizable_manager_merge_remotes:
+ * @manager: an #EphySynchronizableManager
+ * @is_initial: a boolean saying whether the collection managed by @manager
+ *              requires an initial sync (i.e. a first time sync)
+ * @remotes_deleted: (transfer none): a #GList holding the #EphySynchronizable
+ *                   objects that were removed remotely from the server.
+ * @remotes_updated: (transfer none): a #GList holding the #EphySynchronizable
+ *                   objects that were updated remotely on the server.
+ *
+ * Merges a list of remote-deleted objects and a list of remote-updated objects
+ * with the local objects in @manager's collection.
+ *
+ * Return value: (transfer full): a #GList holding the #EphySynchronizable
+ *               objects that need to be re-uploaded to server.
+ **/
+GList *
+ephy_synchronizable_manager_merge_remotes (EphySynchronizableManager *manager,
+                                           gboolean                   is_initial,
+                                           GList                     *remotes_deleted,
+                                           GList                     *remotes_updated)
+{
+  EphySynchronizableManagerInterface *iface;
+
+  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), NULL);
+
+  iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+  return iface->merge_remotes (manager, is_initial, remotes_deleted, remotes_updated);
+}
diff --git a/src/sync/ephy-synchronizable-manager.h b/src/sync/ephy-synchronizable-manager.h
new file mode 100644
index 0000000..b35112c
--- /dev/null
+++ b/src/sync/ephy-synchronizable-manager.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ephy-synchronizable.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SYNCHRONIZABLE_MANAGER (ephy_synchronizable_manager_get_type ())
+
+G_DECLARE_INTERFACE (EphySynchronizableManager, ephy_synchronizable_manager, EPHY, SYNCHRONIZABLE_MANAGER, 
GObject)
+
+struct _EphySynchronizableManagerInterface {
+  GTypeInterface parent_iface;
+
+  const char         * (*get_collection_name)     (EphySynchronizableManager *manager);
+  GType                (*get_synchronizable_type) (EphySynchronizableManager *manager);
+  gboolean             (*is_initial_sync)         (EphySynchronizableManager *manager);
+  void                 (*set_is_initial_sync)     (EphySynchronizableManager *manager,
+                                                   gboolean                   is_initial);
+  double               (*get_sync_time)           (EphySynchronizableManager *manager);
+  void                 (*set_sync_time)           (EphySynchronizableManager *manager,
+                                                   double                     sync_time);
+  void                 (*add)                     (EphySynchronizableManager *manager,
+                                                   EphySynchronizable        *synchronizable);
+  void                 (*remove)                  (EphySynchronizableManager *manager,
+                                                   EphySynchronizable        *synchronizable);
+  GList              * (*merge_remotes)           (EphySynchronizableManager *manager,
+                                                   gboolean                   is_initial,
+                                                   GList                     *remotes_deleted,
+                                                   GList                     *remotes_updated);
+};
+
+const char         *ephy_synchronizable_manager_get_collection_name     (EphySynchronizableManager *manager);
+GType               ephy_synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager);
+gboolean            ephy_synchronizable_manager_is_initial_sync         (EphySynchronizableManager *manager);
+void                ephy_synchronizable_manager_set_is_initial_sync     (EphySynchronizableManager *manager,
+                                                                         gboolean                   
is_initial);
+double              ephy_synchronizable_manager_get_sync_time           (EphySynchronizableManager *manager);
+void                ephy_synchronizable_manager_set_sync_time           (EphySynchronizableManager *manager,
+                                                                         double                     
sync_time);
+void                ephy_synchronizable_manager_add                     (EphySynchronizableManager *manager,
+                                                                         EphySynchronizable        
*synchronizable);
+void                ephy_synchronizable_manager_remove                  (EphySynchronizableManager *manager,
+                                                                         EphySynchronizable        
*synchronizable);
+GList              *ephy_synchronizable_manager_merge_remotes           (EphySynchronizableManager *manager,
+                                                                         gboolean                   
is_initial,
+                                                                         GList                     
*remotes_deleted,
+                                                                         GList                     
*remotes_updated);
+
+G_END_DECLS
diff --git a/src/sync/ephy-synchronizable.c b/src/sync/ephy-synchronizable.c
new file mode 100644
index 0000000..dc86cc4
--- /dev/null
+++ b/src/sync/ephy-synchronizable.c
@@ -0,0 +1,273 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-synchronizable.h"
+
+G_DEFINE_INTERFACE (EphySynchronizable, ephy_synchronizable, JSON_TYPE_SERIALIZABLE);
+
+static void
+ephy_synchronizable_default_init (EphySynchronizableInterface *iface)
+{
+  iface->get_id = ephy_synchronizable_get_id;
+  iface->get_time_modified = ephy_synchronizable_get_time_modified;
+  iface->set_time_modified = ephy_synchronizable_set_time_modified;
+  iface->set_is_uploaded = ephy_synchronizable_set_is_uploaded;
+  iface->to_bso = ephy_synchronizable_to_bso;
+
+  g_object_interface_install_property (iface,
+                                       g_param_spec_string ("id",
+                                                            "ID",
+                                                            "The record's id",
+                                                            "112233445566",
+                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | 
G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * ephy_synchronizable_get_id:
+ * @synchronizable: an #EphySynchronizable
+ *
+ * Returns @synchronizable's id.
+ *
+ * Return value: (transfer none): @synchronizable's id
+ **/
+const char *
+ephy_synchronizable_get_id (EphySynchronizable *synchronizable)
+{
+  EphySynchronizableInterface *iface;
+
+  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable), NULL);
+
+  iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+  return iface->get_id (synchronizable);
+}
+
+/**
+ * ephy_synchronizable_get_time_modified:
+ * @synchronizable: an #EphySynchronizable
+ *
+ * Returns @synchronizable's last modification time.
+ *
+ * Return value: @synchronizable's last modification time
+ **/
+double
+ephy_synchronizable_get_time_modified (EphySynchronizable *synchronizable)
+{
+  EphySynchronizableInterface *iface;
+
+  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable), 0);
+
+  iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+  return iface->get_time_modified (synchronizable);
+}
+
+/**
+ * ephy_synchronizable_set_time_modified:
+ * @synchronizable: an #EphySynchronizable
+ * @time_modified: the last modification time
+ *
+ * Sets @time_modified as @synchronizable's last modification time.
+ **/
+void
+ephy_synchronizable_set_time_modified (EphySynchronizable *synchronizable,
+                                       double              time_modified)
+{
+  EphySynchronizableInterface *iface;
+
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+  iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+  iface->set_time_modified (synchronizable, time_modified);
+}
+
+/**
+ * ephy_synchronizable_set_is_uploaded:
+ * @synchronizable: an #EphySynchronizable
+ * @uploaded: TRUE if @synchronizable is uploaded to server, FALSE otherwise
+ *
+ * Sets @synchronizable's uploaded flag.
+ **/
+void
+ephy_synchronizable_set_is_uploaded (EphySynchronizable *synchronizable,
+                                     gboolean            uploaded)
+{
+  EphySynchronizableInterface *iface;
+
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+  iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+  iface->set_is_uploaded (synchronizable, uploaded);
+}
+
+/**
+ * ephy_synchronizable_to_bso:
+ * @synchronizable: an #EphySynchronizable
+ * @bundle: a %SyncCryptoKeyBundle holding the encryption key and the HMAC key
+ *          used to validate and encrypt the Basic Storage Object
+ *
+ * Converts an #EphySynchronizable into its JSON string representation
+ * of a Basic Storage Object from the client's point of view
+ * (i.e. the %modified field is missing). Check the BSO format documentation
+ * (https://docs.services.mozilla.com/storage/apis-1.5.html#basic-storage-object)
+ * for more details.
+ *
+ * Return value: (transfer full): @synchronizable's BSO representation as a #JsonNode
+ **/
+JsonNode *
+ephy_synchronizable_to_bso (EphySynchronizable  *synchronizable,
+                            SyncCryptoKeyBundle *bundle)
+{
+  EphySynchronizableInterface *iface;
+
+  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable), NULL);
+  g_return_val_if_fail (bundle, NULL);
+
+  iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+  return iface->to_bso (synchronizable, bundle);
+}
+
+/**
+ * ephy_synchronizable_from_bso:
+ * @bso: a #JsonNode representing the Basic Storage Object
+ * @gtype: the #GType of object to construct
+ * @bundle: a %SyncCryptoKeyBundle holding the encryption key and the HMAC key
+ *          used to validate and decrypt the Basic Storage Object
+ * @is_deleted: return value for a flag that says whether the object
+ *              was marked as deleted
+ *
+ * Converts a JSON object representing the Basic Storage Object
+ * from the server's point of view (i.e. the %modified field is present)
+ * into an object of type @gtype. See the BSO format documentation
+ * (https://docs.services.mozilla.com/storage/apis-1.5.html#basic-storage-object)
+ * for more details.
+ *
+ * Note: The @gtype must be a sub-type of #EphySynchronizable (i.e. must
+ * implement the #EphySynchronizable interface). It is up to the caller to cast
+ * the returned #GObject to the type of @gtype.
+ *
+ *  Return value: (transfer full): a #GObject or %NULL
+ **/
+GObject *
+ephy_synchronizable_from_bso (JsonNode            *bso,
+                              GType                gtype,
+                              SyncCryptoKeyBundle *bundle,
+                              gboolean            *is_deleted)
+{
+  GObject *object = NULL;
+  GError *error = NULL;
+  JsonNode *node = NULL;
+  JsonObject *json;
+  char *serialized = NULL;
+  const char *payload = NULL;
+  double time_modified;
+
+  g_return_val_if_fail (bso, NULL);
+  g_return_val_if_fail (bundle, NULL);
+  g_return_val_if_fail (is_deleted, NULL);
+
+  json = json_node_get_object (bso);
+  if (!json) {
+    g_warning ("JSON node does not hold a JSON object");
+    goto out;
+  }
+  payload = json_object_get_string_member (json, "payload");
+  time_modified = json_object_get_double_member (json, "modified");
+  if (!payload || !time_modified) {
+    g_warning ("JSON object has missing or invalid members");
+    goto out;
+  }
+
+  serialized = ephy_sync_crypto_decrypt_record (payload, bundle);
+  if (!serialized) {
+    g_warning ("Failed to decrypt the BSO payload");
+    goto out;
+  }
+  node = json_from_string (serialized, &error);
+  if (error) {
+    g_warning ("Decrypted text is not a valid JSON: %s", error->message);
+    goto out;
+  }
+  json = json_node_get_object (node);
+  if (!json) {
+    g_warning ("Decrypted JSON node does not hold a JSON object");
+    goto out;
+  }
+  *is_deleted = json_object_has_member (json, "deleted");
+
+  object = json_gobject_from_data (gtype, serialized, -1, &error);
+  if (error) {
+    g_warning ("Failed to create GObject from BSO: %s", error->message);
+    goto out;
+  }
+
+  ephy_synchronizable_set_time_modified (EPHY_SYNCHRONIZABLE (object), time_modified);
+  ephy_synchronizable_set_is_uploaded (EPHY_SYNCHRONIZABLE (object), TRUE);
+
+out:
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
+  g_free (serialized);
+
+  return object;
+}
+
+/**
+ * ephy_synchronizable_default_to_bso:
+ * @synchronizable: an #EphySynchronizable
+ * @bundle: a %SyncCryptoKeyBundle holding the encryption key and the HMAC key
+ *          used to validate and encrypt the Basic Storage Object
+ *
+ * Calls the default implementation of the #EphySynchronizable
+ * #EphySynchronizableInterface.to_bso() virtual function.
+ *
+ * This function can be used inside a custom implementation of the
+ * #EphySynchronizableInterface.to_bso() virtual function in lieu of
+ * calling the default implementation through g_type_default_interface_peek().
+ *
+ * Return value: (transfer full): @synchronizable's BSO representation as a #JsonNode
+ **/
+JsonNode *
+ephy_synchronizable_default_to_bso (EphySynchronizable  *synchronizable,
+                                    SyncCryptoKeyBundle *bundle)
+{
+  JsonNode *bso;
+  JsonObject *object;
+  char *serialized;
+  char *payload;
+
+  g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable), NULL);
+  g_return_val_if_fail (bundle, NULL);
+
+  serialized = json_gobject_to_data (G_OBJECT (synchronizable), NULL);
+  payload = ephy_sync_crypto_encrypt_record (serialized, bundle);
+  bso = json_node_new (JSON_NODE_OBJECT);
+  object = json_object_new ();
+  json_object_set_string_member (object, "id", ephy_synchronizable_get_id (synchronizable));
+  json_object_set_string_member (object, "payload", payload);
+  json_node_set_object (bso, object);
+
+  json_object_unref (object);
+  g_free (payload);
+  g_free (serialized);
+
+  return bso;
+}
diff --git a/src/sync/ephy-synchronizable.h b/src/sync/ephy-synchronizable.h
new file mode 100644
index 0000000..f06d48b
--- /dev/null
+++ b/src/sync/ephy-synchronizable.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ephy-sync-crypto.h"
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SYNCHRONIZABLE (ephy_synchronizable_get_type ())
+
+G_DECLARE_INTERFACE (EphySynchronizable, ephy_synchronizable, EPHY, SYNCHRONIZABLE, JsonSerializable)
+
+struct _EphySynchronizableInterface {
+  GTypeInterface parent_iface;
+
+  const char * (*get_id)            (EphySynchronizable  *synchronizable);
+  double       (*get_time_modified) (EphySynchronizable  *synchronizable);
+  void         (*set_time_modified) (EphySynchronizable  *synchronizable,
+                                     double               time_modified);
+  void         (*set_is_uploaded)   (EphySynchronizable  *synchronizable,
+                                     gboolean             uploaded);
+  JsonNode *   (*to_bso)            (EphySynchronizable  *synchronizable,
+                                     SyncCryptoKeyBundle *bundle);
+};
+
+const char *ephy_synchronizable_get_id             (EphySynchronizable  *synchronizable);
+double      ephy_synchronizable_get_time_modified  (EphySynchronizable  *synchronizable);
+void        ephy_synchronizable_set_time_modified  (EphySynchronizable  *synchronizable,
+                                                    double               time_modified);
+void        ephy_synchronizable_set_is_uploaded    (EphySynchronizable  *synchronizable,
+                                                    gboolean             uploaded);
+JsonNode   *ephy_synchronizable_to_bso             (EphySynchronizable  *synchronizable,
+                                                    SyncCryptoKeyBundle *bundle);
+/* This can't be an interface method because we lack the EphySynchronizable object. */
+GObject    *ephy_synchronizable_from_bso           (JsonNode            *bso,
+                                                    GType                gtype,
+                                                    SyncCryptoKeyBundle *bundle,
+                                                    gboolean            *is_deleted);
+/* Default implementations. */
+JsonNode   *ephy_synchronizable_default_to_bso     (EphySynchronizable  *synchronizable,
+                                                    SyncCryptoKeyBundle *bundle);
+
+G_END_DECLS



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]