/// <summary>
        /// Writes the JSON representation of the object.
        /// </summary>
        /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
        /// <param name="value">The value.</param>
        /// <param name="serializer">The calling serializer.</param>
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value == null)
            {
                writer.WriteNull();
                return;
            }

            switch (value)
            {
            case DateTime dt:
                writer.WriteValue(UnixTimeUtils.ToInt64(dt, Format));
                break;

            case DateTimeOffset dto:
                writer.WriteValue(UnixTimeUtils.ToInt64(dto, Format));
                break;

            case EssentialsDateTime edt:
                writer.WriteValue(UnixTimeUtils.ToInt64(edt.DateTime, Format));
                break;

            case EssentialsTime et:
                writer.WriteValue(UnixTimeUtils.ToInt64(et, Format));
                break;

            default:
                throw new ArgumentException($"Unknown type {value.GetType()}", nameof(value));
            }
        }
        private ulong GetCurrentUnixTimestampMilliseconds()
        {
            var time    = UnixTimeUtils.GetCurrentUnixTimestampMilliseconds();
            var newTime = (ulong)((long)time + TimeDifference);

            return(newTime);
        }
        public void FromMillisecondsString_NormalTime()
        {
            TimeZoneInfo romance = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");

            DateTimeOffset resultUtc     = UnixTimeUtils.FromMilliseconds("1643886000123");
            DateTimeOffset resultDenmark = TimeZoneInfo.ConvertTime(resultUtc, romance);

            Assert.AreEqual("2022-02-03T11:00:00.123+00:00", resultUtc.ToString("yyyy-MM-ddTHH:mm:ss.fffK"));
            Assert.AreEqual("2022-02-03T12:00:00.123+01:00", resultDenmark.ToString("yyyy-MM-ddTHH:mm:ss.fffK"));
        }
        public void FromSecondsDouble_NormalTime()
        {
            TimeZoneInfo romance = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");

            DateTimeOffset resultUtc     = UnixTimeUtils.FromSeconds(1643886000d);
            DateTimeOffset resultDenmark = TimeZoneInfo.ConvertTime(resultUtc, romance);

            Assert.AreEqual("2022-02-03T11:00:00+00:00", resultUtc.ToString(Iso8601Constants.DateTimeSeconds));
            Assert.AreEqual("2022-02-03T12:00:00+01:00", resultDenmark.ToString(Iso8601Constants.DateTimeSeconds));
        }
        public void FromSecondsInt64_SummerTime()
        {
            TimeZoneInfo romance = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");

            DateTimeOffset resultUtc     = UnixTimeUtils.FromSeconds(1629182100L);
            DateTimeOffset resultDenmark = TimeZoneInfo.ConvertTime(resultUtc, romance);

            Assert.AreEqual("2021-08-17T06:35:00+00:00", resultUtc.ToString(Iso8601Constants.DateTimeSeconds));
            Assert.AreEqual("2021-08-17T08:35:00+02:00", resultDenmark.ToString(Iso8601Constants.DateTimeSeconds));
        }
        public void FromMillisecondsString_SummerTime()
        {
            TimeZoneInfo romance = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");

            DateTimeOffset resultUtc     = UnixTimeUtils.FromMilliseconds("1629182100123");
            DateTimeOffset resultDenmark = TimeZoneInfo.ConvertTime(resultUtc, romance);

            Assert.AreEqual("2021-08-17T06:35:00.123+00:00", resultUtc.ToString("yyyy-MM-ddTHH:mm:ss.fffK"));
            Assert.AreEqual("2021-08-17T08:35:00.123+02:00", resultDenmark.ToString("yyyy-MM-ddTHH:mm:ss.fffK"));
        }
        public void ToMillisecondsDateTime()
        {
            // Must be UTC as we really can't rely on anything else when unit testing DateTime
            DateTime dt = new DateTime(2022, 2, 3, 11, 0, 0, 123, DateTimeKind.Utc);

            // Cast to long as we don't really care about any decimals
            long result = (long)UnixTimeUtils.ToMilliseconds(dt);

            // Do we have a match?
            Assert.AreEqual(1643886000123, result);
        }
        public void ToSecondsDateTime()
        {
            // Must be UTC as we really can't rely on anything else when unit testing DateTime
            DateTime dt = new DateTime(2022, 2, 3, 11, 0, 0, DateTimeKind.Utc);

            // Cast to int as we don't really care about the milliseconds here
            int result = (int)UnixTimeUtils.ToSeconds(dt);

            // Do we have a match?
            Assert.AreEqual(1643886000, result);
        }
        public void CurrentMilliseconds()
        {
            double actual = UnixTimeUtils.CurrentMilliseconds;

            // Since Unix time is calculated back to UTC, using "DateTime.Now" and "DateTime.UtcNow" should give the
            // same result
            double expected1 = UnixTimeUtils.ToMilliseconds(DateTime.Now);
            double expected2 = UnixTimeUtils.ToMilliseconds(DateTime.UtcNow);

            Assert.AreEqual(expected2, actual);
            Assert.AreEqual(expected2, expected1);
        }
Esempio n. 10
0
        public void ToMillisecondsDateTimeOffset()
        {
            // Initialize to different DateTimeOffset representing the same point in time, but with different offsets
            DateTimeOffset dto1 = new DateTimeOffset(2022, 2, 3, 11, 0, 0, 123, TimeSpan.Zero);
            DateTimeOffset dto2 = new DateTimeOffset(2022, 2, 3, 12, 0, 0, 123, TimeSpan.FromHours(1));

            // Cast to long as we don't really care about any decimals
            long result1 = (long)UnixTimeUtils.ToMilliseconds(dto1);
            long result2 = (long)UnixTimeUtils.ToMilliseconds(dto2);

            // Do we have a match?
            Assert.AreEqual(1643886000123, result1);
            Assert.AreEqual(1643886000123, result2);
        }
Esempio n. 11
0
        public ulong GetNextMessageId()
        {
            // Documentation says that message id should be unixtime * 2^32.
            // But the real world calculations in other client software looking very weird.
            // Have no idea how it is actually calculated.
            ulong messageId = UnixTimeUtils.GetCurrentUnixTimestampMilliseconds();

            messageId = (messageId * 4294967 + (messageId * 296 / 1000)) & X4Mask;
            if (messageId <= _lastMessageId)
            {
                messageId = _lastMessageId + 4;
            }
            _lastMessageId = messageId;
            return(messageId);
        }
Esempio n. 12
0
        public void GetDateTimeOffsetFromUnixTime_Int64()
        {
            var samples = new[] {
                new { Timestamp = 1483384930L, Date = new DateTimeOffset(2017, 1, 2, 19, 22, 10, TimeSpan.Zero) },
                new { Timestamp = 1483384930L, Date = new DateTimeOffset(2017, 1, 2, 18, 22, 10, TimeSpan.FromHours(-1)) },
                new { Timestamp = 1483384930L, Date = new DateTimeOffset(2017, 1, 2, 20, 22, 10, TimeSpan.FromHours(+1)) }
            };

            int n = 1;

            foreach (var sample in samples)
            {
                Assert.AreEqual(sample.Date, TimeHelper.GetDateTimeOffsetFromUnixTime(sample.Timestamp), $"\n\n#{n}. {sample.Timestamp} (TimeHelper)");
                Assert.AreEqual(sample.Date, TimeUtils.GetDateTimeOffsetFromUnixTime(sample.Timestamp), $"\n\n#{n}. {sample.Timestamp} (TimeUtils)");
                Assert.AreEqual(sample.Date, UnixTimeUtils.FromSeconds(sample.Timestamp), $"\n\n#{n}. {sample.Timestamp} (UnixTimeUtils)");
                n++;
            }
        }
        internal static object ToFormat(DateTimeOffset value, TimeFormat format)
        {
            switch (format)
            {
            case TimeFormat.Iso8601:
                return(Iso8601Utils.ToString(value));

            case TimeFormat.Rfc822:
                return(Rfc822Utils.ToString(value));

            case TimeFormat.Rfc2822:
                return(Rfc2822Utils.ToString(value));

            case TimeFormat.UnixTime:
                return((long)UnixTimeUtils.ToSeconds(value));

            default:
                throw new ArgumentException("Unsupported format " + format, nameof(format));
            }
        }
        /// <summary>
        /// Reads the JSON representation of the object.
        /// </summary>
        /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
        /// <param name="objectType">Type of the object.</param>
        /// <param name="existingValue">The existing value of object being read.</param>
        /// <param name="serializer">The calling serializer.</param>
        /// <returns>The object value.</returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            string type;

            if (objectType.Name == "Nullable`1")
            {
                // Just return NULL
                if (reader.TokenType == JsonToken.Null)
                {
                    return(null);
                }

                // Get the name of the generic type
                type = objectType.GenericTypeArguments[0].FullName;
            }
            else
            {
                // Get the name of the type
                type = objectType.FullName;
            }

            // Read/parse the timestamp from the reader
            double timestamp = reader.TokenType switch {
                JsonToken.Integer => (long)reader.Value,
                JsonToken.Float => (double)reader.Value,
                JsonToken.String => double.Parse((string)reader.Value, CultureInfo.InvariantCulture),
                _ => throw new JsonSerializationException($"Unsupported value {reader.Value}"),
            };

            // Convert to the correct type based on the value type of the property
            return(type switch {
                "System.DateTime" => UnixTimeUtils.ToDateTime(timestamp, Format),
                "System.DateTimeOffset" => UnixTimeUtils.ToDateTimeOffset(timestamp, Format),
                "Skybrud.Essentials.Time.EssentialsDateTime" => new EssentialsDateTime(UnixTimeUtils.ToDateTime(timestamp, Format)),
                "Skybrud.Essentials.Time.EssentialsTime" => new EssentialsTime(UnixTimeUtils.ToDateTimeOffset(timestamp, Format)),
                "Skybrud.Essentials.Time.EssentialsDate" => new EssentialsDate(UnixTimeUtils.ToDateTimeOffset(timestamp, Format)),
                _ => throw new JsonSerializationException($"Unsupported type {objectType.FullName}")
            });
Esempio n. 15
0
        public void CurrentSeconds()
        {
            double actual = UnixTimeUtils.CurrentSeconds;

            // Since Unix time is calculated back to UTC, using "DateTime.Now" and "DateTime.UtcNow" should give the
            // same result
            double expected1 = UnixTimeUtils.ToSeconds(DateTime.Now);
            double expected2 = UnixTimeUtils.ToSeconds(DateTime.UtcNow);

            Assert.AreEqual(expected2, expected1);

            // Comparing against current time is always tricky. If this is the first test to run, there may be a ~100 ms
            // difference between the actual and expected timestamps. If other tests have run first, the two timestamps
            // so far seem to be the same ¯\_(ツ)_/¯

            // Since the important part we want to test here is that the two timestamps are based on the same timezone,
            // we can confirm this by checking that "delta" is below 1 second.

            double delta1 = Math.Abs(actual - expected1);
            double delta2 = Math.Abs(actual - expected2);

            Assert.IsTrue(delta1 < 1, "#1");
            Assert.IsTrue(delta2 < 1, "#2");
        }
        protected override async Task HandleInternalAsync(IMessage responseMessage)
        {
            #region Notice of Ignored Error Message

            /* In certain cases, a server may notify a client that its incoming message was ignored for whatever reason.
             * Note that such a notification cannot be generated unless a message is correctly decoded by the server.
             *
             * bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification;
             * bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification;
             *
             * Here, error_code can also take on the following values:
             *
             * 16: msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications
             *     and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original
             *     message had waited too long on the client to be transmitted)
             * 17: msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)
             * 18: incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)
             * 19: container msg_id is the same as msg_id of a previously received message (this must never happen)
             * 20: message too old, and it cannot be verified whether the server has received a message with this msg_id or not
             * 32: msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)
             * 33: msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)
             * 34: an even msg_seqno expected (irrelevant message), but odd received
             * 35: odd msg_seqno expected (relevant message), but even received
             * 48: incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)
             * 64: invalid container.
             * The intention is that error_code values are grouped (error_code >> 4): for example, the codes 0x40 - 0x4f correspond to errors in container decomposition.
             *
             * Notifications of an ignored message do not require acknowledgment (i.e., are irrelevant).
             *
             * Important: if server_salt has changed on the server or if client time is incorrect, any query will result in a notification in the above format.
             * The client must check that it has, in fact, recently sent a message with the specified msg_id, and if that is the case,
             * update its time correction value (the difference between the client’s and the server’s clocks) and the server salt based on msg_id
             * and the server_salt notification, so as to use these to (re)send future messages.
             * In the meantime, the original message (the one that caused the error message to be returned) must also be re-sent with a better msg_id and/or server_salt.
             *
             * In addition, the client can update the server_salt value used to send messages to the server,
             * based on the values of RPC responses or containers carrying an RPC response,
             * provided that this RPC response is actually a match for the query sent recently.
             * (If there is doubt, it is best not to update since there is risk of a replay attack).
             *
             * https://core.telegram.org/mtproto/service_messages_about_messages#notice-of-ignored-error-message
             */
            #endregion

            var response = (IBadMsgNotification)responseMessage.Body;

            var errorCode = (ErrorCode)response.ErrorCode;
            Console.WriteLine(string.Format("Bad message notification received with error code: {0} ({1}).", response.ErrorCode, errorCode));

            Console.WriteLine("Searching for bad message in the request manager...");

            IRequest request = _requestsManager.Get(response.BadMsgId);
            if (request == null)
            {
                Console.WriteLine(string.Format("Bad message (MsgId: 0x{0:X}) was NOT found. Ignored.", response.BadMsgId));
                return;
            }
            if (request.Message.Seqno != response.BadMsgSeqno)
            {
                Console.WriteLine(
                    string.Format(
                        "Bad message (MsgId: 0x{0:X}) was found, but message sequence number is not the same ({1}) as server expected ({2}). Ignored.",
                        response.BadMsgId,
                        request.Message.Seqno,
                        response.BadMsgSeqno));
                return;
            }

            var badServerSalt = response as BadServerSalt;
            if (badServerSalt != null)
            {
                if (errorCode != ErrorCode.IncorrectServerSalt)
                {
                    Console.WriteLine(string.Format("Error code must be '{0}' for a BadServerSalt notification, but found '{1}'.", ErrorCode.IncorrectServerSalt, errorCode));
                }

                Console.WriteLine(
                    string.Format(
                        "Bad server salt was in outgoing message (MsgId = 0x{0:X}, Seqno = {1}). Error code = {2}.",
                        badServerSalt.BadMsgId,
                        badServerSalt.BadMsgSeqno,
                        errorCode));

                Console.WriteLine("Setting new salt.");

                _connection.UpdateSalt(badServerSalt.NewServerSalt);

                Console.WriteLine("Resending bad message with the new salt.");

                await request.SendAsync();

                return;
            }

            var badMsgNotification = response as BadMsgNotification;
            if (badMsgNotification != null)
            {
                // TODO: implement.
                switch (errorCode)
                {
                case ErrorCode.MsgIdIsTooSmall:
                case ErrorCode.MsgIdIsTooBig:
                case ErrorCode.MsgIdDuplicate:
                case ErrorCode.MsgTooOld:
                case ErrorCode.MsgIdBadTwoLowBytes:
                    ulong time        = (ulong)(responseMessage.MsgId / 4294967296.0 * 1000);
                    ulong currentTime = UnixTimeUtils.GetCurrentUnixTimestampMilliseconds();
                    var   timeDelta   = ((long)time - (long)currentTime);
                    _connection.MessageIdGenerator.TimeDifference = timeDelta;
                    Disa.Framework.Utils.DebugPrint("Time drift set to: " + _connection.MessageIdGenerator.TimeDifference);
                    var newMessageId = _connection.MessageIdGenerator.GetNextMessageId();
                    var oldMessageId = request.Message.MsgId;
                    var message      = new Message(newMessageId, request.Message.Seqno, request.Message.Body);
                    request.UpdateMessage(message);
                    _requestsManager.Change(newMessageId, oldMessageId);
                    await request.SendAsync();

                    break;

                case ErrorCode.MsgSeqnoIsTooLow:
                case ErrorCode.MsgSeqnoIsTooBig:
                case ErrorCode.MsgSeqnoNotEven:
                case ErrorCode.MsgSeqnoNotOdd:
                case ErrorCode.IncorrectServerSalt:
                case ErrorCode.InvalidContainer:
                    throw new NotImplementedException();

                default:
                    throw new ArgumentOutOfRangeException();
                }
            }
        }
Esempio n. 17
0
 public static int GetUnixTimeFromDateTimeOffset(DateTimeOffset date)
 {
     return((int)UnixTimeUtils.ToSeconds(date));
 }
Esempio n. 18
0
 public static double GetUnixTimeFromDateTimeOffsetAsDouble(DateTimeOffset date)
 {
     return(UnixTimeUtils.ToSeconds(date));
 }
Esempio n. 19
0
 public static DateTimeOffset GetDateTimeOffsetFromUnixTime(int timestamp)
 {
     return(UnixTimeUtils.FromSeconds(timestamp));
 }
Esempio n. 20
0
 public static DateTime GetDateTimeFromUnixTime(string timestamp)
 {
     return(UnixTimeUtils.FromSeconds(timestamp).UtcDateTime.ToUniversalTime());
 }