[#59552] ExternalControl: Add getting current virtual time
diff --git a/src/Renode/Network/ExternalControl/ExternalControlServer.cs b/src/Renode/Network/ExternalControl/ExternalControlServer.cs
index 00f7f6d..f786f9a 100644
--- a/src/Renode/Network/ExternalControl/ExternalControlServer.cs
+++ b/src/Renode/Network/ExternalControl/ExternalControlServer.cs
@@ -35,6 +35,7 @@
 
             commandHandlers = new CommandHandlerCollection();
             commandHandlers.Register(new RunFor(this));
+            commandHandlers.Register(new GetTime(this));
 
             socketServerProvider.ConnectionAccepted += delegate
             {
diff --git a/src/Renode/Network/ExternalControl/GetTime.cs b/src/Renode/Network/ExternalControl/GetTime.cs
new file mode 100644
index 0000000..0c96b5d
--- /dev/null
+++ b/src/Renode/Network/ExternalControl/GetTime.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) 2010-2024 Antmicro
+//
+// This file is licensed under the MIT License.
+// Full license text is available in 'licenses/MIT.txt'.
+//
+using System.Collections.Generic;
+using Antmicro.Renode.Core;
+using Antmicro.Renode.Logging;
+using Antmicro.Renode.Utilities;
+
+namespace Antmicro.Renode.Network.ExternalControl
+{
+    public class GetTime : BaseCommand
+    {
+        public GetTime(IEmulationElement parent)
+            : base(parent)
+        {
+        }
+
+        public override Response Invoke(List<byte> data)
+        {
+            var timestamp = EmulationManager.Instance.CurrentEmulation.MasterTimeSource.ElapsedVirtualTime;
+            parent.Log(LogLevel.Info, "Executing GetTime command: {0}", timestamp);
+
+            return Response.Success(Identifier, timestamp.TotalMicroseconds.AsRawBytes());
+        }
+
+        public override Command Identifier => Command.GetTime;
+        public override byte Version => 0x0;
+    }
+}
diff --git a/src/Renode/Network/ExternalControl/ICommand.cs b/src/Renode/Network/ExternalControl/ICommand.cs
index dd0086a..f58f928 100644
--- a/src/Renode/Network/ExternalControl/ICommand.cs
+++ b/src/Renode/Network/ExternalControl/ICommand.cs
@@ -11,6 +11,7 @@
     public enum Command : byte
     {
         RunFor = 1,
+        GetTime,
     }
 
     public interface ICommand
diff --git a/src/Renode/Renode.csproj b/src/Renode/Renode.csproj
index 9be62b6..a558c2a 100644
--- a/src/Renode/Renode.csproj
+++ b/src/Renode/Renode.csproj
@@ -134,6 +134,7 @@
     <Compile Include="Network\ExternalControl\ICommand.cs" />
     <Compile Include="Network\ExternalControl\Response.cs" />
     <Compile Include="Network\ExternalControl\RunFor.cs" />
+    <Compile Include="Network\ExternalControl\GetTime.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
diff --git a/tools/external_control_client/README.md b/tools/external_control_client/README.md
index 810f3d4..7817bd8 100644
--- a/tools/external_control_client/README.md
+++ b/tools/external_control_client/README.md
@@ -27,6 +27,7 @@
 * `renode_connect`
 * `renode_disconnect`
 * `renode_run_for`
+* `renode_get_current_time`
 * `renode_error_free`
 
 The library itself can be built with CMake using the `./lib/CMakeLists.txt`.
diff --git a/tools/external_control_client/examples/run_for/main.c b/tools/external_control_client/examples/run_for/main.c
index 4564142..40f89b0 100644
--- a/tools/external_control_client/examples/run_for/main.c
+++ b/tools/external_control_client/examples/run_for/main.c
@@ -33,6 +33,19 @@
     return error->message;
 }
 
+uint64_t get_current_virtual_time(renode_t *renode)
+{
+    renode_error_t *error;
+    uint64_t current_time;
+    if ((error = renode_get_current_time(renode, TU_MICROSECONDS, &current_time)) != NO_ERROR) {
+        fprintf(stderr, "Get current time failed with: %s\n", get_error_message(error));
+        renode_free_error(error);
+        exit(EXIT_FAILURE);
+    }
+
+    return current_time;
+}
+
 int main(int argc, char **argv)
 {
     if (argc != 3) {
@@ -74,17 +87,36 @@
         exit(EXIT_FAILURE);
     }
 
+    uint64_t t0 = get_current_virtual_time(renode);
+
     if ((error = renode_run_for(renode, time_unit, value)) != NO_ERROR) {
         fprintf(stderr, "Run for failed with: %s\n", get_error_message(error));
         renode_free_error(error);
         exit(EXIT_FAILURE);
     }
 
+    uint64_t t1 = get_current_virtual_time(renode);
+    uint64_t t_delta = t1 - t0;
+
     if ((error = renode_disconnect(&renode)) != NO_ERROR) {
         fprintf(stderr, "Disconnecting from Renode failed with: %s\n", get_error_message(error));
         renode_free_error(error);
         exit(EXIT_FAILURE);
     }
 
+    int microseconds = t1 % TU_SECONDS;
+    int seconds_total = t1 / TU_SECONDS;
+    int seconds = seconds_total % 60;
+    int minutes_total = seconds_total / 60;
+    int minutes = minutes_total % 60;
+    int hours_total = minutes_total / 60;
+
+    printf("Elapsed virtual time %02d:%02d:%02d.%06d\n", hours_total, minutes, seconds, microseconds);
+
+    if (t_delta != value * time_unit) {
+        fprintf(stderr, "Reported current virtual time doesn't match the expected virtual time after running for the requested interval\n");
+        exit(EXIT_FAILURE);
+    }
+
     exit(EXIT_SUCCESS);
 }
diff --git a/tools/external_control_client/include/renode_api.h b/tools/external_control_client/include/renode_api.h
index 8d67831..c333f4f 100644
--- a/tools/external_control_client/include/renode_api.h
+++ b/tools/external_control_client/include/renode_api.h
@@ -47,3 +47,4 @@
 } renode_time_unit_t;
 
 renode_error_t *renode_run_for(renode_t *renode, renode_time_unit_t unit, uint64_t value);
+renode_error_t *renode_get_current_time(renode_t *renode_instance, renode_time_unit_t unit, uint64_t *current_time);
diff --git a/tools/external_control_client/lib/renode_api.c b/tools/external_control_client/lib/renode_api.c
index b45de96..7f286ea 100644
--- a/tools/external_control_client/lib/renode_api.c
+++ b/tools/external_control_client/lib/renode_api.c
@@ -114,11 +114,13 @@
 
 typedef enum {
     RUN_FOR = 1,
+    GET_TIME,
 } api_command_t;
 
 static uint8_t command_versions[][2] = {
     { 0x0, 0x0 }, // reserved for size
     { RUN_FOR, 0x0 },
+    { GET_TIME, 0x0 },
 };
 
 static renode_error_t *write_or_fail(int socket_fd, const uint8_t *data, ssize_t count)
@@ -400,3 +402,29 @@
 
     return renode_execute_command(renode, RUN_FOR, &data, sizeof(data), sizeof(data), NULL);
 }
+
+renode_error_t *renode_get_current_time(renode_t *renode, renode_time_unit_t unit, uint64_t *current_time)
+{
+    assert(renode != NULL);
+
+    uint64_t divider;
+    switch (unit) {
+        case TU_MICROSECONDS:
+        case TU_MILLISECONDS:
+        case TU_SECONDS:
+            // The enum values are equal to 1 `unit` expressed in microseconds.
+            divider = unit;
+            break;
+        default:
+            assert_fmsg(false, "Invalid unit: %d\n", unit);
+    }
+
+    uint32_t response_size;
+    return_error_if_fails(renode_execute_command(renode, GET_TIME, current_time, sizeof(*current_time), sizeof(*current_time), &response_size));
+
+    assert_msg(response_size == sizeof(*current_time), "Received unexpected number of bytes");
+
+    *current_time /= divider;
+
+    return NO_ERROR;
+}