From 71949fd1a2fe826594a0c62b79d5ac8002911e3d Mon Sep 17 00:00:00 2001
From: Josh Kalderimis <josh.kalderimis@gmail.com>
Date: Fri, 24 May 2024 17:54:27 +1200
Subject: [PATCH] check time synchronization before socket connection

---
 lib/nerves_hub_link/configurator.ex | 10 +++--
 lib/nerves_hub_link/socket.ex       | 63 ++++++++++++++++++++++-------
 2 files changed, 54 insertions(+), 19 deletions(-)

diff --git a/lib/nerves_hub_link/configurator.ex b/lib/nerves_hub_link/configurator.ex
index ee47000..fbfb493 100644
--- a/lib/nerves_hub_link/configurator.ex
+++ b/lib/nerves_hub_link/configurator.ex
@@ -9,7 +9,6 @@ defmodule NervesHubLink.Configurator do
   defmodule Config do
     defstruct archive_public_keys: [],
               connect: true,
-              connect_wait_for_network: true,
               data_path: "/data/nerves-hub",
               device_api_host: nil,
               device_api_port: 443,
@@ -25,12 +24,13 @@ defmodule NervesHubLink.Configurator do
               request_fwup_public_keys: false,
               shared_secret: [],
               socket: [],
-              ssl: []
+              ssl: [],
+              wait_for_network: true,
+              wait_for_time_sync: true
 
     @type t() :: %__MODULE__{
             archive_public_keys: [binary()],
             connect: boolean(),
-            connect_wait_for_network: boolean(),
             data_path: Path.t(),
             device_api_host: String.t(),
             device_api_port: String.t(),
@@ -46,7 +46,9 @@ defmodule NervesHubLink.Configurator do
             request_fwup_public_keys: boolean(),
             shared_secret: [product_key: String.t(), product_secret: String.t()],
             socket: any(),
-            ssl: [:ssl.tls_client_option()]
+            ssl: [:ssl.tls_client_option()],
+            wait_for_network: boolean(),
+            wait_for_time_sync: boolean()
           }
   end
 
diff --git a/lib/nerves_hub_link/socket.ex b/lib/nerves_hub_link/socket.ex
index 154a584..bdb7477 100644
--- a/lib/nerves_hub_link/socket.ex
+++ b/lib/nerves_hub_link/socket.ex
@@ -90,12 +90,8 @@ defmodule NervesHubLink.Socket do
       |> assign(connected_at: nil)
       |> assign(joined_at: nil)
 
-    if config.connect_wait_for_network do
-      schedule_network_availability_check()
-      {:ok, socket}
-    else
-      {:ok, socket, {:continue, :connect}}
-    end
+    schedule_network_and_time_check()
+    {:ok, socket}
   end
 
   @impl Slipstream
@@ -388,15 +384,25 @@ defmodule NervesHubLink.Socket do
   end
 
   @impl Slipstream
-  def handle_info(:connect_check_network_availability, socket) do
-    case :inet.gethostbyname(to_charlist(socket.assigns.config.device_api_host)) do
-      {:ok, _} ->
+  def handle_info(:check_network_and_time, socket) do
+    cond do
+      network_connected?(socket) && time_synchronized?(socket) ->
         {:noreply, socket, {:continue, :connect}}
 
-      _ ->
-        Logger.info("[NervesHubLink] waiting for network to become available")
-        schedule_network_availability_check(2_000)
-        {:noreply, socket}
+      !network_connected?(socket) && !time_synchronized?(socket) ->
+        network_check_with_message(
+          "[NervesHubLink] waiting for network to become available and time to sync",
+          socket
+        )
+
+      !network_connected?(socket) ->
+        network_check_with_message(
+          "[NervesHubLink] waiting for network to become available",
+          socket
+        )
+
+      !time_synchronized?(socket) ->
+        network_check_with_message("[NervesHubLink] waiting for time to sync", socket)
     end
   end
 
@@ -447,6 +453,33 @@ defmodule NervesHubLink.Socket do
     {:noreply, socket}
   end
 
+  defp network_connected?(%{assigns: %{config: config}}) do
+    if config.wait_for_network do
+      host = to_charlist(config.device_api_host)
+
+      case :inet.gethostbyname(host) do
+        {:ok, _} -> true
+        _ -> false
+      end
+    else
+      true
+    end
+  end
+
+  defp time_synchronized?(%{assigns: %{config: config}}) do
+    if config.wait_for_time_sync do
+      NervesTime.synchronized?()
+    else
+      true
+    end
+  end
+
+  defp network_check_with_message(msg, socket) do
+    Logger.info(msg)
+    schedule_network_and_time_check(2_000)
+    {:noreply, socket}
+  end
+
   @impl Slipstream
   def handle_topic_close(topic, reason, socket) when reason != :left do
     if topic == @device_topic do
@@ -485,8 +518,8 @@ defmodule NervesHubLink.Socket do
     disconnect(socket)
   end
 
-  defp schedule_network_availability_check(delay \\ 100) do
-    Process.send_after(self(), :connect_check_network_availability, delay)
+  defp schedule_network_and_time_check(delay \\ 100) do
+    Process.send_after(self(), :check_network_and_time, delay)
   end
 
   defp handle_join_reply(%{"firmware_url" => url} = update, socket) when is_binary(url) do