[epiphany/wip/sync: 1/4] sync: Replace old bookmarks sync code



commit 95a8d543cd85464a6df2995796879aa33e8773bf
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date:   Sat Apr 29 16:38:26 2017 +0300

    sync: Replace old bookmarks sync code
    
    We can now actually sync with Firefox

 data/org.gnome.epiphany.gschema.xml           |   48 +-
 lib/ephy-prefs.h                              |   11 +-
 lib/ephy-settings.h                           |    1 +
 meson.build                                   |    2 +-
 po/POTFILES.in                                |    1 -
 src/bookmarks/ephy-add-bookmark-popover.c     |   17 +-
 src/bookmarks/ephy-bookmark-properties-grid.c |  148 +-
 src/bookmarks/ephy-bookmark.c                 |  443 ++--
 src/bookmarks/ephy-bookmark.h                 |   80 +-
 src/bookmarks/ephy-bookmarks-export.c         |    4 +-
 src/bookmarks/ephy-bookmarks-import.c         |   23 +-
 src/bookmarks/ephy-bookmarks-manager.c        |  439 ++++-
 src/ephy-shell.c                              |   31 +-
 src/meson.build                               |    4 +-
 src/prefs-dialog.c                            |  655 ++++---
 src/profile-migrator/ephy-profile-migrator.c  |    7 +-
 src/resources/gtk/prefs-dialog.ui             |  181 ++-
 src/sync/ephy-sync-crypto.c                   | 1130 +++++++----
 src/sync/ephy-sync-crypto.h                   |  138 +-
 src/sync/ephy-sync-secret.c                   |  258 ---
 src/sync/ephy-sync-secret.h                   |   49 -
 src/sync/ephy-sync-service.c                  | 2725 ++++++++++++++++---------
 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        |  249 +++
 src/sync/ephy-synchronizable-manager.h        |   71 +
 src/sync/ephy-synchronizable.c                |  246 +++
 src/sync/ephy-synchronizable.h                |   59 +
 29 files changed, 4539 insertions(+), 2799 deletions(-)
---
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/meson.build b/meson.build
index e87c497..1119843 100644
--- a/meson.build
+++ b/meson.build
@@ -59,7 +59,7 @@ gtk_unix_print_dep = dependency('gtk+-unix-print-3.0', version: gtk_requirement)
 hogweed_dep = dependency('hogweed', version: nettle_requirement)
 icu_uc_dep = dependency('icu-uc', version: '>= 4.6')
 iso_codes_dep = dependency('iso-codes', version: '>= 0.35')
-json_glib_dep = dependency('json-glib-1.0', version: '>= 1.2.0')
+json_glib_dep = dependency('json-glib-1.0', version: '>= 1.2.4')
 libnotify_dep = dependency('libnotify', version: '>= 0.5.1')
 libsecret_dep = dependency('libsecret-1', version: '>= 0.14')
 libsoup_dep = dependency('libsoup-2.4', version: '>= 2.48.0')
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 939bfd7..8ca7f32 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -60,6 +60,5 @@ src/resources/gtk/prefs-lang-dialog.ui
 src/resources/gtk/search-engine-dialog.ui
 src/resources/gtk/shortcuts-dialog.ui
 src/search-provider/ephy-search-provider.c
-src/sync/ephy-sync-secret.c
 src/sync/ephy-sync-service.c
 src/window-commands.c
diff --git a/src/bookmarks/ephy-add-bookmark-popover.c b/src/bookmarks/ephy-add-bookmark-popover.c
index 195396d..76019ee 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;
@@ -113,6 +112,7 @@ static void
 ephy_add_bookmark_popover_closed_cb (GtkPopover *popover,
                                      gpointer    user_data)
 {
+  EphyAddBookmarkPopover *self;
   EphyBookmarksManager *manager;
 
   g_assert (EPHY_IS_ADD_BOOKMARK_POPOVER (popover));
@@ -122,6 +122,10 @@ ephy_add_bookmark_popover_closed_cb (GtkPopover *popover,
   ephy_bookmarks_manager_save_to_file_async (manager, NULL,
                                              ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
                                              NULL);
+
+  self = EPHY_ADD_BOOKMARK_POPOVER (popover);
+  g_clear_pointer (&self->address, g_free);
+  g_clear_pointer (&self->grid, gtk_widget_destroy);
 }
 
 static void
@@ -182,9 +186,6 @@ ephy_add_bookmark_popover_update_bookmarked_status_cb (EphyAddBookmarkPopover *s
                                              NULL);
 
   gtk_widget_hide (GTK_WIDGET (self));
-
-  g_clear_pointer (&self->address, g_free);
-  g_clear_pointer (&self->grid, gtk_widget_destroy);
 }
 
 void
@@ -206,14 +207,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",
@@ -221,12 +225,9 @@ ephy_add_bookmark_popover_show (EphyAddBookmarkPopover *self)
                            self,
                            G_CONNECT_SWAPPED);
 
-  if (self->grid)
-    gtk_widget_destroy (self->grid);
   self->grid = ephy_bookmark_properties_grid_new (bookmark,
                                                   EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_POPOVER,
                                                   GTK_WIDGET (self));
-
   gtk_container_add (GTK_CONTAINER (self), self->grid);
   gtk_popover_set_default_widget (GTK_POPOVER (self),
                                   ephy_bookmark_properties_grid_get_add_tag_button 
(EPHY_BOOKMARK_PROPERTIES_GRID (self->grid)));
diff --git a/src/bookmarks/ephy-bookmark-properties-grid.c b/src/bookmarks/ephy-bookmark-properties-grid.c
index b538c63..2a4d9ce 100644
--- a/src/bookmarks/ephy-bookmark-properties-grid.c
+++ b/src/bookmarks/ephy-bookmark-properties-grid.c
@@ -25,7 +25,6 @@
 #include "ephy-bookmarks-manager.h"
 #include "ephy-debug.h"
 #include "ephy-shell.h"
-#include "ephy-sync-service.h"
 #include "ephy-type-builtins.h"
 #include "ephy-uri-helpers.h"
 
@@ -33,16 +32,17 @@
 #include <libsoup/soup.h>
 #include <string.h>
 
-
 struct _EphyBookmarkPropertiesGrid {
   GtkGrid                         parent_instance;
 
+  EphyBookmarksManager           *manager;
   EphyBookmark                   *bookmark;
+  gboolean                        bookmark_is_modified;
+  gboolean                        bookmark_is_removed;
+
   EphyBookmarkPropertiesGridType  type;
   GtkWidget                      *parent;
 
-  EphyBookmarksManager           *manager;
-
   GtkWidget                      *popover_bookmark_label;
   GtkWidget                      *name_entry;
   GtkWidget                      *address_entry;
@@ -51,10 +51,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)
@@ -243,14 +239,12 @@ ephy_bookmarks_properties_grid_actions_remove_bookmark (GSimpleAction *action,
                                                         GVariant      *value,
                                                         gpointer       user_data)
 {
-  EphySyncService *service;
   EphyBookmarkPropertiesGrid *self = user_data;
 
   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);
-  ephy_bookmarks_manager_remove_bookmark (self->manager,  self->bookmark);
+  self->bookmark_is_removed = TRUE;
+  ephy_bookmarks_manager_remove_bookmark (self->manager, self->bookmark);
 
   if (self->type == EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_DIALOG)
     gtk_widget_destroy (self->parent);
@@ -278,6 +272,57 @@ ephy_bookmark_properties_grid_buffer_text_changed_cb (EphyBookmarkPropertiesGrid
 }
 
 static void
+ephy_bookmark_properties_grid_bookmark_title_changed_cb (EphyBookmarkPropertiesGrid *self,
+                                                         EphyBookmark               *bookmark,
+                                                         EphyBookmarksManager       *manager)
+{
+  g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+  g_assert (EPHY_IS_BOOKMARK (bookmark));
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (manager));
+
+  self->bookmark_is_modified = TRUE;
+}
+
+static void
+ephy_bookmark_properties_grid_bookmark_url_changed_cb (EphyBookmarkPropertiesGrid *self,
+                                                       EphyBookmark               *bookmark,
+                                                       EphyBookmarksManager       *manager)
+{
+  g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+  g_assert (EPHY_IS_BOOKMARK (bookmark));
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (manager));
+
+  self->bookmark_is_modified = TRUE;
+}
+
+static void
+ephy_bookmark_properties_grid_bookmark_tag_added_cb (EphyBookmarkPropertiesGrid *self,
+                                                     EphyBookmark               *bookmark,
+                                                     const char                 *tag,
+                                                     EphyBookmarksManager       *manager)
+{
+  g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+  g_assert (EPHY_IS_BOOKMARK (bookmark));
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (manager));
+
+  self->bookmark_is_modified = TRUE;
+}
+
+static void
+ephy_bookmark_properties_grid_bookmark_tag_removed_cb (EphyBookmarkPropertiesGrid *self,
+                                                       EphyBookmark               *bookmark,
+                                                       const char                 *tag,
+                                                       EphyBookmarksManager       *manager)
+{
+  g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+  g_assert (EPHY_IS_BOOKMARK (bookmark));
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (manager));
+  g_assert (tag);
+
+  self->bookmark_is_modified = TRUE;
+}
+
+static void
 ephy_bookmark_properties_grid_set_property (GObject      *object,
                                             guint         prop_id,
                                             const GValue *value,
@@ -323,7 +368,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",
@@ -336,15 +380,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);
@@ -357,11 +399,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);
@@ -375,56 +414,13 @@ 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);
+  if (self->bookmark_is_modified && !self->bookmark_is_removed)
+    g_signal_emit_by_name (self->manager, "synchronizable-modified", self->bookmark);
+
   ephy_bookmarks_manager_save_to_file_async (self->manager, NULL,
                                              ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
                                              NULL);
@@ -491,6 +487,26 @@ ephy_bookmark_properties_grid_init (EphyBookmarkPropertiesGrid *self)
   gtk_widget_init_template (GTK_WIDGET (self));
 
   self->manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
+  g_signal_connect_object (self->manager,
+                           "bookmark-title-changed",
+                           G_CALLBACK (ephy_bookmark_properties_grid_bookmark_title_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->manager,
+                           "bookmark-url-changed",
+                           G_CALLBACK (ephy_bookmark_properties_grid_bookmark_url_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->manager,
+                           "bookmark-tag-added",
+                           G_CALLBACK (ephy_bookmark_properties_grid_bookmark_tag_added_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->manager,
+                           "bookmark-tag-removed",
+                           G_CALLBACK (ephy_bookmark_properties_grid_bookmark_tag_removed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
 
   gtk_flow_box_set_sort_func (GTK_FLOW_BOX (self->tags_box),
                               (GtkFlowBoxSortFunc)flow_box_sort_func,
diff --git a/src/bookmarks/ephy-bookmark.c b/src/bookmarks/ephy-bookmark.c
index 6b19dae..0bb4cbb 100644
--- a/src/bookmarks/ephy-bookmark.c
+++ b/src/bookmarks/ephy-bookmark.c
@@ -19,16 +19,17 @@
  */
 
 #include "config.h"
-
 #include "ephy-bookmark.h"
 
-#include "ephy-shell.h"
-#include "ephy-sync-crypto.h"
-#include "ephy-sync-utils.h"
+#include "ephy-bookmarks-manager.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,27 +39,36 @@ 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;
-  gboolean     uploaded;
+  char        *type;
+  char        *parent_id;
+  char        *parent_name;
+  gboolean     load_in_sidebar;
+  double       server_time_modified;
 };
 
-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,
-  PROP_TIME_ADDED,
-  PROP_TITLE,
-  PROP_URL,
+  PROP_TIME_ADDED,      /* Epiphany */
+  PROP_ID,              /* Firefox Sync */
+  PROP_TITLE,           /* Epiphany && Firefox Sync */
+  PROP_BMK_URI,         /* Epiphany && Firefox Sync */
+  PROP_TAGS,            /* Epiphany && Firefox Sync */
+  PROP_TYPE,            /* Firefox Sync */
+  PROP_PARENT_ID,       /* Firefox Sync */
+  PROP_PARENT_NAME,     /* Firefox Sync */
+  PROP_LOAD_IN_SIDEBAR, /* Firefox Sync */
   LAST_PROP
 };
 
@@ -80,46 +90,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 +177,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 +192,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",
@@ -163,6 +201,13 @@ ephy_bookmark_class_init (EphyBookmarkClass *klass)
                         0,
                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
+  obj_properties[PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "The bookmark's id",
+                         "Default bookmark id",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
   obj_properties[PROP_TITLE] =
     g_param_spec_string ("title",
                          "Title",
@@ -170,13 +215,47 @@ 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);
 
   signals[TAG_ADDED] =
@@ -201,92 +280,24 @@ 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 *
-ephy_bookmark_json_serializable_serialize_property (JsonSerializable *serializable,
-                                                    const char       *name,
-                                                    const GValue     *value,
-                                                    GParamSpec       *pspec)
-{
-  JsonNode *node = NULL;
-
-  if (g_strcmp0 (name, "tags") == 0) {
-    GSequence *tags;
-    GSequenceIter *iter;
-    JsonArray *array;
-
-    node = json_node_new (JSON_NODE_ARRAY);
-    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));
-    }
-
-    json_node_set_array (node, array);
-  } else {
-    node = serializable_iface->serialize_property (serializable, name,
-                                                   value, pspec);
-  }
-
-  return node;
-}
-
-static gboolean
-ephy_bookmark_json_serializable_deserialize_property (JsonSerializable *serializable,
-                                                      const char       *name,
-                                                      GValue           *value,
-                                                      GParamSpec       *pspec,
-                                                      JsonNode         *node)
-{
-  if (g_strcmp0 (name, "tags") == 0) {
-    GSequence *tags;
-    JsonArray *array;
-    const char *tag;
-
-    g_assert (JSON_NODE_HOLDS_ARRAY (node));
-    array = json_node_get_array (node);
-    tags = g_sequence_new (g_free);
-
-    for (gsize i = 0; i < json_array_get_length (array); i++) {
-      tag = json_node_get_string (json_array_get_element (array, i));
-      g_sequence_insert_sorted (tags, g_strdup (tag),
-                                (GCompareDataFunc)ephy_bookmark_tags_compare, NULL);
-    }
-
-    g_value_set_pointer (value, tags);
-  } else {
-    serializable_iface->deserialize_property (serializable, name,
-                                              value, pspec, node);
-  }
-
-  return TRUE;
-}
-
-static void
-json_serializable_iface_init (gpointer g_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;
 }
 
 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,37 +375,23 @@ ephy_bookmark_get_id (EphyBookmark *self)
 }
 
 void
-ephy_bookmark_set_modification_time (EphyBookmark *self,
-                                     double        modified)
-{
-  g_return_if_fail (EPHY_IS_BOOKMARK (self));
-
-  self->modified = modified;
-}
-
-double
-ephy_bookmark_get_modification_time (EphyBookmark *self)
-{
-  g_return_val_if_fail (EPHY_IS_BOOKMARK (self), -1);
-
-  return self->modified;
-}
-
-void
 ephy_bookmark_set_is_uploaded (EphyBookmark *self,
                                gboolean      uploaded)
 {
-  g_return_if_fail (EPHY_IS_BOOKMARK (self));
 
-  self->uploaded = uploaded;
+  /* FIXME: This is no longer used for Firefox Sync, but bookmarks import/export
+   * expects it. We need to delete it and write a migrator for bookmarks. */
+  g_return_if_fail (EPHY_IS_BOOKMARK (self));
 }
 
 gboolean
 ephy_bookmark_is_uploaded (EphyBookmark *self)
 {
+  /* FIXME: This is no longer used for Firefox Sync, but bookmarks import/export
+   * expects it. We need to delete it and write a migrator for bookmarks. */
   g_return_val_if_fail (EPHY_IS_BOOKMARK (self), FALSE);
 
-  return self->uploaded;
+  return FALSE;
 }
 
 void
@@ -517,90 +514,102 @@ ephy_bookmark_tags_compare (const char *tag1, const char *tag2)
   return result;
 }
 
-char *
-ephy_bookmark_to_bso (EphyBookmark *self)
+static JsonNode *
+serializable_serialize_property (JsonSerializable *serializable,
+                                 const char       *name,
+                                 const GValue     *value,
+                                 GParamSpec       *pspec)
 {
-  EphySyncService *service;
-  guint8 *encrypted;
-  guint8 *sync_key;
-  char *serialized;
-  char *payload;
-  char *bso;
-  gsize length;
+  JsonNode *node = NULL;
 
-  g_return_val_if_fail (EPHY_IS_BOOKMARK (self), NULL);
+  if (g_strcmp0 (name, "tags") == 0) {
+    GSequence *tags;
+    GSequenceIter *iter;
+    JsonArray *array;
+
+    node = json_node_new (JSON_NODE_ARRAY);
+    array = json_array_new ();
+    tags = g_value_get_pointer (value);
 
-  /* 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;
+    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 = json_serializable_default_serialize_property (serializable, name, value, pspec);
+  }
+
+  return node;
 }
 
-EphyBookmark *
-ephy_bookmark_from_bso (JsonObject *bso)
+static gboolean
+serializable_deserialize_property (JsonSerializable *serializable,
+                                   const char       *name,
+                                   GValue           *value,
+                                   GParamSpec       *pspec,
+                                   JsonNode         *node)
 {
-  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;
+  if (g_strcmp0 (name, "tags") == 0) {
+    GSequence *tags;
+    JsonArray *array;
+    const char *tag;
+
+    g_assert (JSON_NODE_HOLDS_ARRAY (node));
+    array = json_node_get_array (node);
+    tags = g_sequence_new (g_free);
+
+    for (gsize i = 0; i < json_array_get_length (array); i++) {
+      tag = json_node_get_string (json_array_get_element (array, i));
+      g_sequence_insert_sorted (tags, g_strdup (tag),
+                                (GCompareDataFunc)ephy_bookmark_tags_compare, NULL);
+    }
+
+    g_value_set_pointer (value, tags);
+
+    return TRUE;
   }
 
-  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);
+  return json_serializable_default_deserialize_property (serializable, name, value, pspec, node);
+}
 
-out:
-  g_free (decoded);
-  g_free (decrypted);
+static void
+json_serializable_iface_init (JsonSerializableIface *iface)
+{
+  iface->serialize_property = serializable_serialize_property;
+  iface->deserialize_property = serializable_deserialize_property;
+}
 
-  return bookmark;
+static const char *
+synchronizable_get_id (EphySynchronizable *synchronizable)
+{
+  return ephy_bookmark_get_id (EPHY_BOOKMARK (synchronizable));
+}
+
+static double
+synchronizable_get_server_time_modified (EphySynchronizable *synchronizable)
+{
+  return EPHY_BOOKMARK (synchronizable)->server_time_modified;
+}
+
+static void
+synchronizable_set_server_time_modified (EphySynchronizable *synchronizable,
+                                         double              server_time_modified)
+{
+  EPHY_BOOKMARK (synchronizable)->server_time_modified = server_time_modified;
+}
+
+static void
+ephy_synchronizable_iface_init (EphySynchronizableInterface *iface)
+{
+  iface->get_id = synchronizable_get_id;
+  iface->get_server_time_modified = synchronizable_get_server_time_modified;
+  iface->set_server_time_modified = synchronizable_set_server_time_modified;
+  iface->to_bso = ephy_synchronizable_default_to_bso;
 }
diff --git a/src/bookmarks/ephy-bookmark.h b/src/bookmarks/ephy-bookmark.h
index 9e950d9..cb96a4d 100644
--- a/src/bookmarks/ephy-bookmark.h
+++ b/src/bookmarks/ephy-bookmark.h
@@ -29,48 +29,42 @@ 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_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..ae54187 100644
--- a/src/bookmarks/ephy-bookmarks-export.c
+++ b/src/bookmarks/ephy-bookmarks-export.c
@@ -18,8 +18,10 @@
  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include "config.h"
 #include "ephy-bookmarks-export.h"
 
+#include "ephy-synchronizable.h"
 #include "gvdb-builder.h"
 
 static void
@@ -45,7 +47,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_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE 
(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..1376b3a 100644
--- a/src/bookmarks/ephy-bookmarks-import.c
+++ b/src/bookmarks/ephy-bookmarks-import.c
@@ -18,9 +18,8 @@
  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "ephy-bookmarks-import.h"
-
 #include "config.h"
+#include "ephy-bookmarks-import.h"
 
 #include "ephy-shell.h"
 #include "ephy-sqlite-connection.h"
@@ -59,13 +58,15 @@ get_bookmarks_from_table (GvdbTable *table)
     const char *title;
     gint64 time_added;
     char *id;
-    double modified;
-    gboolean uploaded;
+    double server_time_modified;
+    gboolean is_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,
+                   &server_time_modified, &is_uploaded, &iter);
 
     /* Add all stored tags in a GSequence. */
     tags = g_sequence_new (g_free);
@@ -77,11 +78,10 @@ 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_is_uploaded (bookmark, uploaded);
+    ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (bookmark), server_time_modified);
+    ephy_bookmark_set_is_uploaded (bookmark, is_uploaded);
     g_sequence_prepend (bookmarks, bookmark);
 
     g_variant_unref (value);
@@ -214,7 +214,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 +260,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..2e1c503 100644
--- a/src/bookmarks/ephy-bookmarks-manager.c
+++ b/src/bookmarks/ephy-bookmarks-manager.c
@@ -19,13 +19,14 @@
  */
 
 #include "config.h"
-
 #include "ephy-bookmarks-manager.h"
 
 #include "ephy-bookmarks-export.h"
 #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 +42,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 +76,36 @@ ephy_bookmarks_manager_save_to_file (EphyBookmarksManager *self, GTask *task)
 }
 
 static void
+ephy_bookmarks_manager_copy_tags_from_bookmark (EphyBookmarksManager *self,
+                                                EphyBookmark         *dest,
+                                                EphyBookmark         *source)
+{
+  GSequenceIter *iter;
+
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+  g_assert (EPHY_IS_BOOKMARK (dest));
+  g_assert (EPHY_IS_BOOKMARK (source));
+
+  for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (source));
+       !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
+    ephy_bookmark_add_tag (dest, g_sequence_get (iter));
+}
+
+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);
@@ -188,40 +223,6 @@ ephy_bookmarks_manager_init (EphyBookmarksManager *self)
   ephy_bookmarks_manager_load_from_file (self);
 }
 
-static GType
-ephy_bookmarks_manager_list_model_get_item_type (GListModel *model)
-{
-  return EPHY_TYPE_BOOKMARK;
-}
-
-static guint
-ephy_bookmarks_manager_list_model_get_n_items (GListModel *model)
-{
-  EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (model);
-
-  return g_sequence_get_length (self->bookmarks);
-}
-
-static gpointer
-ephy_bookmarks_manager_list_model_get_item (GListModel *model,
-                                            guint       position)
-{
-  EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (model);
-  GSequenceIter *iter;
-
-  iter = g_sequence_get_iter_at_pos (self->bookmarks, position);
-
-  return g_object_ref (g_sequence_get (iter));
-}
-
-static void
-list_model_iface_init (GListModelInterface *iface)
-{
-  iface->get_item_type = ephy_bookmarks_manager_list_model_get_item_type;
-  iface->get_n_items = ephy_bookmarks_manager_list_model_get_n_items;
-  iface->get_item = ephy_bookmarks_manager_list_model_get_item;
-}
-
 static void
 bookmark_title_changed_cb (EphyBookmark         *bookmark,
                            GParamSpec           *pspec,
@@ -254,7 +255,6 @@ bookmark_tag_removed_cb (EphyBookmark         *bookmark,
   g_signal_emit (self, signals[BOOKMARK_TAG_REMOVED], 0, bookmark, tag);
 }
 
-
 EphyBookmarksManager *
 ephy_bookmarks_manager_new (void)
 {
@@ -267,7 +267,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);
@@ -305,15 +305,16 @@ ephy_bookmarks_search_and_insert_bookmark (GSequence     *bookmarks,
   return NULL;
 }
 
-void
-ephy_bookmarks_manager_add_bookmark (EphyBookmarksManager *self,
-                                     EphyBookmark         *bookmark)
+static void
+add_bookmark_internal (EphyBookmarksManager *self,
+                       EphyBookmark         *bookmark,
+                       gboolean              should_save)
 {
   GSequenceIter *iter;
-  gint position;
+  int position;
 
-  g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
-  g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+  g_assert (EPHY_IS_BOOKMARK (bookmark));
 
   iter = ephy_bookmarks_search_and_insert_bookmark (self->bookmarks,
                                                     g_object_ref (bookmark));
@@ -323,12 +324,24 @@ ephy_bookmarks_manager_add_bookmark (EphyBookmarksManager *self,
     g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
 
     g_signal_emit (self, signals[BOOKMARK_ADDED], 0, bookmark);
+    ephy_bookmarks_manager_watch_bookmark (self, bookmark);
+  }
 
+  if (should_save)
     ephy_bookmarks_manager_save_to_file_async (self, NULL,
                                                
(GAsyncReadyCallback)ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
                                                NULL);
-    ephy_bookmarks_manager_watch_bookmark (self, bookmark);
-  }
+}
+
+void
+ephy_bookmarks_manager_add_bookmark (EphyBookmarksManager *self,
+                                     EphyBookmark         *bookmark)
+{
+  g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
+  g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+
+  add_bookmark_internal (self, bookmark, TRUE);
+  g_signal_emit_by_name (self, "synchronizable-modified", bookmark);
 }
 
 void
@@ -336,26 +349,16 @@ ephy_bookmarks_manager_add_bookmarks (EphyBookmarksManager *self,
                                       GSequence            *bookmarks)
 {
   GSequenceIter *iter;
-  GSequenceIter *new_iter;
-  int position;
 
   g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
   g_return_if_fail (bookmarks != NULL);
 
   for (iter = g_sequence_get_begin_iter (bookmarks);
-       !g_sequence_iter_is_end (iter);
-       iter = g_sequence_iter_next (iter)) {
+       !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
     EphyBookmark *bookmark = g_sequence_get (iter);
 
-    new_iter = ephy_bookmarks_search_and_insert_bookmark (self->bookmarks,
-                                                          g_object_ref (bookmark));
-    if (new_iter) {
-      position = g_sequence_iter_get_position (new_iter);
-      g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
-
-      g_signal_emit (self, signals[BOOKMARK_ADDED], 0, bookmark);
-      ephy_bookmarks_manager_watch_bookmark (self, bookmark);
-    }
+    add_bookmark_internal (self, bookmark, FALSE);
+    g_signal_emit_by_name (self, "synchronizable-modified", bookmark);
   }
 
   ephy_bookmarks_manager_save_to_file_async (self, NULL,
@@ -363,21 +366,21 @@ ephy_bookmarks_manager_add_bookmarks (EphyBookmarksManager *self,
                                              NULL);
 }
 
-void
-ephy_bookmarks_manager_remove_bookmark (EphyBookmarksManager *self,
-                                        EphyBookmark         *bookmark)
+static void
+remove_bookmark_internal (EphyBookmarksManager *self,
+                          EphyBookmark         *bookmark)
 {
   GSequenceIter *iter;
   gint position;
 
-  g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
-  g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+  g_assert (EPHY_IS_BOOKMARK (bookmark));
 
   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));
@@ -400,6 +403,19 @@ ephy_bookmarks_manager_remove_bookmark (EphyBookmarksManager *self,
   g_object_unref (bookmark);
 }
 
+void ephy_bookmarks_manager_remove_bookmark (EphyBookmarksManager *self,
+                                             EphyBookmark         *bookmark)
+{
+  g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
+  g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+
+  g_signal_emit_by_name (EPHY_SYNCHRONIZABLE_MANAGER (self),
+                         "synchronizable-deleted",
+                         bookmark);
+
+  remove_bookmark_internal (self, bookmark);
+}
+
 EphyBookmark *
 ephy_bookmarks_manager_get_bookmark_by_url (EphyBookmarksManager *self,
                                             const char           *url)
@@ -611,3 +627,292 @@ ephy_bookmarks_manager_save_to_file_warn_on_error_cb (GObject      *object,
     g_error_free (error);
   }
 }
+
+static GType
+ephy_bookmarks_manager_list_model_get_item_type (GListModel *model)
+{
+  return EPHY_TYPE_BOOKMARK;
+}
+
+static guint
+ephy_bookmarks_manager_list_model_get_n_items (GListModel *model)
+{
+  EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (model);
+
+  return g_sequence_get_length (self->bookmarks);
+}
+
+static gpointer
+ephy_bookmarks_manager_list_model_get_item (GListModel *model,
+                                            guint       position)
+{
+  EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (model);
+  GSequenceIter *iter;
+
+  iter = g_sequence_get_iter_at_pos (self->bookmarks, position);
+
+  return g_object_ref (g_sequence_get (iter));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = ephy_bookmarks_manager_list_model_get_item_type;
+  iface->get_n_items = ephy_bookmarks_manager_list_model_get_n_items;
+  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);
+
+  add_bookmark_internal (self, bookmark, TRUE);
+  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);
+
+  remove_bookmark_internal (self, bookmark);
+}
+
+static GSList *
+handle_initial_sync (EphyBookmarksManager *self,
+                     GSList               *remote_bookmarks)
+{
+  GSList *to_upload = NULL;
+  EphyBookmark *bookmark;
+  GSequence *bookmarks;
+  GSequenceIter *iter;
+  GHashTable *dont_upload;
+  double timestamp;
+
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+
+  dont_upload = g_hash_table_new (g_str_hash, g_str_equal);
+
+  for (GSList *l = remote_bookmarks; l && l->data; l = l->next) {
+    const char *id;
+    const char *url;
+    char *type;
+    char *parent_id;
+
+    g_object_get (l->data, "type", &type, "parentid", &parent_id, NULL);
+    /* Ignore mobile/unfiled bookmarks and everything that is not of type 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 (l->data))
+      ephy_bookmark_set_time_added (l->data, g_get_real_time ());
+
+    id = ephy_bookmark_get_id (l->data);
+    url = ephy_bookmark_get_url (l->data);
+    bookmark = ephy_bookmarks_manager_get_bookmark_by_id (self, id);
+
+    if (bookmark) {
+      if (!g_strcmp0 (ephy_bookmark_get_url (bookmark), url)) {
+        /* Same id, same url. Merge tags and reupload. */
+        ephy_bookmarks_manager_copy_tags_from_bookmark (self, bookmark, l->data);
+        timestamp = ephy_synchronizable_get_server_time_modified (l->data);
+        ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (bookmark), timestamp);
+      } else {
+        /* Same id, different url. Keep both and upload local one with new id. */
+        char *new_id = ephy_sync_crypto_get_random_sync_id ();
+        ephy_bookmark_set_id (bookmark, new_id);
+        add_bookmark_internal (self, l->data, FALSE);
+        g_hash_table_add (dont_upload, (char *)id);
+        g_free (new_id);
+      }
+    } else {
+      bookmark = ephy_bookmarks_manager_get_bookmark_by_url (self, url);
+      if (bookmark) {
+        /* Different id, same url. Keep remote id, merge tags and reupload. */
+        ephy_bookmark_set_id (bookmark, id);
+        ephy_bookmarks_manager_copy_tags_from_bookmark (self, bookmark, l->data);
+        timestamp = ephy_synchronizable_get_server_time_modified (l->data);
+        ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (bookmark), timestamp);
+      } else {
+        /* Different id, different url. Add remote bookmark. */
+        add_bookmark_internal (self, l->data, FALSE);
+        g_hash_table_add (dont_upload, (char *)id);
+      }
+    }
+
+    /* In any case, create new tags from the remote bookmark if any. */
+    ephy_bookmarks_manager_create_tags_from_bookmark (self, l->data);
+
+next:
+    g_free (type);
+    g_free (parent_id);
+  }
+
+  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)) {
+    bookmark = g_sequence_get (iter);
+    if (!g_hash_table_contains (dont_upload, ephy_bookmark_get_id (bookmark)))
+      to_upload = g_slist_prepend (to_upload, g_object_ref (bookmark));
+  }
+
+  /* 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_unref (dont_upload);
+
+  return to_upload;
+}
+
+static GSList *
+handle_regular_sync (EphyBookmarksManager *self,
+                     GSList               *updated_bookmarks,
+                     GSList               *deleted_bookmarks)
+{
+  GSList *to_upload = NULL;
+  EphyBookmark *bookmark;
+  double timestamp;
+
+  g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+
+  for (GSList *l = deleted_bookmarks; l && l->data; l = l->next) {
+    bookmark = ephy_bookmarks_manager_get_bookmark_by_id (self, ephy_bookmark_get_id (l->data));
+    if (bookmark)
+      remove_bookmark_internal (self, bookmark);
+  }
+
+  for (GSList *l = updated_bookmarks; l && l->data; l = l->next) {
+    const char *id;
+    const char *url;
+    char *type;
+    char *parent_id;
+
+    g_object_get (l->data, "type", &type, "parentid", &parent_id, NULL);
+    /* Ignore mobile/unfiled bookmarks and everything that is not of type 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 (l->data))
+      ephy_bookmark_set_time_added (l->data, g_get_real_time ());
+
+    id = ephy_bookmark_get_id (l->data);
+    url = ephy_bookmark_get_url (l->data);
+    bookmark = ephy_bookmarks_manager_get_bookmark_by_id (self, id);
+
+    if (bookmark) {
+      /* Same id. Overwrite local bookmark. */
+      remove_bookmark_internal (self, bookmark);
+      add_bookmark_internal (self, l->data, FALSE);
+    } else {
+      bookmark = ephy_bookmarks_manager_get_bookmark_by_url (self, url);
+      if (bookmark) {
+        /* Different id, same url. Keep remote id, merge tags and reupload. */
+        ephy_bookmark_set_id (bookmark, id);
+        ephy_bookmarks_manager_copy_tags_from_bookmark (self, bookmark, l->data);
+        timestamp = ephy_synchronizable_get_server_time_modified (l->data);
+        ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (bookmark), timestamp);
+        to_upload = g_slist_prepend (to_upload, g_object_ref (bookmark));
+      } else {
+        /* Different id, different url. Add remote bookmark. */
+        add_bookmark_internal (self, l->data, FALSE);
+      }
+    }
+
+    /* In any case, create new tags from the remote bookmark if any. */
+    ephy_bookmarks_manager_create_tags_from_bookmark (self, l->data);
+
+next:
+    g_free (type);
+    g_free (parent_id);
+  }
+
+  /* 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);
+
+  return to_upload;
+}
+
+static GSList *
+synchronizable_manager_merge_remotes (EphySynchronizableManager *manager,
+                                      gboolean                   is_initial,
+                                      GSList                    *remotes_deleted,
+                                      GSList                    *remotes_updated)
+{
+  if (is_initial)
+    return handle_initial_sync (EPHY_BOOKMARKS_MANAGER (manager), remotes_updated);
+
+  return handle_regular_sync (EPHY_BOOKMARKS_MANAGER (manager), remotes_updated, remotes_deleted);
+}
+
+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;
+}
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index d505faf..4801cfd 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -48,7 +48,6 @@
 #include <gdk/gdkx.h>
 #include <gtk/gtk.h>
 
-
 struct _EphyShell {
   EphyEmbedShell parent_instance;
 
@@ -309,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);
@@ -367,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/meson.build b/src/meson.build
index cdab0e6..38eae58 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -41,9 +41,9 @@ libephymain_sources = [
   'popup-commands.c',
   'prefs-dialog.c',
   'sync/ephy-sync-crypto.c',
-  'sync/ephy-sync-secret.c',
   'sync/ephy-sync-service.c',
-  'sync/ephy-sync-utils.c',
+  'sync/ephy-synchronizable.c',
+  'sync/ephy-synchronizable-manager.c',
   'window-commands.c',
   enums
 ]
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index e9d5410..45f3e27 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"
@@ -54,7 +52,7 @@
 #include <string.h>
 
 #define DOWNLOAD_BUTTON_WIDTH   8
-#define FXA_IFRAME_URL "https://accounts.firefox.com/signin?service=sync&context=fx_ios_v1";
+#define FXA_IFRAME_URL "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v1";
 
 enum {
   COL_LANG_NAME,
@@ -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,113 @@ 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_bookmarks_toggled_cb (GtkToggleButton *button,
+                           PrefsDialog     *dialog)
 {
-  char *text;
-  char *account;
+  EphyBookmarksManager *manager;
 
-  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);
+  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
 
-  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);
-
-  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);
+}
+
+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));
 
-  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);
+  if (!error) {
+    char *text;
+    char *account;
 
-    g_free (bundle);
-    fxa_callback_data_free (data);
-    data->dialog->fxa_id = 0;
+    /* 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_REMOVE;
+    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);
   }
-
-  return G_SOURCE_CONTINUE;
 }
 
 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 +295,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 *session_token;
+  const char *key_fetch_token;
+  const char *unwrap_b_key;
 
-  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");
+  session_token = json_object_get_string_member (data, "sessionToken");
+  key_fetch_token = json_object_get_string_member (data, "keyFetchToken");
+  unwrap_b_key = json_object_get_string_member (data, "unwrapBKey");
+  if (!email || !uid || !session_token || !key_fetch_token || !unwrap_b_key) {
+    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;
   }
 
-out:
+  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,
+                                session_token, key_fetch_token, unwrap_b_key);
+  goto out_no_error;
+
+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 +418,70 @@ 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 ());
+  ephy_sync_service_do_sign_out (dialog->sync_service);
 
-  /* 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);
+  /* 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_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);
+  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 +575,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 +1200,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 +1600,96 @@ 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_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/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..7607053 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,61 +125,112 @@ 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 *hawk_header;
 
-  hheader = g_slice_new (EphySyncCryptoHawkHeader);
-  hheader->header = header;
-  hheader->artifacts = artifacts;
+  hawk_header = g_slice_new (SyncCryptoHawkHeader);
+  hawk_header->header = g_strdup (header);
+  hawk_header->artifacts = artifacts;
 
-  return hheader;
+  return hawk_header;
 }
 
 void
-ephy_sync_crypto_hawk_header_free (EphySyncCryptoHawkHeader *hheader)
+ephy_sync_crypto_hawk_header_free (SyncCryptoHawkHeader *hawk_header)
 {
-  g_return_if_fail (hheader != NULL);
+  g_return_if_fail (hawk_header);
 
-  g_free (hheader->header);
-  ephy_sync_crypto_hawk_artifacts_free (hheader->artifacts);
+  g_free (hawk_header->header);
+  ephy_sync_crypto_hawk_artifacts_free (hawk_header->artifacts);
 
-  g_slice_free (EphySyncCryptoHawkHeader, hheader);
+  g_slice_free (SyncCryptoHawkHeader, hawk_header);
 }
 
-static EphySyncCryptoRSAKeyPair *
+static SyncCryptoRSAKeyPair *
 ephy_sync_crypto_rsa_key_pair_new (struct rsa_public_key  public,
                                    struct rsa_private_key private)
 {
-  EphySyncCryptoRSAKeyPair *keypair;
+  SyncCryptoRSAKeyPair *rsa_key_pair;
 
-  keypair = g_slice_new (EphySyncCryptoRSAKeyPair);
-  keypair->public = public;
-  keypair->private = private;
+  rsa_key_pair = g_slice_new (SyncCryptoRSAKeyPair);
+  rsa_key_pair->public = public;
+  rsa_key_pair->private = private;
 
-  return keypair;
+  return rsa_key_pair;
 }
 
 void
-ephy_sync_crypto_rsa_key_pair_free (EphySyncCryptoRSAKeyPair *keypair)
+ephy_sync_crypto_rsa_key_pair_free (SyncCryptoRSAKeyPair *rsa_key_pair)
 {
-  g_return_if_fail (keypair != NULL);
+  g_return_if_fail (rsa_key_pair);
 
-  rsa_public_key_clear (&keypair->public);
-  rsa_private_key_clear (&keypair->private);
+  rsa_public_key_clear (&rsa_key_pair->public);
+  rsa_private_key_clear (&rsa_key_pair->private);
 
-  g_slice_free (EphySyncCryptoRSAKeyPair, keypair);
+  g_slice_free (SyncCryptoRSAKeyPair, rsa_key_pair);
+}
+
+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,23 +400,25 @@ 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);
-  digest_hex = g_compute_hmac_for_string (G_CHECKSUM_SHA256, key, key_len, normalized, -1);
+  digest_hex = g_compute_hmac_for_string (G_CHECKSUM_SHA256,
+                                          key, key_len,
+                                          normalized, -1);
   digest = ephy_sync_crypto_decode_hex (digest_hex);
   mac = g_base64_encode (digest, g_checksum_type_get_length (G_CHECKSUM_SHA256));
 
@@ -341,14 +432,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 +448,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 +497,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,13 +508,15 @@ 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;
   }
 
   /* Step 1: Extract */
-  prk_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256, salt, salt_len, in, in_len);
+  prk_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+                                     salt, salt_len,
+                                     in, in_len);
   prk = ephy_sync_crypto_decode_hex (prk_hex);
 
   /* Step 2: Expand */
@@ -405,16 +526,19 @@ 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;
     }
 
-    tmp_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256, prk, hash_len, data, data_len);
+    tmp_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+                                       prk, hash_len,
+                                       data, data_len);
     tmp = ephy_sync_crypto_decode_hex (tmp_hex);
     memcpy (out_full + i * hash_len, tmp, hash_len);
 
@@ -434,7 +558,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,173 +568,549 @@ 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)
+ephy_sync_crypto_process_key_fetch_token (const char  *key_fetch_token,
+                                          guint8     **token_id,
+                                          guint8     **req_hmac_key,
+                                          guint8     **resp_hmac_key,
+                                          guint8     **resp_xor_key,
+                                          gsize        token_len)
 {
   guint8 *kft;
   guint8 *out1;
   guint8 *out2;
-  guint8 *keyRequestKey;
+  guint8 *key_request_key;
   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 (key_fetch_token);
+  g_return_if_fail (token_id);
+  g_return_if_fail (req_hmac_key);
+  g_return_if_fail (resp_hmac_key);
+  g_return_if_fail (resp_xor_key);
 
-  kft = ephy_sync_crypto_decode_hex (keyFetchToken);
+  kft = ephy_sync_crypto_decode_hex (key_fetch_token);
   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);
+  *token_id = g_malloc (token_len);
+  *req_hmac_key = g_malloc (token_len);
+  key_request_key = g_malloc (token_len);
+  memcpy (*token_id, out1, token_len);
+  memcpy (*req_hmac_key, out1 + token_len, token_len);
+  memcpy (key_request_key, 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 (key_request_key, 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);
+  *resp_hmac_key = g_malloc (token_len);
+  *resp_xor_key = g_malloc (2 * token_len);
+  memcpy (*resp_hmac_key, out2, token_len);
+  memcpy (*resp_xor_key, out2 + token_len, 2 * token_len);
 
   g_free (kft);
   g_free (out1);
   g_free (out2);
   g_free (info_kft);
   g_free (info_keys);
-  g_free (keyRequestKey);
+  g_free (key_request_key);
 }
 
 void
-ephy_sync_crypto_process_session_token (const char  *sessionToken,
-                                        guint8     **tokenID,
-                                        guint8     **reqHMACkey,
-                                        guint8     **requestKey)
+ephy_sync_crypto_process_session_token (const char  *session_token,
+                                        guint8     **token_id,
+                                        guint8     **req_hmac_key,
+                                        guint8     **request_key,
+                                        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 (session_token);
+  g_return_if_fail (token_id);
+  g_return_if_fail (req_hmac_key);
+  g_return_if_fail (request_key);
 
-  st = ephy_sync_crypto_decode_hex (sessionToken);
+  st = ephy_sync_crypto_decode_hex (session_token);
   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);
+  *token_id = g_malloc (token_len);
+  *req_hmac_key = g_malloc (token_len);
+  *request_key = g_malloc (token_len);
+  memcpy (*token_id, out, token_len);
+  memcpy (*req_hmac_key, out + token_len, token_len);
+  memcpy (*request_key, 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  *resp_hmac_key,
+                                    const guint8  *resp_xor_key,
+                                    const guint8  *unwrap_b_key,
+                                    guint8       **kA,
+                                    guint8       **kB,
+                                    gsize          key_len)
+{
+  guint8 *bundle;
   guint8 *ciphertext;
-  guint8 *respMAC;
-  guint8 *respMAC2;
+  guint8 *resp_hmac;
+  guint8 *resp_hmac_2;
   guint8 *xored;
-  guint8 *wrapKB;
-  char *respMAC2_hex;
-
-  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);
-
-  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);
+  guint8 *wrap_key_b;
+  char *resp_hmac_2_hex;
+  gboolean retval = TRUE;
+
+  g_return_val_if_fail (bundle_hex, FALSE);
+  g_return_val_if_fail (resp_hmac_key, FALSE);
+  g_return_val_if_fail (resp_xor_key, FALSE);
+  g_return_val_if_fail (unwrap_b_key, FALSE);
+  g_return_val_if_fail (kA, FALSE);
+  g_return_val_if_fail (kB, FALSE);
+
+  bundle = ephy_sync_crypto_decode_hex (bundle_hex);
+  ciphertext = g_malloc (2 * key_len);
+  resp_hmac = 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);
-  respMAC2_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
-                                          respHMACkey, EPHY_SYNC_TOKEN_LENGTH,
-                                          ciphertext, 2 * EPHY_SYNC_TOKEN_LENGTH);
-  respMAC2 = ephy_sync_crypto_decode_hex (respMAC2_hex);
-  g_assert (ephy_sync_crypto_equals (respMAC, respMAC2, EPHY_SYNC_TOKEN_LENGTH) == TRUE);
+  memcpy (ciphertext, bundle, 2 * key_len);
+  memcpy (resp_hmac, bundle + 2 * key_len, key_len);
+  resp_hmac_2_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+                                             resp_hmac_key, key_len,
+                                             ciphertext, 2 * key_len);
+  resp_hmac_2 = ephy_sync_crypto_decode_hex (resp_hmac_2_hex);
+  if (!ephy_sync_crypto_equals (resp_hmac, resp_hmac_2, 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, resp_xor_key, 2 * key_len);
+  *kA = g_malloc (key_len);
+  memcpy (*kA, xored, key_len);
+  wrap_key_b = g_malloc (key_len);
+  memcpy (wrap_key_b, xored + key_len, key_len);
+  /* XOR wrap(kB) with unwrapBKey to obtain kB. There is no MAC on wrap(kB). */
+  *kB = ephy_sync_crypto_xor (unwrap_b_key, wrap_key_b, key_len);
+
+  g_free (wrap_key_b);
+  g_free (xored);
+out:
+  g_free (resp_hmac_2);
+  g_free (resp_hmac_2_hex);
+  g_free (resp_hmac);
+  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;
+}
 
-  /* 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);
+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;
+  }
 
-  g_free (bdl);
+  /* 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 (respMAC);
-  g_free (respMAC2);
-  g_free (xored);
-  g_free (wrapKB);
-  g_free (respMAC2_hex);
+  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 *hawk_header;
+  SyncCryptoHawkArtifacts *artifacts;
   SoupURI *uri;
   char *resource;
   char *hash;
@@ -619,32 +1119,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,14 +1156,14 @@ 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. */
+    /* Calculate hash for the given payload. */
     hash = ephy_sync_crypto_calculate_payload_hash (payload, content_type);
   }
 
-  /* Create the artifacts from the options. */
+  /* Create artifacts from options. */
   artifacts = ephy_sync_crypto_hawk_artifacts_new (options ? options->app : NULL,
                                                    options ? options->dlg : NULL,
                                                    options ? options->ext : NULL,
@@ -678,16 +1181,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 +1202,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);
   }
 
+  hawk_header = 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 hawk_header;
 }
 
-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 +1236,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               seconds,
+                                   SyncCryptoRSAKeyPair *rsa_key_pair)
 {
   mpz_t signature;
   const char *header = "{\"alg\": \"RS256\"}";
@@ -755,21 +1257,22 @@ 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 (rsa_key_pair, NULL);
 
   /* Encode the header and body to base64 url safe and join them. */
-  expires_at = g_get_real_time () / 1000 + duration * 1000;
+  expires_at = g_get_real_time () / 1000 + seconds * 1000;
   body = g_strdup_printf ("{\"exp\": %lu, \"aud\": \"%s\"}", expires_at, audience);
   body_b64 = ephy_sync_crypto_base64_urlsafe_encode ((guint8 *)body, strlen (body), TRUE);
   header_b64 = ephy_sync_crypto_base64_urlsafe_encode ((guint8 *)header, strlen (header), TRUE);
@@ -781,27 +1284,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 (&rsa_key_pair->public, &rsa_key_pair->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);
+  /* Given correct parameters, this never fails. */
+  g_assert (count == expected_size);
 
-  if (count != expected_size) {
-    g_warning ("Expected %lu bytes, got %lu. Giving up.", count, expected_size);
-    goto out;
-  }
-
-  /* Finally, join certificate, header, body and signed message to create the assertion. */
+  /* 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 +1313,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 +1354,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 +1371,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 +1395,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..f753e3d 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 *rsa_key_pair);
+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  *key_fetch_token,
+                                                                   guint8     **token_id,
+                                                                   guint8     **req_hmac_key,
+                                                                   guint8     **resp_hmac_key,
+                                                                   guint8     **resp_xor_key,
+                                                                   gsize        token_len);
+void                    ephy_sync_crypto_process_session_token    (const char  *session_token,
+                                                                   guint8     **token_id,
+                                                                   guint8     **req_hmac_key,
+                                                                   guint8     **requestKey,
+                                                                   gsize        token_len);
+gboolean                ephy_sync_crypto_compute_sync_keys        (const char    *bundle_hex,
+                                                                   const guint8  *resp_hmac_key,
+                                                                   const guint8  *resp_xor_key,
+                                                                   const guint8  *unwrap_b_key,
+                                                                   guint8       **key_a,
+                                                                   guint8       **key_b,
+                                                                   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 *rsa_key_pair);
+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-service.c b/src/sync/ephy-sync-service.c
index 99d512d..3f410e3 100644
--- a/src/sync/ephy-sync-service.c
+++ b/src/sync/ephy-sync-service.c
@@ -21,24 +21,40 @@
 #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"
 
 #include <glib/gi18n.h>
 #include <json-glib/json-glib.h>
+#include <libsecret/secret.h>
+#include <libsoup/soup.h>
 #include <string.h>
 
-#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 TOKEN_SERVER_URL "https://token.services.mozilla.com/1.0/sync/1.5";
+#define FIREFOX_ACCOUNTS_SERVER_URL "https://api.accounts.firefox.com/v1/";
+
+#define STORAGE_VERSION 5
+
+#define ACCOUNT_KEY "firefox_account"
+
+static const SecretSchema *
+ephy_sync_service_get_secret_schema (void)
+{
+  static const SecretSchema schema = {
+    "org.epiphany.SyncSecrets", SECRET_SCHEMA_NONE,
+    {
+      { ACCOUNT_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { "NULL", 0 },
+    }
+  };
+
+  return &schema;
+}
+
+#define EPHY_SYNC_SECRET_SCHEMA (ephy_sync_service_get_secret_schema ())
 
 struct _EphySyncService {
   GObject      parent_instance;
@@ -46,16 +62,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;
+  char        *account;
+  GHashTable  *secrets;
+  GSList      *managers;
 
   gboolean     locked;
   char        *storage_endpoint;
@@ -64,47 +73,86 @@ struct _EphySyncService {
   gint64       storage_credentials_expiry_time;
   GQueue      *storage_queue;
 
-  char                     *certificate;
-  EphySyncCryptoRSAKeyPair *keypair;
+  char                 *certificate;
+  SyncCryptoRSAKeyPair *rsa_key_pair;
 };
 
 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            *session_token;
+  char            *unwrap_b_key;
+  char            *token_id_hex;
+  guint8          *req_hmac_key;
+  guint8          *resp_hmac_key;
+  guint8          *resp_xor_key;
+} 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 +163,193 @@ 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      *session_token,
+                        const char      *unwrap_b_key,
+                        const char      *token_id_hex,
+                        const guint8    *req_hmac_key,
+                        const guint8    *resp_hmac_key,
+                        const guint8    *resp_xor_key)
+{
+  SignInAsyncData *data;
+
+  data = g_slice_new (SignInAsyncData);
+  data->service = g_object_ref (service);
+  data->email = g_strdup (email);
+  data->uid = g_strdup (uid);
+  data->session_token = g_strdup (session_token);
+  data->unwrap_b_key = g_strdup (unwrap_b_key);
+  data->token_id_hex = g_strdup (token_id_hex);
+  data->req_hmac_key = g_malloc (32);
+  memcpy (data->req_hmac_key, req_hmac_key, 32);
+  data->resp_hmac_key = g_malloc (32);
+  memcpy (data->resp_hmac_key, resp_hmac_key, 32);
+  data->resp_xor_key = g_malloc (2 * 32);
+  memcpy (data->resp_xor_key, resp_xor_key, 2 * 32);
+
+  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->session_token);
+  g_free (data->unwrap_b_key);
+  g_free (data->token_id_hex);
+  g_free (data->req_hmac_key);
+  g_free (data->resp_hmac_key);
+  g_free (data->resp_xor_key);
+  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 const char *
+ephy_sync_service_get_secret (EphySyncService *self,
+                              const char      *name)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (name);
+
+  return g_hash_table_lookup (self->secrets, name);
+}
+
+static void
+ephy_sync_service_set_secret (EphySyncService *self,
+                              const char      *name,
+                              const char      *value)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (name);
+  g_assert (value);
+
+  g_hash_table_replace (self->secrets, g_strdup (name), g_strdup (value));
+}
+
+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_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (collection);
 
-  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_assert (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
@@ -169,134 +357,138 @@ ephy_sync_service_fxa_hawk_post_async (EphySyncService     *self,
                                        const char          *endpoint,
                                        const char          *id,
                                        guint8              *key,
-                                       gsize                key_length,
+                                       gsize                key_len,
                                        char                *request_body,
                                        SoupSessionCallback  callback,
                                        gpointer             user_data)
 {
-  EphySyncCryptoHawkOptions *hoptions;
-  EphySyncCryptoHawkHeader *hheader;
+  SyncCryptoHawkOptions *hawk_options;
+  SyncCryptoHawkHeader *hawk_header;
   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);
+  url = g_strdup_printf ("%s%s", FIREFOX_ACCOUNTS_SERVER_URL, endpoint);
   msg = soup_message_new (SOUP_METHOD_POST, url);
   soup_message_set_request (msg, content_type, SOUP_MEMORY_COPY,
                             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", id, key, key_length, hoptions);
-  soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
-  soup_message_headers_append (msg->request_headers, "content-type", content_type);
+  hawk_options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL,
+                                                    content_type,
+                                                    NULL, NULL, NULL,
+                                                    request_body,
+                                                    NULL);
+  hawk_header = ephy_sync_crypto_compute_hawk_header (url, "POST", id,
+                                                      key, key_len,
+                                                      hawk_options);
+  soup_message_headers_append (msg->request_headers,
+                               "authorization", hawk_header->header);
+  soup_message_headers_append (msg->request_headers,
+                               "content-type", content_type);
   soup_session_queue_message (self->session, msg, callback, user_data);
 
   g_free (url);
-  ephy_sync_crypto_hawk_options_free (hoptions);
-  ephy_sync_crypto_hawk_header_free (hheader);
+  ephy_sync_crypto_hawk_options_free (hawk_options);
+  ephy_sync_crypto_hawk_header_free (hawk_header);
 }
 
-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_len,
+                                      SoupSessionCallback  callback,
+                                      gpointer             user_data)
 {
-  EphySyncCryptoHawkHeader *hheader;
+  SyncCryptoHawkHeader *hawk_header;
   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);
+  url = g_strdup_printf ("%s%s", FIREFOX_ACCOUNTS_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;
+  hawk_header = ephy_sync_crypto_compute_hawk_header (url, "GET", id,
+                                                      key, key_len,
+                                                      NULL);
+  soup_message_headers_append (msg->request_headers, "authorization", hawk_header->header);
+  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;
+  ephy_sync_crypto_hawk_header_free (hawk_header);
 }
 
 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 *hawk_options = NULL;
+  SyncCryptoHawkHeader *hawk_header;
   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) {
-    hoptions = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
-                                                  NULL, NULL, NULL, data->request_body, NULL);
+  if (data->request_body) {
+    hawk_options = 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)
-    soup_message_headers_append (msg->request_headers, "content-type", content_type);
+  if (!g_strcmp0 (data->method, SOUP_METHOD_POST))
+    soup_message_headers_append (msg->request_headers,
+                                 "content-type", content_type);
 
   if (data->modified_since >= 0) {
     if_modified_since = g_strdup_printf ("%.2lf", data->modified_since);
-    soup_message_headers_append (msg->request_headers, "X-If-Modified-Since", if_modified_since);
+    soup_message_headers_append (msg->request_headers,
+                                 "X-If-Modified-Since", if_modified_since);
   }
 
   if (data->unmodified_since >= 0) {
     if_unmodified_since = g_strdup_printf ("%.2lf", data->unmodified_since);
-    soup_message_headers_append (msg->request_headers, "X-If-Unmodified-Since", if_unmodified_since);
+    soup_message_headers_append (msg->request_headers,
+                                 "X-If-Unmodified-Since", if_unmodified_since);
   }
 
-  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);
-  soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
+  hawk_header = ephy_sync_crypto_compute_hawk_header (url, data->method,
+                                                      self->storage_credentials_id,
+                                                      (guint8 *)self->storage_credentials_key,
+                                                      strlen (self->storage_credentials_key),
+                                                      hawk_options);
+  soup_message_headers_append (msg->request_headers,
+                               "authorization", hawk_header->header);
   soup_session_queue_message (self->session, msg, data->callback, data->user_data);
 
-  if (hoptions != NULL)
-    ephy_sync_crypto_hawk_options_free (hoptions);
+  if (hawk_options)
+    ephy_sync_crypto_hawk_options_free (hawk_options);
 
   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);
+  ephy_sync_crypto_hawk_header_free (hawk_header);
+  storage_request_async_data_free (data);
 }
 
 static gboolean
@@ -306,141 +498,262 @@ ephy_sync_service_certificate_is_valid (EphySyncService *self,
   JsonParser *parser;
   JsonObject *json;
   JsonObject *principal;
-  SoupURI *uri;
+  GError *error = NULL;
+  SoupURI *uri = NULL;
   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 (FIREFOX_ACCOUNTS_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      *session_token)
+{
+  SyncCryptoHawkOptions *hawk_options;
+  SyncCryptoHawkHeader *hawk_header;
+  SoupMessage *msg;
+  guint8 *token_id;
+  guint8 *req_hmac_key;
+  guint8 *request_key;
+  char *token_id_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_assert (EPHY_IS_SYNC_SERVICE (self));
+  if (!session_token)
+    session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
+  g_assert (session_token);
 
-  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", FIREFOX_ACCOUNTS_SERVER_URL);
+  ephy_sync_crypto_process_session_token (session_token, &token_id,
+                                          &req_hmac_key, &request_key, 32);
+  token_id_hex = ephy_sync_crypto_encode_hex (token_id, 32);
+
+  msg = soup_message_new (SOUP_METHOD_POST, url);
+  soup_message_set_request (msg, content_type, SOUP_MEMORY_STATIC,
+                            request_body, strlen (request_body));
+  hawk_options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL,
+                                                    content_type,
+                                                    NULL, NULL, NULL,
+                                                    request_body,
+                                                    NULL);
+  hawk_header = ephy_sync_crypto_compute_hawk_header (url, "POST", token_id_hex,
+                                                      req_hmac_key, 32,
+                                                      hawk_options);
+  soup_message_headers_append (msg->request_headers,
+                               "authorization", hawk_header->header);
+  soup_message_headers_append (msg->request_headers,
+                               "content-type", content_type);
+  soup_session_queue_message (self->session, msg, destroy_session_cb, NULL);
+
+  g_free (token_id_hex);
+  g_free (token_id);
+  g_free (req_hmac_key);
+  g_free (request_key);
+  g_free (url);
+  ephy_sync_crypto_hawk_options_free (hawk_options);
+  ephy_sync_crypto_hawk_header_free (hawk_header);
+}
+
+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_assert (url);
+
+  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;
-  char *hashed_kB;
+  guint8 *key_b;
+  char *hashed_key_b;
   char *client_state;
   char *audience;
   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->rsa_key_pair);
 
-  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 (TOKEN_SERVER_URL);
+  assertion = ephy_sync_crypto_create_assertion (self->certificate, audience,
+                                                 300, self->rsa_key_pair);
+  key_b = ephy_sync_crypto_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+  hashed_key_b = g_compute_checksum_for_data (G_CHECKSUM_SHA256, key_b, 32);
+  client_state = g_strndup (hashed_key_b, 32);
   authorization = g_strdup_printf ("BrowserID %s", assertion);
 
-  msg = soup_message_new (SOUP_METHOD_GET, MOZILLA_TOKEN_SERVER_URL);
+  msg = soup_message_new (SOUP_METHOD_GET, TOKEN_SERVER_URL);
   /* We need to add the X-Client-State header so that the Token Server will
    * 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);
+  g_free (key_b);
+  g_free (hashed_key_b);
   g_free (client_state);
   g_free (audience);
   g_free (assertion);
@@ -448,1009 +761,1445 @@ 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->rsa_key_pair);
+      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.");
+    ephy_sync_service_do_sign_out (self);
   }
 
-  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)
-{
-  guint8 *tokenID;
-  guint8 *reqHMACkey;
-  guint8 *requestKey;
-  char *tokenID_hex;
-  char *public_key_json;
+ephy_sync_service_obtain_signed_certificate (EphySyncService *self)
+{
+  JsonNode *node;
+  JsonObject *object_key;
+  JsonObject *object_body;
+  guint8 *token_id;
+  guint8 *req_hmac_key;
+  guint8 *request_key;
+  const char *session_token;
+  char *token_id_hex;
   char *request_body;
   char *n;
   char *e;
 
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (self->sessionToken != NULL);
-
-  /* Generate a new RSA key pair that is going to be used to sign the new certificate. */
-  if (self->keypair != NULL)
-    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);
-
-  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);
-  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);
-
-  g_free (tokenID);
-  g_free (reqHMACkey);
-  g_free (requestKey);
-  g_free (tokenID_hex);
-  g_free (public_key_json);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+  /* Generate a new RSA key pair to sign the new certificate. */
+  if (self->rsa_key_pair)
+    ephy_sync_crypto_rsa_key_pair_free (self->rsa_key_pair);
+  self->rsa_key_pair = ephy_sync_crypto_generate_rsa_key_pair ();
+
+  /* Derive tokenID, reqHMACkey and requestKey from sessionToken. */
+  session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
+  ephy_sync_crypto_process_session_token (session_token, &token_id,
+                                          &req_hmac_key, &request_key, 32);
+  token_id_hex = ephy_sync_crypto_encode_hex (token_id, 32);
+
+  n = mpz_get_str (NULL, 10, self->rsa_key_pair->public.n);
+  e = mpz_get_str (NULL, 10, self->rsa_key_pair->public.e);
+  node = json_node_new (JSON_NODE_OBJECT);
+  object_body = json_object_new ();
+  /* Milliseconds, limited to 24 hours. */
+  json_object_set_int_member (object_body, "duration", 1 * 60 * 60 * 1000);
+  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", token_id_hex,
+                                         req_hmac_key, 32, request_body,
+                                         obtain_signed_certificate_cb, self);
+
   g_free (request_body);
-  g_free (n);
+  json_object_unref (object_body);
+  json_node_unref (node);
   g_free (e);
+  g_free (n);
+  g_free (token_id_hex);
+  g_free (request_key);
+  g_free (req_hmac_key);
+  g_free (token_id);
 }
 
 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)
 {
-  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);
-  } else {
+  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
-ephy_sync_service_finalize (GObject *object)
+delete_synchronizable_cb (SoupSession *session,
+                          SoupMessage *msg,
+                          gpointer     user_data)
 {
-  EphySyncService *self = EPHY_SYNC_SERVICE (object);
-
-  if (self->keypair != NULL)
-    ephy_sync_crypto_rsa_key_pair_free (self->keypair);
-
-  g_queue_free_full (self->storage_queue, (GDestroyNotify) storage_server_request_async_data_free);
-
-  G_OBJECT_CLASS (ephy_sync_service_parent_class)->finalize (object);
+  if (msg->status_code == 200) {
+    LOG ("Successfully deleted from server");
+  } else {
+    g_warning ("Failed to delete object. Status code: %u, response: %s",
+               msg->status_code, msg->response_body->data);
+  }
 }
 
 static void
-ephy_sync_service_dispose (GObject *object)
+ephy_sync_service_delete_synchronizable (EphySyncService           *self,
+                                         EphySynchronizableManager *manager,
+                                         EphySynchronizable        *synchronizable)
 {
-  EphySyncService *self = EPHY_SYNC_SERVICE (object);
+  JsonNode *node;
+  JsonObject *object;
+  SyncCryptoKeyBundle *bundle;
+  char *endpoint;
+  char *record;
+  char *payload;
+  char *body;
+  const char *collection;
+  const char *id;
 
-  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);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+  g_assert (ephy_sync_service_is_signed_in (self));
+
+  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 from collection %s...", id, collection);
+  ephy_sync_service_queue_storage_request (self, endpoint,
+                                           SOUP_METHOD_PUT, body, -1, -1,
+                                           delete_synchronizable_cb, NULL);
 
-  G_OBJECT_CLASS (ephy_sync_service_parent_class)->dispose (object);
+  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);
 }
 
 static void
-ephy_sync_service_class_init (EphySyncServiceClass *klass)
+download_synchronizable_cb (SoupSession *session,
+                            SoupMessage *msg,
+                            gpointer     user_data)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  SyncAsyncData *data = (SyncAsyncData *)user_data;
+  EphySynchronizable *synchronizable;
+  SyncCryptoKeyBundle *bundle = NULL;
+  JsonNode *node = NULL;
+  GError *error = NULL;
+  GType type;
+  const char *collection;
+  gboolean is_deleted;
 
-  object_class->finalize = ephy_sync_service_finalize;
-  object_class->dispose = ephy_sync_service_dispose;
+  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;
+  }
 
-  signals[STORE_FINISHED] =
-    g_signal_new ("sync-tokens-store-finished",
-                  EPHY_TYPE_SYNC_SERVICE,
-                  G_SIGNAL_RUN_LAST,
-                  0, NULL, NULL, NULL,
-                  G_TYPE_NONE, 1,
-                  G_TYPE_ERROR);
+  /* 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_manager_add (data->manager, synchronizable);
+    LOG ("Successfully downloaded from server");
+  } else {
+    LOG ("The newer version was a deleted object");
+  }
 
-  signals[LOAD_FINISHED] =
-    g_signal_new ("sync-tokens-load-finished",
-                  EPHY_TYPE_SYNC_SERVICE,
-                  G_SIGNAL_RUN_LAST,
-                  0, NULL, NULL, NULL,
-                  G_TYPE_NONE, 1,
-                  G_TYPE_ERROR);
+  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_init (EphySyncService *self)
+ephy_sync_service_download_synchronizable (EphySyncService           *self,
+                                           EphySynchronizableManager *manager,
+                                           EphySynchronizable        *synchronizable)
 {
-  char *email;
-  const char *user_agent;
-  WebKitSettings *settings;
+  SyncAsyncData *data;
+  char *endpoint;
+  const char *collection;
+  const char *id;
 
-  self->session = soup_session_new ();
-  self->storage_queue = g_queue_new ();
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+  g_assert (ephy_sync_service_is_signed_in (self));
 
-  settings = ephy_embed_prefs_get_settings ();
-  user_agent = webkit_settings_get_user_agent (settings);
-  g_object_set (self->session, "user-agent", user_agent, NULL);
+  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);
 
-  email = g_settings_get_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER);
+  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);
+}
 
-  if (g_strcmp0 (email, "") != 0) {
-    ephy_sync_service_set_user_email (self, email);
-    ephy_sync_secret_load_tokens (self);
+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_server_time_modified (data->synchronizable, time_modified);
+  } else {
+    g_warning ("Failed to upload object. Status code: %u, response: %s",
+               msg->status_code, msg->response_body->data);
   }
 
-  g_free (email);
+  sync_async_data_free (data);
 }
 
-EphySyncService *
-ephy_sync_service_new (void)
+static void
+ephy_sync_service_upload_synchronizable (EphySyncService           *self,
+                                         EphySynchronizableManager *manager,
+                                         EphySynchronizable        *synchronizable)
 {
-  return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE, NULL));
+  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));
+  g_assert (ephy_sync_service_is_signed_in (self));
+
+  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_server_time_modified (synchronizable),
+                                           upload_synchronizable_cb, data);
+
+  g_free (body);
+  g_free (endpoint);
+  json_node_unref (bso);
+  ephy_sync_crypto_key_bundle_free (bundle);
 }
 
-gboolean
-ephy_sync_service_is_signed_in (EphySyncService *self)
+static void
+sync_collection_cb (SoupSession *session,
+                    SoupMessage *msg,
+                    gpointer     user_data)
 {
-  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), FALSE);
+  SyncCollectionAsyncData *data = (SyncCollectionAsyncData *)user_data;
+  EphySynchronizable *remote;
+  SyncCryptoKeyBundle *bundle;
+  JsonNode *node = NULL;
+  JsonArray *array = NULL;
+  GError *error = NULL;
+  GSList *remotes_updated = NULL;
+  GSList *remotes_deleted = NULL;
+  GSList *to_upload = NULL;
+  GType type;
+  const char *collection;
+  const char *last_modified;
+  gboolean is_deleted;
+
+  collection = ephy_synchronizable_manager_get_collection_name (data->manager);
+
+  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;
+  }
+
+  LOG ("Found %u new remote objects...", json_array_get_length (array));
 
-  return self->user_email != NULL;
+  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_slist_prepend (remotes_deleted, remote);
+    else
+      remotes_updated = g_slist_prepend (remotes_updated, remote);
+  }
+
+  to_upload = ephy_synchronizable_manager_merge_remotes (data->manager, data->is_initial,
+                                                         remotes_deleted, remotes_updated);
+  for (GSList *l = to_upload; l && l->data; l = l->next)
+    ephy_sync_service_upload_synchronizable (data->service, data->manager, l->data);
+
+  /* Update sync time. */
+  last_modified = soup_message_headers_get_one (msg->response_headers, "X-Last-Modified");
+  ephy_synchronizable_manager_set_sync_time (data->manager, g_ascii_strtod (last_modified, 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_slist_free_full (to_upload, g_object_unref);
+  if (remotes_updated)
+    g_slist_free_full (remotes_updated, g_object_unref);
+  if (remotes_deleted)
+    g_slist_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);
 }
 
-char *
-ephy_sync_service_get_user_email (EphySyncService *self)
+static void
+ephy_sync_service_sync_collection (EphySyncService           *self,
+                                   EphySynchronizableManager *manager,
+                                   gboolean                   is_last)
 {
-  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
+  SyncCollectionAsyncData *data;
+  const char *collection;
+  char *endpoint;
+  gboolean is_initial;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_assert (ephy_sync_service_is_signed_in (self));
+
+  collection = ephy_synchronizable_manager_get_collection_name (manager);
+  is_initial = ephy_synchronizable_manager_is_initial_sync (manager);
+
+  if (is_initial) {
+    endpoint = g_strdup_printf ("storage/%s?full=true", collection);
+  } else {
+    endpoint = g_strdup_printf ("storage/%s?newer=%.2lf&full=true", collection,
+                                ephy_synchronizable_manager_get_sync_time (manager));
+  }
+
+  LOG ("Syncing %s collection %s...", collection, is_initial ? "initial" : "regular");
+  data = sync_collection_async_data_new (self, manager, is_initial, is_last);
+  ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_GET,
+                                           NULL, -1, -1,
+                                           sync_collection_cb, data);
 
-  return self->user_email;
+  g_free (endpoint);
 }
 
-void
-ephy_sync_service_set_user_email (EphySyncService *self,
-                                  const char      *email)
+static gboolean
+ephy_sync_service_sync (gpointer user_data)
 {
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+  guint index = 0;
+  guint num_managers;
+
+  g_assert (ephy_sync_service_is_signed_in (self));
+
+  if (!self->managers) {
+    g_signal_emit (self, signals[SYNC_FINISHED], 0);
+    return G_SOURCE_CONTINUE;
+  }
 
-  g_free (self->user_email);
-  self->user_email = g_strdup (email);
+  num_managers = g_slist_length (self->managers);
+  for (GSList *l = self->managers; l && l->data; l = l->next)
+    ephy_sync_service_sync_collection (self, l->data, ++index == num_managers);
+
+  return G_SOURCE_CONTINUE;
 }
 
-double
-ephy_sync_service_get_sync_time (EphySyncService *self)
+static void
+ephy_sync_service_unregister_client_id (EphySyncService *self)
 {
-  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), 0);
+  char *client_id;
+  char *endpoint;
 
-  if (self->sync_time != 0)
-    return self->sync_time;
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-  self->sync_time = g_settings_get_double (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_TIME);
-  return self->sync_time;
-}
+  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, "");
 
-void
-ephy_sync_service_set_sync_time (EphySyncService *self,
-                                 double           time)
+  g_free (endpoint);
+  g_free (client_id);
+}
+
+static void
+ephy_sync_service_register_client_id (EphySyncService *self)
 {
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (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;
 
-  self->sync_time = time;
-  g_settings_set_double (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_TIME, time);
+  g_assert (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);
 }
 
-char *
-ephy_sync_service_get_token (EphySyncService   *self,
-                             EphySyncTokenType  type)
+static void
+ephy_sync_service_stop_periodical_sync (EphySyncService *self)
 {
-  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-  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 ();
+  if (self->source_id != 0) {
+    g_source_remove (self->source_id);
+    self->source_id = 0;
   }
 }
 
-void
-ephy_sync_service_set_token (EphySyncService   *self,
-                             const char        *value,
-                             EphySyncTokenType  type)
+static void
+ephy_sync_service_schedule_periodical_sync (EphySyncService *self)
 {
-  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_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));
 }
 
-void
-ephy_sync_service_clear_storage_credentials (EphySyncService *self)
+static void
+sync_frequency_changed_cb (EphySyncService *self)
 {
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (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;
+  ephy_sync_service_stop_periodical_sync (self);
+  ephy_sync_service_schedule_periodical_sync (self);
 }
 
-void
-ephy_sync_service_clear_tokens (EphySyncService *self)
+static void
+forget_secrets_cb (SecretService *service,
+                   GAsyncResult  *result,
+                   gpointer       user_data)
 {
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  GError *error = NULL;
 
-  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);
+  secret_service_clear_finish (service, result, &error);
+  if (error) {
+    g_warning ("Failed to clear sync secrets: %s", error->message);
+    g_error_free (error);
+  }
 }
 
-void
-ephy_sync_service_destroy_session (EphySyncService *self,
-                                   const char      *sessionToken)
+static void
+ephy_sync_service_forget_secrets (EphySyncService *self)
 {
-  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));
+  GHashTable *attributes;
 
-  if (sessionToken == NULL)
-    sessionToken = ephy_sync_service_get_token (self, TOKEN_SESSIONTOKEN);
-  g_return_if_fail (sessionToken != NULL);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (self->secrets);
 
-  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);
+  attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
+                                        ACCOUNT_KEY, self->account,
+                                        NULL);
+  secret_service_clear (NULL, EPHY_SYNC_SECRET_SCHEMA, attributes, NULL,
+                        (GAsyncReadyCallback)forget_secrets_cb, NULL);
+  g_hash_table_remove_all (self->secrets);
 
-  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_unref (attributes);
 }
 
-char *
-ephy_sync_service_start_sign_in (EphySyncService  *self,
-                                 guint8           *tokenID,
-                                 guint8           *reqHMACkey)
+static void
+load_secrets_cb (SecretService   *service,
+                 GAsyncResult    *result,
+                 EphySyncService *self)
 {
+  SecretValue *value = NULL;
   JsonNode *node = NULL;
-  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);
+  JsonObject *object;
+  GList *res = NULL;
+  GError *error = NULL;
+  const char *message;
+  const char *suggestion;
+
+  res = secret_service_search_finish (service, result, &error);
+  if (error) {
+    g_warning ("Failed to search for sync secrets: %s", error->message);
+    g_error_free (error);
+    message = _("Could not find the sync secrets for the current sync user.");
+    goto out_error;
+  }
 
-  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"));
+  if (!(res && res->data)) {
+    message = _("The sync secrets for the current sync user are null.");
+    goto out_error;
   }
 
-  g_free (tokenID_hex);
-  json_node_free (node);
+  value = secret_item_get_secret ((SecretItem *)res->data);
+  node = json_from_string (secret_value_get_text (value), &error);
+  if (error) {
+    g_warning ("Sync secrets are not a valid JSON: %s", error->message);
+    g_error_free (error);
+    message = _("The sync secrets for the current sync user are invalid.");
+    goto out_error;
+  }
 
-  return bundle;
+  /* Set secrets and start periodical sync. */
+  object = json_node_get_object (node);
+  for (GList *l = json_object_get_members (object); l && l->data; l = l->next)
+    ephy_sync_service_set_secret (self, l->data,
+                                  json_object_get_string_member (object, l->data));
+
+  ephy_sync_service_start_periodical_sync (self);
+  goto out_no_error;
+
+out_error:
+  suggestion = _("Please visit Preferences and sign in again to continue syncing.");
+  ephy_notification_show (ephy_notification_new (message, suggestion));
+out_no_error:
+  if (value)
+    secret_value_unref (value);
+  if (res)
+    g_list_free_full (res, g_object_unref);
+  if (node)
+    json_node_unref (node);
 }
 
-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;
+static void
+ephy_sync_service_load_secrets (EphySyncService *self)
+{
+  GHashTable *attributes;
 
-  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);
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (self->secrets);
+
+  attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
+                                        ACCOUNT_KEY, self->account,
+                                        NULL);
+  secret_service_search (NULL, EPHY_SYNC_SECRET_SCHEMA, attributes,
+                         SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
+                         NULL, (GAsyncReadyCallback)load_secrets_cb, self);
+
+  g_hash_table_unref (attributes);
 }
 
-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)
+static void
+store_secrets_cb (SecretService   *service,
+                  GAsyncResult    *result,
+                  EphySyncService *self)
 {
-  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);
+  GError *error = NULL;
+
+  secret_service_store_finish (service, result, &error);
+  if (error) {
+    g_warning ("Failed to store sync secrets: %s", error->message);
+    ephy_sync_service_destroy_session (self, NULL);
+    g_clear_pointer (&self->account, g_free);
+    g_hash_table_remove_all (self->secrets);
   } else {
-    g_queue_push_tail (self->storage_queue, data);
+    ephy_sync_service_register_client_id (self);
   }
-}
 
-void
-ephy_sync_service_release_next_storage_message (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);
+  g_signal_emit (self, signals[STORE_FINISHED], 0, error);
 
-  /* 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;
+  if (error)
+    g_error_free (error);
 }
 
 static void
-upload_bookmark_response_cb (SoupSession *session,
-                             SoupMessage *msg,
-                             gpointer     user_data)
+ephy_sync_service_store_secrets (EphySyncService *self)
 {
-  EphySyncService *service;
-  EphyBookmarksManager *manager;
-  EphyBookmark *bookmark;
-  double last_modified;
+  JsonNode *node;
+  JsonObject *object;
+  SecretValue *secret;
+  GHashTable *attributes;
+  GHashTableIter iter;
+  gpointer key;
+  gpointer value;
+  char *json_string;
+  char *label;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (self->secrets);
+  g_assert (self->account);
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  object = json_object_new ();
+  g_hash_table_iter_init (&iter, self->secrets);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    json_object_set_string_member (object, key, value);
+  json_node_set_object (node, object);
+  json_string = json_to_string (node, FALSE);
+
+  secret = secret_value_new (json_string, -1, "text/plain");
+  attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
+                                        ACCOUNT_KEY, self->account,
+                                        NULL);
+  /* Translators: %s is the email of the user. */
+  label = g_strdup_printf (_("The sync secrets of %s"), self->account);
+
+  secret_service_store (NULL, EPHY_SYNC_SECRET_SCHEMA,
+                        attributes, NULL, label, secret, NULL,
+                        (GAsyncReadyCallback)store_secrets_cb, self);
+
+  g_free (label);
+  g_free (json_string);
+  secret_value_unref (secret);
+  g_hash_table_unref (attributes);
+  json_object_unref (object);
+  json_node_unref (node);
+}
 
-  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);
+static void
+ephy_sync_service_dispose (GObject *object)
+{
+  EphySyncService *self = EPHY_SYNC_SERVICE (object);
 
-  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 (ephy_sync_service_is_signed_in (self))
+    ephy_sync_service_stop_periodical_sync (self);
 
-    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);
-  }
+  ephy_sync_service_clear_storage_credentials (self);
+  g_clear_object (&self->session);
+  g_clear_pointer (&self->account, g_free);
+  g_clear_pointer (&self->rsa_key_pair, ephy_sync_crypto_rsa_key_pair_free);
+  g_clear_pointer (&self->secrets, g_hash_table_destroy);
+  g_clear_pointer (&self->managers, g_slist_free);
+  g_queue_free_full (self->storage_queue, (GDestroyNotify)storage_request_async_data_free);
+  self->storage_queue = NULL;
 
-  ephy_sync_service_release_next_storage_message (service);
+  G_OBJECT_CLASS (ephy_sync_service_parent_class)->dispose (object);
 }
 
-void
-ephy_sync_service_upload_bookmark (EphySyncService *self,
-                                   EphyBookmark    *bookmark,
-                                   gboolean         force)
+static void
+ephy_sync_service_class_init (EphySyncServiceClass *klass)
 {
-  char *endpoint;
-  char *bso;
-  double modified;
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-  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);
+  object_class->dispose = ephy_sync_service_dispose;
 
-  g_free (endpoint);
-  g_free (bso);
+  signals[STORE_FINISHED] =
+    g_signal_new ("sync-secrets-store-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
-download_bookmark_response_cb (SoupSession *session,
-                               SoupMessage *msg,
-                               gpointer     user_data)
+ephy_sync_service_init (EphySyncService *self)
 {
-  EphySyncService *service;
-  EphyBookmarksManager *manager;
-  EphyBookmark *bookmark;
-  GSequenceIter *iter;
-  JsonParser *parser;
-  JsonObject *bso;
-  const char *id;
+  char *email;
+  const char *user_agent;
+  WebKitSettings *settings;
 
-  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;
+  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);
+
+  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_SYNC, EPHY_PREFS_SYNC_USER);
+
+  if (g_strcmp0 (email, "")) {
+    self->account = g_strdup (email);
+    ephy_sync_service_load_secrets (self);
   }
 
-  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_signal_connect (self, "sync-frequency-changed",
+                    G_CALLBACK (sync_frequency_changed_cb),
+                    NULL);
 
-out:
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  ephy_sync_service_release_next_storage_message (service);
+  g_free (email);
 }
 
-void
-ephy_sync_service_download_bookmark (EphySyncService *self,
-                                     EphyBookmark    *bookmark)
+EphySyncService *
+ephy_sync_service_new (void)
 {
-  char *endpoint;
+  return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE, NULL));
+}
 
-  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));
+gboolean
+ephy_sync_service_is_signed_in (EphySyncService *self)
+{
+  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), FALSE);
+
+  return self->account != NULL;
+}
 
-  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);
+const char *
+ephy_sync_service_get_user_email (EphySyncService *self)
+{
+  g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
 
-  g_free (endpoint);
+  return self->account;
 }
 
 static void
-delete_bookmark_conditional_response_cb (SoupSession *session,
-                                         SoupMessage *msg,
-                                         gpointer     user_data)
+ephy_sync_service_report_sign_in_error (EphySyncService *self,
+                                        const char      *message,
+                                        const char      *session_token,
+                                        gboolean         clear_secrets)
 {
-  EphySyncService *service;
-  EphyBookmark *bookmark;
-  EphyBookmarksManager *manager;
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (message);
 
-  bookmark = EPHY_BOOKMARK (user_data);
-  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
+  g_signal_emit (self, signals[SIGN_IN_ERROR], 0, message);
+  ephy_sync_service_destroy_session (self, session_token);
 
-  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);
+  if (clear_secrets) {
+    g_clear_pointer (&self->account, g_free);
+    g_hash_table_remove_all (self->secrets);
   }
+}
 
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  ephy_sync_service_release_next_storage_message (service);
+static char *
+ephy_sync_service_upload_crypto_keys_record (EphySyncService *self)
+{
+  SyncCryptoKeyBundle *bundle;
+  JsonNode *node;
+  JsonObject *record;
+  char *payload_clear;
+  char *payload_cipher;
+  char *body;
+  const char *master_key_hex;
+  guint8 *master_key;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  master_key_hex = ephy_sync_service_get_secret (self, secrets[MASTER_KEY]);
+  g_assert (master_key_hex);
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  record = json_object_new ();
+  payload_clear = ephy_sync_crypto_generate_crypto_keys (32);
+  master_key = ephy_sync_crypto_decode_hex (master_key_hex);
+  bundle = ephy_sync_crypto_derive_key_bundle (master_key, 32);
+  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);
+  g_free (master_key);
+  json_object_unref (record);
+  json_node_unref (node);
+  ephy_sync_crypto_key_bundle_free (bundle);
+
+  return payload_clear;
 }
 
 static void
-delete_bookmark_response_cb (SoupSession *session,
-                             SoupMessage *msg,
-                             gpointer     user_data)
+obtain_crypto_keys_cb (SoupSession *session,
+                       SoupMessage *msg,
+                       gpointer     user_data)
 {
-  EphySyncService *service;
+  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 *key_b = NULL;
 
-  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);
+  if (msg->status_code == 404) {
+    crypto_keys = ephy_sync_service_upload_crypto_keys_record (self);
+    goto store_secrets;
+  }
 
-  service = ephy_shell_get_sync_service (ephy_shell_get_default ());
-  ephy_sync_service_release_next_storage_message (service);
+  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;
+  }
+
+  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. */
+  key_b = ephy_sync_crypto_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+  bundle = ephy_sync_crypto_derive_key_bundle (key_b, 32);
+  crypto_keys = ephy_sync_crypto_decrypt_record (payload, bundle);
+  if (!crypto_keys) {
+    g_warning ("Failed to decrypt crypto/keys record");
+    goto out_error;
+  }
+
+store_secrets:
+  ephy_sync_service_set_secret (self, secrets[CRYPTO_KEYS], crypto_keys);
+  ephy_sync_service_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 (key_b);
 }
 
-void
-ephy_sync_service_delete_bookmark (EphySyncService *self,
-                                   EphyBookmark    *bookmark,
-                                   gboolean         conditional)
+static void
+ephy_sync_service_obtain_crypto_keys (EphySyncService *self)
 {
-  char *endpoint;
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
-  g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+  ephy_sync_service_queue_storage_request (self, "storage/crypto/keys",
+                                           SOUP_METHOD_GET, NULL, -1, -1,
+                                           obtain_crypto_keys_cb, self);
+}
 
-  if (ephy_sync_service_is_signed_in (self) == FALSE)
-    return;
+static JsonObject *
+make_engine_object (int version)
+{
+  JsonObject *object;
+  char *sync_id;
 
-  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);
-  }
+  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);
 
-  g_free (endpoint);
+  g_free (sync_id);
+
+  return object;
 }
 
 static void
-sync_bookmarks_first_time_response_cb (SoupSession *session,
-                                       SoupMessage *msg,
-                                       gpointer     user_data)
+ephy_sync_service_upload_meta_global_record (EphySyncService *self)
 {
-  EphySyncService *service;
-  EphyBookmarksManager *manager;
-  GSequence *bookmarks;
-  GSequenceIter *iter;
-  GHashTable *marked;
-  JsonParser *parser;
-  JsonArray *array;
-  const char *timestamp;
-  double server_time;
+  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);
+}
 
-  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);
+static void
+check_storage_version_cb (SoupSession *session,
+                          SoupMessage *msg,
+                          gpointer     user_data)
+{
+  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 != 200) {
-    LOG ("Failed to do a first time sync. Status code: %u, response: %s",
-         msg->status_code, msg->response_body->data);
-    goto out;
+  if (msg->status_code == 404) {
+    ephy_sync_service_upload_meta_global_record (self);
+    goto obtain_crypto_keys;
   }
 
-  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 (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 (remote == NULL)
-      continue;
+  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;
+  }
 
-    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));
-      }
-    }
+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);
+}
 
-    g_object_unref (remote);
-  }
+static void
+ephy_sync_service_check_storage_version (EphySyncService *self)
+{
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-  /* 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);
+  ephy_sync_service_queue_storage_request (self, "storage/meta/global",
+                                           SOUP_METHOD_GET, NULL, -1, -1,
+                                           check_storage_version_cb, self);
+}
 
-    if (g_hash_table_contains (marked, bookmark) == FALSE)
-      ephy_sync_service_upload_bookmark (service, bookmark, TRUE);
+static void
+ephy_sync_service_conclude_sign_in (EphySyncService *self,
+                                    SignInAsyncData *data,
+                                    const char      *bundle)
+{
+  guint8 *unwrap_key_b;
+  guint8 *key_a;
+  guint8 *key_b;
+  char *key_b_hex;
+
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
+  g_assert (data);
+  g_assert (bundle);
+
+  /* Derive the master sync keys form the key bundle. */
+  unwrap_key_b = ephy_sync_crypto_decode_hex (data->unwrap_b_key);
+  if (!ephy_sync_crypto_compute_sync_keys (bundle, data->resp_hmac_key,
+                                           data->resp_xor_key, unwrap_key_b,
+                                           &key_a, &key_b, 32)) {
+    ephy_sync_service_report_sign_in_error (self, _("Failed to retrieve the Sync Key"),
+                                            data->session_token, FALSE);
+    goto out;
   }
 
-  /* Save changes to file. */
-  ephy_bookmarks_manager_save_to_file_async (manager, NULL, NULL, NULL);
+  /* Save email and tokens. */
+  self->account = g_strdup (data->email);
+  ephy_sync_service_set_secret (self, secrets[UID], data->uid);
+  ephy_sync_service_set_secret (self, secrets[SESSION_TOKEN], data->session_token);
+  key_b_hex = ephy_sync_crypto_encode_hex (key_b, 32);
+  ephy_sync_service_set_secret (self, secrets[MASTER_KEY], key_b_hex);
 
-  /* 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 (key_b_hex);
+  g_free (key_b);
+  g_free (key_a);
 out:
-  g_object_unref (parser);
-  g_hash_table_unref (marked);
-
-  ephy_sync_service_release_next_storage_message (service);
+  g_free (unwrap_key_b);
+  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);
+  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;
+  }
 
-  /* 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) {
+    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;
+  }
 
-  if (msg->status_code != 200) {
-    LOG ("Failed to sync bookmarks. Status code: %u, response: %s",
-         msg->status_code, msg->response_body->data);
-    goto out;
+  /* 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->token_id_hex, data->req_hmac_key,
+                                          32, get_account_keys_cb, data);
+    goto out_no_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;
+  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->session_token, FALSE);
+  sign_in_async_data_free (data);
+out_no_error:
+  if (node)
+    json_node_unref (node);
+  if (error)
+    g_error_free (error);
+}
 
-    if (remote == NULL)
-      continue;
+void
+ephy_sync_service_do_sign_in (EphySyncService *self,
+                              const char      *email,
+                              const char      *uid,
+                              const char      *session_token,
+                              const char      *key_fetch_token,
+                              const char      *unwrap_b_key)
+{
+  SignInAsyncData *data;
+  guint8 *token_id;
+  guint8 *req_hmac_key;
+  guint8 *resp_hmac_key;
+  guint8 *resp_xor_key;
+  char *token_id_hex;
 
-    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);
-      }
-    }
-  }
+  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  g_return_if_fail (email);
+  g_return_if_fail (uid);
+  g_return_if_fail (session_token);
+  g_return_if_fail (key_fetch_token);
+  g_return_if_fail (unwrap_b_key);
+
+  /* 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 (key_fetch_token, &token_id, &req_hmac_key,
+                                            &resp_hmac_key, &resp_xor_key, 32);
+  token_id_hex = ephy_sync_crypto_encode_hex (token_id, 32);
+
+  /* Get the master sync key bundle from the /account/keys endpoint. */
+  data = sign_in_async_data_new (self, email, uid,
+                                 session_token, unwrap_b_key,
+                                 token_id_hex, req_hmac_key,
+                                 resp_hmac_key, resp_xor_key);
+  ephy_sync_service_fxa_hawk_get_async (self, "account/keys", token_id_hex,
+                                        req_hmac_key, 32,
+                                        get_account_keys_cb, data);
+
+  g_free (token_id_hex);
+  g_free (token_id);
+  g_free (req_hmac_key);
+  g_free (resp_hmac_key);
+  g_free (resp_xor_key);
+}
 
-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));
+static void
+synchronizable_deleted_cb (EphySynchronizableManager *manager,
+                           EphySynchronizable        *synchronizable,
+                           EphySyncService           *self)
+{
+  g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-    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 (!ephy_sync_service_is_signed_in (self))
+    return;
 
-  /* Save changes to file. */
-  ephy_bookmarks_manager_save_to_file_async (manager, NULL, NULL, NULL);
+  ephy_sync_service_delete_synchronizable (self, manager, synchronizable);
+}
 
-  /* 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);
+static void
+synchronizable_modified_cb (EphySynchronizableManager *manager,
+                            EphySynchronizable        *synchronizable,
+                            EphySyncService           *self)
+{
+  g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+  g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+  g_assert (EPHY_IS_SYNC_SERVICE (self));
 
-out:
-  g_object_unref (parser);
+  if (!ephy_sync_service_is_signed_in (self))
+    return;
 
-  ephy_sync_service_release_next_storage_message (service);
+  ephy_sync_service_upload_synchronizable (self, manager, synchronizable);
 }
 
 void
-ephy_sync_service_sync_bookmarks (EphySyncService *self,
-                                  gboolean         first)
+ephy_sync_service_register_manager (EphySyncService           *self,
+                                    EphySynchronizableManager *manager)
 {
-  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_SYNCHRONIZABLE_MANAGER (manager));
 
-  endpoint = g_strdup_printf ("storage/%s?full=true", EPHY_BOOKMARKS_COLLECTION);
+  if (!g_slist_find (self->managers, manager)) {
+    self->managers = g_slist_prepend (self->managers, manager);
 
-  if (first == TRUE) {
-    ephy_sync_service_send_storage_message (self, endpoint,
-                                            SOUP_METHOD_GET, NULL, -1, -1,
-                                            sync_bookmarks_first_time_response_cb, NULL);
-  } 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_signal_connect (manager, "synchronizable-deleted",
+                      G_CALLBACK (synchronizable_deleted_cb), self);
+    g_signal_connect (manager, "synchronizable-modified",
+                      G_CALLBACK (synchronizable_modified_cb), self);
   }
-
-  g_free (endpoint);
 }
 
-static gboolean
-do_periodical_sync (gpointer user_data)
+void
+ephy_sync_service_unregister_manager (EphySyncService           *self,
+                                      EphySynchronizableManager *manager)
 {
-  EphySyncService *service = EPHY_SYNC_SERVICE (user_data);
+  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
 
-  ephy_sync_service_sync_bookmarks (service, FALSE);
+  self->managers = g_slist_remove (self->managers, manager);
 
-  return G_SOURCE_CONTINUE;
+  g_signal_handlers_disconnect_by_func (manager, synchronizable_deleted_cb, self);
+  g_signal_handlers_disconnect_by_func (manager, synchronizable_modified_cb, self);
 }
 
+
 void
-ephy_sync_service_start_periodical_sync (EphySyncService *self,
-                                         gboolean         now)
+ephy_sync_service_do_sign_out (EphySyncService *self)
 {
   g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
 
-  if (ephy_sync_service_is_signed_in (self) == FALSE)
-    return;
+  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_service_forget_secrets (self);
+  g_clear_pointer (&self->account, g_free);
+
+  /* Clear storage messages queue. */
+  while (!g_queue_is_empty (self->storage_queue))
+    storage_request_async_data_free (g_queue_pop_head (self->storage_queue));
 
-  if (now == TRUE)
-    do_periodical_sync (self);
+  /* Clear managers. */
+  for (GSList *l = self->managers; l && l->data; l = l->next) {
+    g_signal_handlers_disconnect_by_func (l->data, synchronizable_deleted_cb, self);
+    g_signal_handlers_disconnect_by_func (l->data, synchronizable_modified_cb, self);
+  }
+  g_slist_free (self->managers);
+  self->managers = NULL;
 
-  self->source_id = g_timeout_add_seconds (SYNC_FREQUENCY, do_periodical_sync, self);
+  g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER, "");
+  g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_INITIAL, TRUE);
 }
 
 void
-ephy_sync_service_stop_periodical_sync (EphySyncService *self)
+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;
+  ephy_sync_service_sync (self);
+}
 
-  if (self->source_id != 0) {
-    g_source_remove (self->source_id);
-    self->source_id = 0;
-  }
+void
+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));
+
+  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..925fc31 100644
--- a/src/sync/ephy-sync-service.h
+++ b/src/sync/ephy-sync-service.h
@@ -20,11 +20,9 @@
 
 #pragma once
 
-#include "ephy-bookmark.h"
-#include "ephy-sync-utils.h"
+#include "ephy-synchronizable-manager.h"
 
 #include <glib-object.h>
-#include <libsoup/soup.h>
 
 G_BEGIN_DECLS
 
@@ -32,56 +30,21 @@ 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,
-                                                                 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);
+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);
+void               ephy_sync_service_do_sign_in             (EphySyncService *self,
+                                                             const char      *email,
+                                                             const char      *uid,
+                                                             const char      *session_token,
+                                                             const char      *key_fetch_token,
+                                                             const char      *unwrap_b_key);
+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_register_manager       (EphySyncService           *self,
+                                                             EphySynchronizableManager *manager);
+void               ephy_sync_service_unregister_manager     (EphySyncService           *self,
+                                                             EphySynchronizableManager *manager);
 
 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..a1def1d
--- /dev/null
+++ b/src/sync/ephy-synchronizable-manager.c
@@ -0,0 +1,249 @@
+/* -*- 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);
+
+enum {
+  SYNCHRONIZABLE_DELETED,
+  SYNCHRONIZABLE_MODIFIED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+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;
+
+  signals[SYNCHRONIZABLE_DELETED] =
+    g_signal_new ("synchronizable-deleted",
+                  EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 1,
+                  EPHY_TYPE_SYNCHRONIZABLE);
+
+  signals[SYNCHRONIZABLE_MODIFIED] =
+    g_signal_new ("synchronizable-modified",
+                  EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 1,
+                  EPHY_TYPE_SYNCHRONIZABLE);
+}
+
+/**
+ * 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 #GSList holding the #EphySynchronizable
+ *                   objects that were removed remotely from the server.
+ * @remotes_updated: (transfer none): a #GSList 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 #GSList holding the #EphySynchronizable
+ *               objects that need to be re-uploaded to server.
+ **/
+GSList *
+ephy_synchronizable_manager_merge_remotes (EphySynchronizableManager *manager,
+                                           gboolean                   is_initial,
+                                           GSList                    *remotes_deleted,
+                                           GSList                    *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..a59a2a0
--- /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);
+  GSList             * (*merge_remotes)           (EphySynchronizableManager *manager,
+                                                   gboolean                   is_initial,
+                                                   GSList                    *remotes_deleted,
+                                                   GSList                    *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);
+GSList             *ephy_synchronizable_manager_merge_remotes           (EphySynchronizableManager *manager,
+                                                                         gboolean                   
is_initial,
+                                                                         GSList                    
*remotes_deleted,
+                                                                         GSList                    
*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..410b54e
--- /dev/null
+++ b/src/sync/ephy-synchronizable.c
@@ -0,0 +1,246 @@
+/* -*- 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_server_time_modified = ephy_synchronizable_get_server_time_modified;
+  iface->set_server_time_modified = ephy_synchronizable_set_server_time_modified;
+  iface->to_bso = ephy_synchronizable_to_bso;
+}
+
+/**
+ * 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_server_time_modified:
+ * @synchronizable: an #EphySynchronizable
+ *
+ * Returns @synchronizable's last modification time on the storage server.
+ *
+ * Return value: @synchronizable's last modification time
+ **/
+double
+ephy_synchronizable_get_server_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_server_time_modified (synchronizable);
+}
+
+/**
+ * ephy_synchronizable_set_server_time_modified:
+ * @synchronizable: an #EphySynchronizable
+ * @server_time_modified: the last modification time on the storage server
+ *
+ * Sets @server_time_modified as @synchronizable's last modification time.
+ **/
+void
+ephy_synchronizable_set_server_time_modified (EphySynchronizable *synchronizable,
+                                              double              server_time_modified)
+{
+  EphySynchronizableInterface *iface;
+
+  g_return_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+  iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+  iface->set_server_time_modified (synchronizable, server_time_modified);
+}
+
+/**
+ * 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 server_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");
+  server_time_modified = json_object_get_double_member (json, "modified");
+  if (!payload || !server_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_server_time_modified (EPHY_SYNCHRONIZABLE (object),
+                                                server_time_modified);
+
+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..84f77da
--- /dev/null
+++ b/src/sync/ephy-synchronizable.h
@@ -0,0 +1,59 @@
+/* -*- 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>
+
+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_server_time_modified) (EphySynchronizable  *synchronizable);
+  void         (*set_server_time_modified) (EphySynchronizable  *synchronizable,
+                                            double               time_modified);
+  JsonNode *   (*to_bso)                   (EphySynchronizable  *synchronizable,
+                                            SyncCryptoKeyBundle *bundle);
+};
+
+const char *ephy_synchronizable_get_id                    (EphySynchronizable  *synchronizable);
+double      ephy_synchronizable_get_server_time_modified  (EphySynchronizable  *synchronizable);
+void        ephy_synchronizable_set_server_time_modified  (EphySynchronizable  *synchronizable,
+                                                           double               time_modified);
+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]