From 7fe5989f5dad47876ee6b048a1aee9562fce956d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Domeradzki?= Date: Wed, 1 Jun 2022 21:13:50 +0200 Subject: [PATCH] Rewrite Steam time to ulongs (#2594) My latest "research" resulted in learning that under the hood, Steam unix time seconds is bullet-proof not only for year 2038 but for uint range as well. While I do not expect to be alive by 2106, let alone ASF still being operative, it makes sense to base our time on the correct backend implementation regardless. Small breaking change for people using `GetUnixTime()`. --- ArchiSteamFarm/Core/Utilities.cs | 12 +++++++- .../Steam/Integration/ArchiWebHandler.cs | 10 +++---- .../Steam/Security/MobileAuthenticator.cs | 28 ++++++++++--------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/ArchiSteamFarm/Core/Utilities.cs b/ArchiSteamFarm/Core/Utilities.cs index 7c091949a..faabd5c9c 100644 --- a/ArchiSteamFarm/Core/Utilities.cs +++ b/ArchiSteamFarm/Core/Utilities.cs @@ -94,7 +94,7 @@ public static class Utilities { } [PublicAPI] - public static uint GetUnixTime() => (uint) DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + public static ulong GetUnixTime() => (ulong) DateTimeOffset.UtcNow.ToUnixTimeSeconds(); [PublicAPI] public static async void InBackground(Action action, bool longRunning = false) { @@ -253,6 +253,16 @@ public static class Utilities { } } + internal static ulong MathAdd(ulong first, int second) { + if (second >= 0) { + first += (uint) second; + } else { + first -= (uint) -second; + } + + return first; + } + internal static bool RelativeDirectoryStartsWith(string directory, params string[] prefixes) { if (string.IsNullOrEmpty(directory)) { throw new ArgumentNullException(nameof(directory)); diff --git a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs index 616350e17..cc95b6f3b 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs @@ -1678,7 +1678,7 @@ public sealed class ArchiWebHandler : IDisposable { return result; } - internal async Task GetConfirmationsPage(string deviceID, string confirmationHash, uint time) { + internal async Task GetConfirmationsPage(string deviceID, string confirmationHash, ulong time) { if (string.IsNullOrEmpty(deviceID)) { throw new ArgumentNullException(nameof(deviceID)); } @@ -1803,7 +1803,7 @@ public sealed class ArchiWebHandler : IDisposable { return response?.Content; } - internal async Task GetServerTime() { + internal async Task GetServerTime() { KeyValue? response = null; for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) { @@ -1835,7 +1835,7 @@ public sealed class ArchiWebHandler : IDisposable { return 0; } - uint result = response["server_time"].AsUnsignedInteger(); + ulong result = response["server_time"].AsUnsignedLong(); if (result == 0) { Bot.ArchiLogger.LogNullError(result); @@ -1966,7 +1966,7 @@ public sealed class ArchiWebHandler : IDisposable { return resultInSeconds == 0 ? (byte) 0 : (byte) (resultInSeconds / 86400); } - internal async Task HandleConfirmation(string deviceID, string confirmationHash, uint time, ulong confirmationID, ulong confirmationKey, bool accept) { + internal async Task HandleConfirmation(string deviceID, string confirmationHash, ulong time, ulong confirmationID, ulong confirmationKey, bool accept) { if (string.IsNullOrEmpty(deviceID)) { throw new ArgumentNullException(nameof(deviceID)); } @@ -2008,7 +2008,7 @@ public sealed class ArchiWebHandler : IDisposable { return response?.Content?.Success; } - internal async Task HandleConfirmations(string deviceID, string confirmationHash, uint time, IReadOnlyCollection confirmations, bool accept) { + internal async Task HandleConfirmations(string deviceID, string confirmationHash, ulong time, IReadOnlyCollection confirmations, bool accept) { if (string.IsNullOrEmpty(deviceID)) { throw new ArgumentNullException(nameof(deviceID)); } diff --git a/ArchiSteamFarm/Steam/Security/MobileAuthenticator.cs b/ArchiSteamFarm/Steam/Security/MobileAuthenticator.cs index 0dd0f1cb0..d5df2069f 100644 --- a/ArchiSteamFarm/Steam/Security/MobileAuthenticator.cs +++ b/ArchiSteamFarm/Steam/Security/MobileAuthenticator.cs @@ -74,7 +74,7 @@ public sealed class MobileAuthenticator : IDisposable { throw new InvalidOperationException(nameof(Bot)); } - uint time = await GetSteamTime().ConfigureAwait(false); + ulong time = await GetSteamTime().ConfigureAwait(false); if (time == 0) { throw new InvalidOperationException(nameof(time)); @@ -96,7 +96,7 @@ public sealed class MobileAuthenticator : IDisposable { return null; } - uint time = await GetSteamTime().ConfigureAwait(false); + ulong time = await GetSteamTime().ConfigureAwait(false); if (time == 0) { throw new InvalidOperationException(nameof(time)); @@ -211,7 +211,7 @@ public sealed class MobileAuthenticator : IDisposable { return false; } - uint time = await GetSteamTime().ConfigureAwait(false); + ulong time = await GetSteamTime().ConfigureAwait(false); if (time == 0) { throw new InvalidOperationException(nameof(time)); @@ -281,7 +281,7 @@ public sealed class MobileAuthenticator : IDisposable { } } - private string? GenerateConfirmationHash(uint time, string? tag = null) { + private string? GenerateConfirmationHash(ulong time, string? tag = null) { if (time == 0) { throw new ArgumentOutOfRangeException(nameof(time)); } @@ -312,7 +312,7 @@ public sealed class MobileAuthenticator : IDisposable { bufferSize += (byte) Math.Min(32, tag!.Length); } - byte[] timeArray = BitConverter.GetBytes((ulong) time); + byte[] timeArray = BitConverter.GetBytes(time); if (BitConverter.IsLittleEndian) { Array.Reverse(timeArray); @@ -334,7 +334,7 @@ public sealed class MobileAuthenticator : IDisposable { return Convert.ToBase64String(hash); } - private string? GenerateTokenForTime(uint time) { + private string? GenerateTokenForTime(ulong time) { if (time == 0) { throw new ArgumentOutOfRangeException(nameof(time)); } @@ -358,7 +358,7 @@ public sealed class MobileAuthenticator : IDisposable { return null; } - byte[] timeArray = BitConverter.GetBytes((ulong) (time / CodeInterval)); + byte[] timeArray = BitConverter.GetBytes(time / CodeInterval); if (BitConverter.IsLittleEndian) { Array.Reverse(timeArray); @@ -394,7 +394,7 @@ public sealed class MobileAuthenticator : IDisposable { ); } - private async Task GetSteamTime() { + private async Task GetSteamTime() { if (Bot == null) { throw new InvalidOperationException(nameof(Bot)); } @@ -402,7 +402,7 @@ public sealed class MobileAuthenticator : IDisposable { int? steamTimeDifference = SteamTimeDifference; if (steamTimeDifference.HasValue && (DateTime.UtcNow.Subtract(LastSteamTimeCheck).TotalHours < SteamTimeTTL)) { - return (uint) (Utilities.GetUnixTime() + steamTimeDifference.Value); + return Utilities.MathAdd(Utilities.GetUnixTime(), steamTimeDifference.Value); } await TimeSemaphore.WaitAsync().ConfigureAwait(false); @@ -411,19 +411,21 @@ public sealed class MobileAuthenticator : IDisposable { steamTimeDifference = SteamTimeDifference; if (steamTimeDifference.HasValue && (DateTime.UtcNow.Subtract(LastSteamTimeCheck).TotalHours < SteamTimeTTL)) { - return (uint) (Utilities.GetUnixTime() + steamTimeDifference.Value); + return Utilities.MathAdd(Utilities.GetUnixTime(), steamTimeDifference.Value); } - uint serverTime = await Bot.ArchiWebHandler.GetServerTime().ConfigureAwait(false); + ulong serverTime = await Bot.ArchiWebHandler.GetServerTime().ConfigureAwait(false); if (serverTime == 0) { return Utilities.GetUnixTime(); } - SteamTimeDifference = (int) (serverTime - Utilities.GetUnixTime()); + // We assume that the difference between times will be within int range, therefore we accept underflow here (for subtraction), and since we cast that result to int afterwards, we also accept overflow for the cast itself + SteamTimeDifference = unchecked((int) (serverTime - Utilities.GetUnixTime())); + LastSteamTimeCheck = DateTime.UtcNow; - return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.Value); + return Utilities.MathAdd(Utilities.GetUnixTime(), SteamTimeDifference.Value); } finally { TimeSemaphore.Release(); }