{"action":"created","comment":{"url":"https://api.github.com/repos/endlessm/eos-updater/pulls/comments/68908862","id":68908862,"diff_hunk":"@@ -0,0 +1,920 @@\n+/* -*- mode: C; c-file-style: \"gnu\"; indent-tabs-mode: nil; -*-\n+ *\n+ * Copyright 2016 Kinvolk GmbH\n+ *\n+ * This library is free software; you can redistribute it and/or\n+ * modify it under the terms of the GNU Lesser General Public\n+ * License as published by the Free Software Foundation; either\n+ * version 2 of the License, or (at your option) any later version.\n+ *\n+ * This library is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+ * Lesser General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU Lesser General Public\n+ * License along with this library; if not, write to the\n+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,\n+ * Boston, MA 02111-1307, USA.\n+ *\n+ * Author: Krzesimir Nowak <krzesimir@kinvolk.io>\n+ */\n+\n+#include \"eos-updater-avahi.h\"\n+#include \"eos-updater-poll-common.h\"\n+#include \"eos-updater-poll-lan.h\"\n+\n+#include \"eos-util.h\"\n+\n+#include <string.h>\n+#include <errno.h>\n+\n+typedef enum\n+{\n+  TXT_RECORD_OK,\n+  TXT_RECORD_NOT_UNIQUE,\n+  TXT_RECORD_NOT_FOUND\n+} TxtRecordError;\n+\n+typedef struct LanData LanData;\n+\n+struct LanData\n+{\n+  EosMetadataFetchData *fetch_data;\n+  GMainLoop *main_loop;\n+  GError *error;\n+  EosUpdateInfo *info;\n+  EosMetricsInfo *metrics;\n+};\n+\n+#define LAN_DATA_CLEARED { NULL, NULL, NULL, NULL, NULL }\n+\n+static void\n+lan_data_init (LanData *lan_data,\n+               EosMetadataFetchData *fetch_data)\n+{\n+  if (lan_data == NULL)\n+    return;\n+\n+  g_return_if_fail (EOS_IS_METADATA_FETCH_DATA (fetch_data));\n+\n+  memset (lan_data, 0, sizeof (*lan_data));\n+  lan_data->fetch_data = g_object_ref (fetch_data);\n+  lan_data->main_loop = g_main_loop_new (fetch_data->context, FALSE);\n+}\n+\n+static void\n+lan_data_clear (LanData *lan_data)\n+{\n+  if (lan_data == NULL)\n+    return;\n+\n+  g_clear_object (&lan_data->metrics);\n+  g_clear_object (&lan_data->info);\n+  g_clear_error (&lan_data->error);\n+  g_clear_pointer (&lan_data->main_loop, g_main_loop_unref);\n+  g_clear_object (&lan_data->fetch_data);\n+}\n+\n+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (LanData, lan_data_clear)\n+\n+static TxtRecordError\n+fill_txt_records (gchar **txt_records,\n+                  GHashTable *keys)\n+{\n+  gchar **iter;\n+  g_autoptr(GPtrArray) keys_to_find = g_ptr_array_new ();\n+  const gchar *fail_key = NULL;\n+  TxtRecordError error = TXT_RECORD_OK;\n+\n+  {\n+    GHashTableIter key_iter;\n+    gpointer key;\n+\n+    g_hash_table_iter_init (&key_iter, keys);\n+    while (g_hash_table_iter_next (&key_iter, &key, NULL))\n+      g_ptr_array_add (keys_to_find, key);\n+  }\n+\n+  for (iter = txt_records; *iter != NULL; ++iter)\n+    {\n+      const gchar *txt_record = *iter;\n+      GHashTableIter key_iter;\n+      g_hash_table_iter_init (&key_iter, keys);\n+      const gchar *key;\n+\n+      while (g_hash_table_iter_next (&key_iter, (gpointer *)&key, NULL))\n+        {\n+          gsize key_len;\n+\n+          if (!g_str_has_prefix (txt_record, key))\n+            continue;\n+          key_len = strlen (key);\n+          if (txt_record[key_len] != '=')\n+            continue;\n+          if (g_hash_table_lookup (keys, key) != NULL)\n+            {\n+              fail_key = key;\n+              error = TXT_RECORD_NOT_UNIQUE;\n+              goto out;\n+            }\n+          g_hash_table_insert (keys, (gchar *)key, (gchar *)txt_record + key_len + 1);\n+          g_ptr_array_remove_fast (keys_to_find, (gchar *)key);\n+          break;\n+        }\n+    }\n+\n+  if (keys_to_find->len > 0)\n+    {\n+      fail_key = g_ptr_array_index (keys_to_find, 0);\n+      error = TXT_RECORD_NOT_FOUND;\n+    }\n+\n+ out:\n+  if (fail_key != NULL)\n+    {\n+      g_hash_table_steal_all (keys);\n+      g_hash_table_insert (keys, (gchar *)fail_key, NULL);\n+    }\n+  return error;\n+}\n+\n+static TxtRecordError\n+get_unique_txt_records (gchar **txt_records,\n+                        GHashTable **records,\n+                        ...)\n+{\n+  gchar *key = NULL;\n+  GHashTable *unique_keys = g_hash_table_new (g_str_hash, g_str_equal);\n+  va_list args;\n+\n+  va_start (args, records);\n+  for (;;)\n+    {\n+      key = va_arg (args, gchar*);\n+\n+      if (key == NULL)\n+        break;\n+      g_hash_table_insert (unique_keys, key, NULL);\n+    }\n+  va_end (args);\n+\n+  *records = unique_keys;\n+  return fill_txt_records (txt_records, unique_keys);\n+}\n+\n+static const gchar*\n+get_unique_txt_record (gchar **txt_records,\n+                       const gchar *key,\n+                       TxtRecordError *txt_error)\n+{\n+  g_autoptr(GHashTable) records = NULL;\n+  TxtRecordError error;\n+\n+  error = get_unique_txt_records (txt_records, &records, key, NULL);\n+  if (txt_error != NULL)\n+    *txt_error = error;\n+  if (error != TXT_RECORD_OK)\n+    return NULL;\n+\n+  /* the hash table does not manage the memory of its values, so we\n+     don't have to steal them */\n+  return g_hash_table_lookup (records, key);\n+}\n+\n+#define EOS_TYPE_SERVICE_WITH_BRANCH_FILE eos_service_with_branch_file_get_type ()\n+EOS_DECLARE_REFCOUNTED (EosServiceWithBranchFile,\n+                        eos_service_with_branch_file,\n+                        EOS,\n+                        SERVICE_WITH_BRANCH_FILE)\n+\n+struct _EosServiceWithBranchFile\n+{\n+  GObject parent_instance;\n+\n+  EosAvahiService *service;\n+  EosBranchFile *branch_file;\n+\n+  GDateTime *declared_download_time;\n+  gchar *declared_sha512sum;\n+};\n+\n+static void\n+eos_service_with_branch_file_dispose_impl (EosServiceWithBranchFile *swbf)\n+{\n+  g_clear_object (&swbf->service);\n+  g_clear_object (&swbf->branch_file);\n+  g_clear_pointer (&swbf->declared_download_time, g_date_time_unref);\n+}\n+\n+static void\n+eos_service_with_branch_file_finalize_impl (EosServiceWithBranchFile *swbf)\n+{\n+  g_free (swbf->declared_sha512sum);\n+}\n+\n+EOS_DEFINE_REFCOUNTED (EOS_SERVICE_WITH_BRANCH_FILE,\n+                       EosServiceWithBranchFile,\n+                       eos_service_with_branch_file,\n+                       eos_service_with_branch_file_dispose_impl,\n+                       eos_service_with_branch_file_finalize_impl)\n+\n+static EosServiceWithBranchFile *\n+eos_service_with_branch_file_new (EosAvahiService *service)\n+{\n+  EosServiceWithBranchFile *swbf = g_object_new (EOS_TYPE_SERVICE_WITH_BRANCH_FILE, NULL);\n+\n+  swbf->service = g_object_ref (service);\n+  swbf->branch_file = eos_branch_file_new_empty ();\n+\n+  return swbf;\n+}\n+\n+static void\n+eos_service_with_branch_file_get_from_lan_data (EosServiceWithBranchFile *swbf,\n+                                                LanData *lan_data)\n+{\n+  g_set_object (&swbf->branch_file, lan_data->fetch_data->data->branch_file);\n+}\n+\n+typedef gboolean (*ServiceCheck) (LanData *lan_data,\n+                                  EosServiceWithBranchFile *swbf,\n+                                  gboolean *valid,\n+                                  GError **error);\n+\n+typedef enum\n+  {\n+    SERVICE_INVALID,\n+    SERVICE_VALID_MORE_CHECKS,\n+    SERVICE_VALID\n+  } ServiceValidResult;\n+\n+static gboolean\n+check_ostree_path (LanData *lan_data,\n+                   const gchar *ostree_path,\n+                   gboolean *ok,\n+                   GError **error)\n+{\n+  g_autofree gchar *current_ostree_path = NULL;\n+  OstreeRepo *repo = lan_data->fetch_data->data->repo;\n+\n+  if (!eos_updater_get_ostree_path (repo, &current_ostree_path, error))\n+    return FALSE;\n+\n+  *ok = (g_strcmp0 (ostree_path, current_ostree_path) == 0);\n+  return TRUE;\n+}\n+\n+typedef enum\n+  {\n+    DL_TIME_NEW,\n+    DL_TIME_OLD,\n+    DL_TIME_INVALID,\n+    DL_TIME_OUT_OF_RANGE,\n+  } DlTimeCheckResult;\n+\n+static DlTimeCheckResult\n+check_dl_time (LanData *lan_data,\n+               const gchar *dl_time,\n+               GDateTime **utc_out)\n+{\n+  gint64 utc_time;\n+  gchar *utc_str_end = NULL;\n+  g_autoptr(GDateTime) utc = NULL;\n+\n+  if (dl_time[0] == '\\0')\n+    return DL_TIME_INVALID;\n+  errno = 0;\n+  utc_time = g_ascii_strtoll (dl_time, &utc_str_end, 10);\n+  if (errno == EINVAL)\n+    return DL_TIME_INVALID;\n+  if (errno == ERANGE)\n+    return DL_TIME_OUT_OF_RANGE;\n+  if (*utc_str_end != '\\0')\n+    return DL_TIME_INVALID;\n+  utc = g_date_time_new_from_unix_utc (utc_time);\n+  if (utc == NULL)\n+    return DL_TIME_INVALID;\n+  *utc_out = g_steal_pointer (&utc);\n+  if (g_date_time_compare (lan_data->fetch_data->data->branch_file->download_time, *utc_out) >= 0)\n+    return DL_TIME_OLD;\n+  return DL_TIME_NEW;\n+}\n+\n+static gboolean\n+txt_v1_handler_risky_checks (LanData *lan_data,\n+                             GHashTable *records,\n+                             gboolean *valid,\n+                             GError **error)\n+{\n+  if (!check_ostree_path (lan_data,\n+                          g_hash_table_lookup (records, eos_avahi_v1_ostree_path ()),\n+                          valid,\n+                          error))\n+    return FALSE;\n+  if (!*valid)\n+    // TODO: message\n+    return TRUE;\n+  return TRUE;\n+}\n+\n+static ServiceValidResult\n+txt_v1_handler_time_check (LanData *lan_data,\n+                           EosServiceWithBranchFile *swbf,\n+                           GHashTable *records)\n+{\n+  const gchar *dl_time;\n+  g_autoptr(GDateTime) txt_utc = NULL;\n+\n+  dl_time = g_hash_table_lookup (records, eos_avahi_v1_branch_file_dl_time ());\n+  switch (check_dl_time (lan_data, dl_time, &txt_utc))\n+    {\n+    case DL_TIME_OLD:\n+      eos_service_with_branch_file_get_from_lan_data (swbf, lan_data);\n+      return SERVICE_VALID;\n+\n+    case DL_TIME_NEW:\n+      swbf->declared_download_time = g_steal_pointer (&txt_utc);\n+      return SERVICE_VALID_MORE_CHECKS;\n+\n+    case DL_TIME_INVALID:\n+      // TODO: message\n+      return SERVICE_INVALID;\n+\n+    case DL_TIME_OUT_OF_RANGE:\n+      // TODO: message\n+      return SERVICE_INVALID;\n+    }\n+  // TODO: message\n+  g_assert_not_reached ();\n+  return SERVICE_INVALID;\n+}\n+\n+static gboolean\n+txt_v1_handler_checks (LanData *lan_data,\n+                       EosServiceWithBranchFile *swbf,\n+                       GHashTable *records)\n+{\n+  ServiceValidResult result;\n+\n+  result = txt_v1_handler_time_check (lan_data, swbf, records);\n+  if (result == SERVICE_VALID_MORE_CHECKS)\n+    {\n+      const gchar *sha512sum;\n+      const gchar *sha512sum_local;\n+\n+      sha512sum = g_hash_table_lookup (records, eos_avahi_v1_branch_file_sha512sum ());\n+      sha512sum_local = lan_data->fetch_data->data->branch_file->contents_sha512sum;\n+      if (g_strcmp0 (sha512sum_local, sha512sum) == 0)\n+        eos_service_with_branch_file_get_from_lan_data (swbf, lan_data);\n+      else\n+        swbf->declared_sha512sum = g_strdup (sha512sum);\n+      result = SERVICE_VALID;\n+    }\n+  if (result == SERVICE_VALID)\n+    return TRUE;\n+  return FALSE;\n+}\n+\n+static gboolean\n+txt_v1_handler (LanData *lan_data,\n+                EosServiceWithBranchFile *swbf,\n+                gboolean *valid,\n+                GError **error)\n+{\n+  g_autoptr(GHashTable) records = NULL;\n+  TxtRecordError txt_error = get_unique_txt_records (swbf->service->txt,\n+                                                     &records,\n+                                                     eos_avahi_v1_ostree_path (),\n+                                                     eos_avahi_v1_branch_file_dl_time (),\n+                                                     eos_avahi_v1_branch_file_sha512sum (),\n+                                                     NULL);\n+  if (txt_error != TXT_RECORD_OK)\n+    {\n+      // TODO: message\n+      *valid = FALSE;\n+      return TRUE;\n+    }\n+  if (!txt_v1_handler_risky_checks (lan_data,\n+                                    records,\n+                                    valid,\n+                                    error))\n+    return FALSE;\n+  if (!*valid)\n+    return TRUE;\n+  *valid = txt_v1_handler_checks (lan_data,\n+                                  swbf,\n+                                  records);\n+  return TRUE;\n+}\n+\n+static gboolean\n+txt_v2_handler_risky_checks (LanData *lan_data,\n+                             GHashTable *records,\n+                             gboolean *valid,\n+                             GError **error)\n+{\n+  if (!check_ostree_path (lan_data,\n+                          g_hash_table_lookup (records, eos_avahi_v2_ostree_path ()),\n+                          valid,\n+                          error))\n+    return FALSE;\n+  if (!*valid)\n+    // TODO: message\n+    return TRUE;\n+  return TRUE;\n+}\n+\n+static gboolean\n+txt_v2_handler_checks (LanData *lan_data,\n+                       EosServiceWithBranchFile *swbf,\n+                       GHashTable *records)\n+{\n+  const gchar *dl_time;\n+  g_autoptr(GDateTime) txt_utc = NULL;\n+\n+  dl_time = g_hash_table_lookup (records, eos_avahi_v2_branch_file_timestamp ());\n+  switch (check_dl_time (lan_data, dl_time, &txt_utc))\n+    {\n+    case DL_TIME_OLD:\n+      eos_service_with_branch_file_get_from_lan_data (swbf, lan_data);\n+      return TRUE;\n+\n+    case DL_TIME_NEW:\n+      swbf->declared_download_time = g_steal_pointer (&txt_utc);\n+      return TRUE;\n+\n+    case DL_TIME_INVALID:\n+      // TODO: message\n+      return FALSE;\n+\n+    case DL_TIME_OUT_OF_RANGE:\n+      // TODO: message\n+      return FALSE;\n+    }\n+\n+  // TODO: message\n+  g_assert_not_reached ();\n+  return FALSE;\n+}\n+\n+static gboolean\n+txt_v2_handler (LanData *lan_data,\n+                EosServiceWithBranchFile *swbf,\n+                gboolean *valid,\n+                GError **error)\n+{\n+  g_autoptr(GHashTable) records = NULL;\n+  TxtRecordError txt_error = get_unique_txt_records (swbf->service->txt,\n+                                                     &records,\n+                                                     eos_avahi_v2_ostree_path (),\n+                                                     eos_avahi_v2_branch_file_timestamp (),\n+                                                     NULL);\n+  if (txt_error != TXT_RECORD_OK)\n+    {\n+      // TODO: message\n+      *valid = FALSE;\n+      return TRUE;\n+    }\n+  if (!txt_v2_handler_risky_checks (lan_data,\n+                                    records,\n+                                    valid,\n+                                    error))\n+    return FALSE;\n+  *valid = txt_v2_handler_checks (lan_data,\n+                                  swbf,\n+                                  records);\n+  return TRUE;\n+}\n+\n+static ServiceCheck\n+get_txt_handler (const gchar *txt_version)\n+{\n+  guint64 v;\n+  const gchar *end = NULL;\n+\n+  errno = 0;\n+  v = g_ascii_strtoull (txt_version, (gchar **)&end, 10);\n+\n+  if (errno != 0 || end == NULL)\n+    return NULL;\n+\n+  if (v == 1 && *end == '\\0')\n+    return txt_v1_handler;\n+  if (v == 2 && *end == '\\0')\n+    return txt_v2_handler;\n+\n+  return NULL;\n+}\n+\n+// Puts services with branch file signatures in front of services\n+// without them.  Puts services with newer branch file timestamps in\n+// front of services with older ones.\n+static gint\n+g_compare_func_swbf_by_timestamp (gconstpointer swbf1_ptr_ptr,\n+                                  gconstpointer swbf2_ptr_ptr)\n+{\n+  EosServiceWithBranchFile *swbf1 = *((EosServiceWithBranchFile **)swbf1_ptr_ptr);\n+  EosServiceWithBranchFile *swbf2 = *((EosServiceWithBranchFile **)swbf2_ptr_ptr);\n+  gboolean with_signature1 = swbf1->declared_sha512sum == NULL;\n+  gboolean with_signature2 = swbf2->declared_sha512sum == NULL;\n+\n+  if ((with_signature1 ^ with_signature2) == 0)\n+    {\n+      // either both have signatures or neither\n+      GDateTime *timestamp1 = swbf1->declared_download_time ?\n+        swbf1->declared_download_time :\n+        swbf1->branch_file->download_time;\n+      GDateTime *timestamp2 = swbf2->declared_download_time ?\n+        swbf2->declared_download_time :\n+        swbf2->branch_file->download_time;\n+\n+      return g_date_time_compare (timestamp2, timestamp1);\n+    }\n+  // one has signature, the other not\n+  if (with_signature1)\n+    return -1;\n+  return 1;\n+}\n+\n+static gboolean\n+filter_services (LanData *lan_data,\n+                 GPtrArray *found_services,\n+                 GPtrArray **out_valid_services,\n+                 GError **error)\n+{\n+  guint idx;\n+  g_autoptr(GPtrArray) valid_services = object_array_new ();\n+\n+  for (idx = 0; idx < found_services->len; ++idx)\n+    {\n+      gpointer service_ptr = g_ptr_array_index (found_services, idx);\n+      EosAvahiService *service = EOS_AVAHI_SERVICE (service_ptr);\n+      TxtRecordError txt_error = TXT_RECORD_OK;\n+      const gchar *txt_version = get_unique_txt_record (service->txt,\n+                                                        \"eos_txt_version\",\n+                                                        &txt_error);\n+      ServiceCheck checker;\n+      gboolean valid = FALSE;\n+      g_autoptr(EosServiceWithBranchFile) swbf = NULL;\n+\n+      if (txt_version == NULL)\n+        {\n+          // TODO: message\n+          continue;\n+        }\n+      checker = get_txt_handler (txt_version);\n+      if (checker == NULL)\n+        {\n+          // TODO: message\n+          continue;\n+        }\n+      swbf = eos_service_with_branch_file_new (service);\n+      if (!checker (lan_data, swbf, &valid, error))\n+        return FALSE;\n+\n+      if (!valid)\n+        continue;\n+      g_ptr_array_add (valid_services, g_steal_pointer (&swbf));\n+    }\n+\n+  g_ptr_array_sort (valid_services, g_compare_func_swbf_by_timestamp);\n+  *out_valid_services = g_steal_pointer (&valid_services);\n+  return TRUE;\n+}\n+\n+static gchar *\n+get_branch_file_uri (EosAvahiService *service)\n+{\n+  return g_strdup_printf (\"http://%s:%\" G_GUINT16_FORMAT \"/extensions/eos/branch_file\",\n+                          service->address,\n+                          service->port);\n+}\n+\n+static EosBranchFile *\n+get_newest_branch_file (LanData *lan_data,\n+                        GPtrArray *valid_services)\n+{\n+  guint idx;\n+  GCancellable *cancellable;\n+\n+  cancellable = g_task_get_cancellable (lan_data->fetch_data->task);\n+  for (idx = 0; idx < valid_services->len; ++idx)\n+    {\n+      gpointer swbf_ptr = g_ptr_array_index (valid_services, idx);\n+      EosServiceWithBranchFile* swbf = EOS_SERVICE_WITH_BRANCH_FILE (swbf_ptr);\n+      g_autoptr(GBytes) branch_file_contents = NULL;\n+      g_autoptr(GBytes) signature_contents = NULL;\n+      g_autofree gchar *uri = get_branch_file_uri (swbf->service);\n+      g_autoptr(GError) error = NULL;\n+      g_autoptr(EosBranchFile) branch_file = NULL;\n+\n+      if (swbf->branch_file->branch_file != NULL)\n+        return g_object_ref (swbf->branch_file);\n+\n+      uri = get_branch_file_uri (swbf->service);\n+      if (!download_file_and_signature (uri,\n+                                        &branch_file_contents,\n+                                        &signature_contents,\n+                                        &error))\n+        {\n+          message (\"Failed to download branch file from %s: %s\",\n+                   uri,\n+                   error->message);\n+          continue;\n+        }\n+\n+      if (branch_file_contents == NULL)\n+        {\n+          message (\"Branch file on %s not found\", uri);\n+          continue;\n+        }\n+\n+      branch_file = eos_branch_file_new_from_raw (branch_file_contents,\n+                                                  signature_contents,\n+                                                  swbf->declared_download_time,\n+                                                  &error);\n+      if (branch_file == NULL)\n+        {\n+          message (\"Branch file from %s is wrong: %s\",\n+                   uri, error->message);\n+          continue;\n+        }\n+\n+      if (signature_contents != NULL)\n+        {\n+          g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL;\n+          OstreeRepo *repo = lan_data->fetch_data->data->repo;\n+          gpg_result = ostree_repo_gpg_verify_data (repo,\n+                                                    NULL,\n+                                                    branch_file_contents,\n+                                                    signature_contents,\n+                                                    NULL,\n+                                                    NULL,\n+                                                    cancellable,\n+                                                    &error);\n+          if (!ostree_gpg_verify_result_require_valid_signature (gpg_result,\n+                                                                 &error))\n+            {\n+              message (\"Branch file from %s has invalid signature: %s\",\n+                       uri,\n+                       error->message);\n+              continue;\n+            }\n+\n+          if (g_date_time_difference (swbf->declared_download_time,\n+                                      branch_file->download_time) != 0)\n+            {\n+              gint64 declared = g_date_time_to_unix (swbf->declared_download_time);\n+              gint64 from_file = g_date_time_to_unix (branch_file->download_time);\n+\n+              message (\"The %s declared timestamp (%\" G_GINT64_FORMAT \") is different from the timestamp in branch file (%\" G_GINT64_FORMAT \"), looks suspicious\",\n+                       uri,\n+                       declared,\n+                       from_file);\n+              continue;\n+            }\n+        }\n+\n+      return g_steal_pointer (&branch_file);\n+    }\n+\n+  /* no valid branch file found */\n+  return NULL;\n+}\n+\n+/*\n+ * TODO: async fetch_ref_file ([](){\n+ *   checksum = get_from_ref_file\n+ *   commit = NULL;\n+ *   if (checksum_is_old (checksum))\n+ *     return\n+ *   if (checksum_unknown (checksum))\n+ *     commit = fetch_the_commit(checksum)\n+ *     if commit_is_an_update (commit)\n+ *       is_commit_valid (commit)\n+ *         make_checksum_old (latest_checksum)\n+ *         latest_checksum = checksum\n+ *         latest_services = service\n+ *     else\n+ *       make_checksum_old (checksum)\n+ */\n+static gboolean\n+get_update_info_from_swbfs (LanData *lan_data,\n+                            GPtrArray *swbfs,\n+                            EosBranchFile *newest,\n+                            EosUpdateInfo **out_info,\n+                            EosMetricsInfo **out_metrics,\n+                            GError **error)\n+{\n+  guint idx;\n+  g_autofree gchar *refspec = NULL;\n+  g_autofree gchar *original_refspec = NULL;\n+  g_autoptr(EosMetricsInfo) metrics = NULL;\n+  g_autofree gchar *remote = NULL;\n+  g_autofree gchar *ref = NULL;\n+  g_autofree gchar *latest_checksum = NULL;\n+  guint64 latest_timestamp = 0;\n+  g_autoptr(GPtrArray) swbfs_with_latest_commit = NULL;\n+  g_autoptr(GVariant) latest_commit = NULL;\n+  g_autoptr(GPtrArray) urls = NULL;\n+  g_autoptr(EosExtensions) latest_extensions = NULL;\n+  OstreeRepo *repo = lan_data->fetch_data->data->repo;\n+\n+  if (!get_upgrade_info_from_branch_file (newest,\n+                                          &refspec,\n+                                          &original_refspec,\n+                                          &metrics,\n+                                          error))\n+    return FALSE;\n+\n+  if (!ostree_parse_refspec (refspec, &remote, &ref, error))\n+    return FALSE;\n+\n+  swbfs_with_latest_commit = object_array_new ();\n+  urls = g_ptr_array_new_with_free_func (g_free);\n+  for (idx = 0; idx < swbfs->len; ++idx)\n+    {\n+      gpointer swbf_ptr = g_ptr_array_index (swbfs, idx);\n+      EosServiceWithBranchFile *swbf = EOS_SERVICE_WITH_BRANCH_FILE (swbf_ptr);\n+      EosAvahiService *service = swbf->service;\n+      g_autofree gchar *url_override = NULL;\n+      g_autoptr(GError) local_error = NULL;\n+      g_autofree gchar *checksum = NULL;\n+      g_autoptr(GVariant) commit = NULL;\n+      guint64 timestamp;\n+      g_autoptr(EosExtensions) extensions = NULL;\n+      GCancellable *cancellable;\n+\n+      cancellable = g_task_get_cancellable (lan_data->fetch_data->task);\n+      url_override = g_strdup_printf (\"http://%s:%d\",\n+                                      service->address,\n+                                      service->port);\n+      if (!fetch_latest_commit (repo,\n+                                cancellable,\n+                                remote,\n+                                ref,\n+                                url_override,\n+                                &checksum,\n+                                &extensions,\n+                                &local_error))\n+        {\n+          message (\"Failed to fetch latest commit from %s: %s\",\n+                   url_override,\n+                   local_error->message);\n+          continue;\n+        }\n+\n+      if (!is_checksum_an_update (repo, checksum, &commit, &local_error))\n+        {\n+          message (\"Commit %s from %s is not an update\",\n+                   checksum,\n+                   url_override);\n+          continue;\n+        }\n+\n+      timestamp = ostree_commit_get_timestamp (commit);\n+      if (latest_checksum != NULL)\n+        {\n+          if (timestamp < latest_timestamp)\n+            continue;\n+\n+          if (timestamp == latest_timestamp && g_strcmp0(checksum, latest_checksum) == 0)\n+            {\n+              g_ptr_array_add (swbfs_with_latest_commit, g_object_ref (swbf));\n+              g_ptr_array_add (urls, g_steal_pointer (&url_override));\n+            }\n+          else if (timestamp > latest_timestamp && g_strcmp0(checksum, latest_checksum) != 0)","path":"src/eos-updater-poll-lan.c","position":779,"original_position":787,"commit_id":"1a14ec76f491e037a71ac15d61ee2d73e7564805","original_commit_id":"c28e9923be1771cd8e7310e33e5f5c393e3f757b","user":{"login":"krnowak","id":96081,"avatar_url":"https://avatars.githubusercontent.com/u/96081?v=3","gravatar_id":"","url":"https://api.github.com/users/krnowak","html_url":"https://github.com/krnowak","followers_url":"https://api.github.com/users/krnowak/followers","following_url":"https://api.github.com/users/krnowak/following{/other_user}","gists_url":"https://api.github.com/users/krnowak/gists{/gist_id}","starred_url":"https://api.github.com/users/krnowak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/krnowak/subscriptions","organizations_url":"https://api.github.com/users/krnowak/orgs","repos_url":"https://api.github.com/users/krnowak/repos","events_url":"https://api.github.com/users/krnowak/events{/privacy}","received_events_url":"https://api.github.com/users/krnowak/received_events","type":"User","site_admin":false},"body":"Yeah, it should imply that. I'll add more checks for that.","created_at":"2016-06-29T08:58:53Z","updated_at":"2016-06-29T08:58:53Z","html_url":"https://github.com/endlessm/eos-updater/pull/37#discussion_r68908862","pull_request_url":"https://api.github.com/repos/endlessm/eos-updater/pulls/37","_links":{"self":{"href":"https://api.github.com/repos/endlessm/eos-updater/pulls/comments/68908862"},"html":{"href":"https://github.com/endlessm/eos-updater/pull/37#discussion_r68908862"},"pull_request":{"href":"https://api.github.com/repos/endlessm/eos-updater/pulls/37"}}},"pull_request":{"url":"https://api.github.com/repos/endlessm/eos-updater/pulls/37","id":72008936,"html_url":"https://github.com/endlessm/eos-updater/pull/37","diff_url":"https://github.com/endlessm/eos-updater/pull/37.diff","patch_url":"https://github.com/endlessm/eos-updater/pull/37.patch","issue_url":"https://api.github.com/repos/endlessm/eos-updater/issues/37","number":37,"state":"open","locked":false,"title":"LAN client and server, tests","user":{"login":"krnowak","id":96081,"avatar_url":"https://avatars.githubusercontent.com/u/96081?v=3","gravatar_id":"","url":"https://api.github.com/users/krnowak","html_url":"https://github.com/krnowak","followers_url":"https://api.github.com/users/krnowak/followers","following_url":"https://api.github.com/users/krnowak/following{/other_user}","gists_url":"https://api.github.com/users/krnowak/gists{/gist_id}","starred_url":"https://api.github.com/users/krnowak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/krnowak/subscriptions","organizations_url":"https://api.github.com/users/krnowak/orgs","repos_url":"https://api.github.com/users/krnowak/repos","events_url":"https://api.github.com/users/krnowak/events{/privacy}","received_events_url":"https://api.github.com/users/krnowak/received_events","type":"User","site_admin":false},"body":"Sorry about the code-droppy nature of this PR. The commits are basically per file as the work grew a bit. The changes done are:\r\n\r\n- add eos-updater daemon config\r\n  - for now it only has the \"Download\" group with an \"Order\" field being a comma-separated list of download sources in order of preference (currently the only possible values are \"lan\" and \"main\").\r\n- add polling for updates on LAN\r\n  - it starts EosAvahiDiscoverer to get the list of servers\r\n  - there are two versions of txt records possible:\r\n    - version 1 is mostly for servers that do not have signed branch file, so in txt records it contains:\r\n      - sha512sum of the branch file\r\n      - download time of the branch file from the main server\r\n      - ostree path\r\n    - version 2 is for the servers that do have signed branch file, so in txt records it contains:\r\n      - ostree path (should be exposed in a branch file too, so we can verify it, not yet implemented)\r\n      - branch file timestamp (verified against a timestamp in the branch file)\r\n    - version 2 does not expose hashed sum in txt records, \r\n      - signature serves the role of the hash checksum\r\n      - branch file has the new \"UnixUTCTimestamp\" field in the \"main\" group\r\n      - branch file would have the new \"OstreePath\" field in the \"main\" group, not implemented\r\n  - once the services are discovered, it downloads the newest branch file\r\n    - signed branch files are preferred over the unsigned ones, \r\n    - for version 1 of txt records we skip downloading the branch file if the timestamp was older or the sha512sum was the same\r\n    - if there are no signed branch files, we search the newest unsigned one\r\n  - it processes the branch file to learn about OstreeRef for the device\r\n    - if OnHold field is set to true, we stop the update\r\n  - it downloads ref->commit information from every service to learn what commit they have for upgrade\r\n    - this might be a lot of churn, maybe ref->commit information should be exposed in avahi txt records too?\r\n  - it filters the services so it has a list of services that offer the newest commit\r\n  - it prepares the update info and the metrics info\r\n- update polling from the main server\r\n  - now it can download the signature of the branch file\r\n  - it queries the main server about the latest commit from the ref->commit mapping\r\n  - it prepares the update info and the metrics info\r\n- update fetching the update\r\n  - the update info prepared in poll stage contains an optional list of url overrides\r\n  - the url actually used for fetching the updates is selected randomly \r\n- update applying the update\r\n  - store the extra files in ostree repo's extensions directory\r\n    - this is basically /ostree/repo/extensions/eos\r\n    - there we store the branch file with the signature and ref->commit mappings with signatures\r\n  - generate avahi service file\r\n- add LAN update server\r\n  - it serves the files in ostree repository\r\n    - repo config is faked to make ostree pull to believe it is pulling from archive-z2 repository\r\n    - refs/heads/\\<ref\\> are handled specially by serving /refs/remotes/\\<remote\\>/\\<ref\\>\r\n    - requests for /objects/\\<checksum\\>.filez are handled by zipping the objects with requested checksum on the fly\r\n    - rest of the files are served as is\r\n  - the server can listen on local port or on socket given to it by systemd\r\n  - the server shuts itself down after some period of inactivity\r\n- add some tests\r\n  - one is checking if updating from the main server works\r\n  - one is checking if updating from the lan server works","created_at":"2016-05-31T12:45:14Z","updated_at":"2016-06-29T08:58:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"63cf7c277bb9c175ea985c1cfa11854f240b5d7f","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/endlessm/eos-updater/pulls/37/commits","review_comments_url":"https://api.github.com/repos/endlessm/eos-updater/pulls/37/comments","review_comment_url":"https://api.github.com/repos/endlessm/eos-updater/pulls/comments{/number}","comments_url":"https://api.github.com/repos/endlessm/eos-updater/issues/37/comments","statuses_url":"https://api.github.com/repos/endlessm/eos-updater/statuses/1a14ec76f491e037a71ac15d61ee2d73e7564805","head":{"label":"kinvolk:krnowak/avahi","ref":"krnowak/avahi","sha":"1a14ec76f491e037a71ac15d61ee2d73e7564805","user":{"login":"kinvolk","id":14073052,"avatar_url":"https://avatars.githubusercontent.com/u/14073052?v=3","gravatar_id":"","url":"https://api.github.com/users/kinvolk","html_url":"https://github.com/kinvolk","followers_url":"https://api.github.com/users/kinvolk/followers","following_url":"https://api.github.com/users/kinvolk/following{/other_user}","gists_url":"https://api.github.com/users/kinvolk/gists{/gist_id}","starred_url":"https://api.github.com/users/kinvolk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kinvolk/subscriptions","organizations_url":"https://api.github.com/users/kinvolk/orgs","repos_url":"https://api.github.com/users/kinvolk/repos","events_url":"https://api.github.com/users/kinvolk/events{/privacy}","received_events_url":"https://api.github.com/users/kinvolk/received_events","type":"Organization","site_admin":false},"repo":{"id":56606373,"name":"eos-updater","full_name":"kinvolk/eos-updater","owner":{"login":"kinvolk","id":14073052,"avatar_url":"https://avatars.githubusercontent.com/u/14073052?v=3","gravatar_id":"","url":"https://api.github.com/users/kinvolk","html_url":"https://github.com/kinvolk","followers_url":"https://api.github.com/users/kinvolk/followers","following_url":"https://api.github.com/users/kinvolk/following{/other_user}","gists_url":"https://api.github.com/users/kinvolk/gists{/gist_id}","starred_url":"https://api.github.com/users/kinvolk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kinvolk/subscriptions","organizations_url":"https://api.github.com/users/kinvolk/orgs","repos_url":"https://api.github.com/users/kinvolk/repos","events_url":"https://api.github.com/users/kinvolk/events{/privacy}","received_events_url":"https://api.github.com/users/kinvolk/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/kinvolk/eos-updater","description":"System components of the OS Tree based updater","fork":true,"url":"https://api.github.com/repos/kinvolk/eos-updater","forks_url":"https://api.github.com/repos/kinvolk/eos-updater/forks","keys_url":"https://api.github.com/repos/kinvolk/eos-updater/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kinvolk/eos-updater/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kinvolk/eos-updater/teams","hooks_url":"https://api.github.com/repos/kinvolk/eos-updater/hooks","issue_events_url":"https://api.github.com/repos/kinvolk/eos-updater/issues/events{/number}","events_url":"https://api.github.com/repos/kinvolk/eos-updater/events","assignees_url":"https://api.github.com/repos/kinvolk/eos-updater/assignees{/user}","branches_url":"https://api.github.com/repos/kinvolk/eos-updater/branches{/branch}","tags_url":"https://api.github.com/repos/kinvolk/eos-updater/tags","blobs_url":"https://api.github.com/repos/kinvolk/eos-updater/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kinvolk/eos-updater/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kinvolk/eos-updater/git/refs{/sha}","trees_url":"https://api.github.com/repos/kinvolk/eos-updater/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kinvolk/eos-updater/statuses/{sha}","languages_url":"https://api.github.com/repos/kinvolk/eos-updater/languages","stargazers_url":"https://api.github.com/repos/kinvolk/eos-updater/stargazers","contributors_url":"https://api.github.com/repos/kinvolk/eos-updater/contributors","subscribers_url":"https://api.github.com/repos/kinvolk/eos-updater/subscribers","subscription_url":"https://api.github.com/repos/kinvolk/eos-updater/subscription","commits_url":"https://api.github.com/repos/kinvolk/eos-updater/commits{/sha}","git_commits_url":"https://api.github.com/repos/kinvolk/eos-updater/git/commits{/sha}","comments_url":"https://api.github.com/repos/kinvolk/eos-updater/comments{/number}","issue_comment_url":"https://api.github.com/repos/kinvolk/eos-updater/issues/comments{/number}","contents_url":"https://api.github.com/repos/kinvolk/eos-updater/contents/{+path}","compare_url":"https://api.github.com/repos/kinvolk/eos-updater/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kinvolk/eos-updater/merges","archive_url":"https://api.github.com/repos/kinvolk/eos-updater/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kinvolk/eos-updater/downloads","issues_url":"https://api.github.com/repos/kinvolk/eos-updater/issues{/number}","pulls_url":"https://api.github.com/repos/kinvolk/eos-updater/pulls{/number}","milestones_url":"https://api.github.com/repos/kinvolk/eos-updater/milestones{/number}","notifications_url":"https://api.github.com/repos/kinvolk/eos-updater/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kinvolk/eos-updater/labels{/name}","releases_url":"https://api.github.com/repos/kinvolk/eos-updater/releases{/id}","deployments_url":"https://api.github.com/repos/kinvolk/eos-updater/deployments","created_at":"2016-04-19T14:57:08Z","updated_at":"2016-04-19T14:57:08Z","pushed_at":"2016-06-28T15:48:15Z","git_url":"git://github.com/kinvolk/eos-updater.git","ssh_url":"git@github.com:kinvolk/eos-updater.git","clone_url":"https://github.com/kinvolk/eos-updater.git","svn_url":"https://github.com/kinvolk/eos-updater","homepage":null,"size":350,"stargazers_count":0,"watchers_count":0,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"endlessm:master","ref":"master","sha":"2c569772f36a29189b9c36bff330c3648fb7dc95","user":{"login":"endlessm","id":1930958,"avatar_url":"https://avatars.githubusercontent.com/u/1930958?v=3","gravatar_id":"","url":"https://api.github.com/users/endlessm","html_url":"https://github.com/endlessm","followers_url":"https://api.github.com/users/endlessm/followers","following_url":"https://api.github.com/users/endlessm/following{/other_user}","gists_url":"https://api.github.com/users/endlessm/gists{/gist_id}","starred_url":"https://api.github.com/users/endlessm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/endlessm/subscriptions","organizations_url":"https://api.github.com/users/endlessm/orgs","repos_url":"https://api.github.com/users/endlessm/repos","events_url":"https://api.github.com/users/endlessm/events{/privacy}","received_events_url":"https://api.github.com/users/endlessm/received_events","type":"Organization","site_admin":false},"repo":{"id":13253303,"name":"eos-updater","full_name":"endlessm/eos-updater","owner":{"login":"endlessm","id":1930958,"avatar_url":"https://avatars.githubusercontent.com/u/1930958?v=3","gravatar_id":"","url":"https://api.github.com/users/endlessm","html_url":"https://github.com/endlessm","followers_url":"https://api.github.com/users/endlessm/followers","following_url":"https://api.github.com/users/endlessm/following{/other_user}","gists_url":"https://api.github.com/users/endlessm/gists{/gist_id}","starred_url":"https://api.github.com/users/endlessm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/endlessm/subscriptions","organizations_url":"https://api.github.com/users/endlessm/orgs","repos_url":"https://api.github.com/users/endlessm/repos","events_url":"https://api.github.com/users/endlessm/events{/privacy}","received_events_url":"https://api.github.com/users/endlessm/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/endlessm/eos-updater","description":"System components of the OS Tree based updater","fork":false,"url":"https://api.github.com/repos/endlessm/eos-updater","forks_url":"https://api.github.com/repos/endlessm/eos-updater/forks","keys_url":"https://api.github.com/repos/endlessm/eos-updater/keys{/key_id}","collaborators_url":"https://api.github.com/repos/endlessm/eos-updater/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/endlessm/eos-updater/teams","hooks_url":"https://api.github.com/repos/endlessm/eos-updater/hooks","issue_events_url":"https://api.github.com/repos/endlessm/eos-updater/issues/events{/number}","events_url":"https://api.github.com/repos/endlessm/eos-updater/events","assignees_url":"https://api.github.com/repos/endlessm/eos-updater/assignees{/user}","branches_url":"https://api.github.com/repos/endlessm/eos-updater/branches{/branch}","tags_url":"https://api.github.com/repos/endlessm/eos-updater/tags","blobs_url":"https://api.github.com/repos/endlessm/eos-updater/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/endlessm/eos-updater/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/endlessm/eos-updater/git/refs{/sha}","trees_url":"https://api.github.com/repos/endlessm/eos-updater/git/trees{/sha}","statuses_url":"https://api.github.com/repos/endlessm/eos-updater/statuses/{sha}","languages_url":"https://api.github.com/repos/endlessm/eos-updater/languages","stargazers_url":"https://api.github.com/repos/endlessm/eos-updater/stargazers","contributors_url":"https://api.github.com/repos/endlessm/eos-updater/contributors","subscribers_url":"https://api.github.com/repos/endlessm/eos-updater/subscribers","subscription_url":"https://api.github.com/repos/endlessm/eos-updater/subscription","commits_url":"https://api.github.com/repos/endlessm/eos-updater/commits{/sha}","git_commits_url":"https://api.github.com/repos/endlessm/eos-updater/git/commits{/sha}","comments_url":"https://api.github.com/repos/endlessm/eos-updater/comments{/number}","issue_comment_url":"https://api.github.com/repos/endlessm/eos-updater/issues/comments{/number}","contents_url":"https://api.github.com/repos/endlessm/eos-updater/contents/{+path}","compare_url":"https://api.github.com/repos/endlessm/eos-updater/compare/{base}...{head}","merges_url":"https://api.github.com/repos/endlessm/eos-updater/merges","archive_url":"https://api.github.com/repos/endlessm/eos-updater/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/endlessm/eos-updater/downloads","issues_url":"https://api.github.com/repos/endlessm/eos-updater/issues{/number}","pulls_url":"https://api.github.com/repos/endlessm/eos-updater/pulls{/number}","milestones_url":"https://api.github.com/repos/endlessm/eos-updater/milestones{/number}","notifications_url":"https://api.github.com/repos/endlessm/eos-updater/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/endlessm/eos-updater/labels{/name}","releases_url":"https://api.github.com/repos/endlessm/eos-updater/releases{/id}","deployments_url":"https://api.github.com/repos/endlessm/eos-updater/deployments","created_at":"2013-10-01T19:23:58Z","updated_at":"2016-03-02T21:58:39Z","pushed_at":"2016-06-28T15:50:39Z","git_url":"git://github.com/endlessm/eos-updater.git","ssh_url":"git@github.com:endlessm/eos-updater.git","clone_url":"https://github.com/endlessm/eos-updater.git","svn_url":"https://github.com/endlessm/eos-updater","homepage":null,"size":279,"stargazers_count":0,"watchers_count":0,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":2,"forks":1,"open_issues":2,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/endlessm/eos-updater/pulls/37"},"html":{"href":"https://github.com/endlessm/eos-updater/pull/37"},"issue":{"href":"https://api.github.com/repos/endlessm/eos-updater/issues/37"},"comments":{"href":"https://api.github.com/repos/endlessm/eos-updater/issues/37/comments"},"review_comments":{"href":"https://api.github.com/repos/endlessm/eos-updater/pulls/37/comments"},"review_comment":{"href":"https://api.github.com/repos/endlessm/eos-updater/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/endlessm/eos-updater/pulls/37/commits"},"statuses":{"href":"https://api.github.com/repos/endlessm/eos-updater/statuses/1a14ec76f491e037a71ac15d61ee2d73e7564805"}}}}
{"actor":{"display_login":"krnowak"}}