Repository: secdev/scapy

Issue:
UTCTimeField and negative value on Windows
### Brief description

On a Windows, `UTCTimeField.i2repr(self, pkt, x`) will trigger an error if x is a negative value.

### Scapy version

2.5.0

### Python version

3.11 (tester on 3.10 also)

### Operating system

Windows 10

### Additional environment information

This comes from  `datetime.fromtimestamp(t)` which will throw an errror on Windows if `t` is negative :

```
In [26]: datetime.fromtimestamp(-1)
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
Cell In[26], line 1
----> 1 datetime.fromtimestamp(-1)

OSError: [Errno 22] Invalid argument
```

I agree this is not really a Scapy issue. But I think it could be useful to catch errors when creating a `datetime` from a timestamp.
For example, on every OS, calling `datetime.frometimestamp(2**43)`will trigger an error. So if a layer contains a `UTCTimeField` attribute based on a 64 bits value (I don't know if it could happen), this could lead to an error on every OS.


### How to reproduce

The following packet will trigger an error when displayed in a Windows environment :

```
>>> p = Ether(binascii.unhexlify('00155d56b60100155d56b6030800450201246bfe4000800677b90a02010d0a02010a01bdc13df3830c636c7a265c50182003bece0000000000f8fe534d4240000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041000100ff020000071c1e4cf44fa749a15341d7b30ed5cb07000000000080000000800000008000845dc27b5d6bda0100000000000000008000780000000000607606062b0601050502a06c306aa03c303a060a2b06010401823702021e06092a864882f71201020206092a864886f712010202060a2a864886f71201020203060a2b06010401823702020aa32a3028a0261b246e6f745f646566696e65645f696e5f5246433431373840706c656173655f69676e6f7265'))
```
In this packet, the error comes from the ServerStartTime attribute from the SMB2 Negotiate response which is null.

```
>>> p[SMB2_Negotiate_Protocol_Response].ServerStartTime
0
```


### Actual result

```
>>> p.show()
File D:\projets\scapy\scapy\fields.py:3523, in UTCTimeField.i2repr(self, pkt, x)
   3521     x = x / self.custom_scaling
   3522 x += self.delta
-> 3523 t = datetime.fromtimestamp(x).strftime(self.strf)
   3524 return "%s (%d)" % (t, int(x))

OSError: [Errno 22] Invalid argument
```


### Expected result

On Linux, you can see how it is rendered :

```
>>> p[SMB2_Negotiate_Protocol_Response]
<SMB2_Negotiate_Protocol_Response  ... 
   ServerTime=Thu, 29 Feb 2024 22:20:21 +0000 (1709245221) 
   ServerStartTime=Mon, 01 Jan 1601 00:00:00 +0000 (-11644473600) ..|>
```
we can see the ServerStartTime attribute being equal to `-11644473600`, which explains the error on Windows ...

### Related resources

_No response_


Discussion:

Resolving patch:
diff --git a/scapy/fields.py b/scapy/fields.py
--- a/scapy/fields.py
+++ b/scapy/fields.py
@@ -12,6 +12,7 @@
 import calendar
 import collections
 import copy
+import datetime
 import inspect
 import math
 import socket
@@ -19,7 +20,6 @@
 import time
 import warnings
 
-from datetime import datetime
 from types import MethodType
 from uuid import UUID
 from enum import Enum
@@ -3520,7 +3520,12 @@ def i2repr(self, pkt, x):
         elif self.custom_scaling:
             x = x / self.custom_scaling
         x += self.delta
-        t = datetime.fromtimestamp(x).strftime(self.strf)
+        # To make negative timestamps work on all plateforms (e.g. Windows),
+        # we need a trick.
+        t = (
+            datetime.datetime(1970, 1, 1) +
+            datetime.timedelta(seconds=x)
+        ).strftime(self.strf)
         return "%s (%d)" % (t, int(x))
 
     def i2m(self, pkt, x):


Test patch:
diff --git a/test/fields.uts b/test/fields.uts
--- a/test/fields.uts
+++ b/test/fields.uts
@@ -365,6 +365,28 @@ p = TestBitLenField(b'\xc0\x01\xf4')
 assert p.mode == 3
 assert p.value == 500
 
+= Test UTCTimeField
+~ field
+
+class TestUTCTimeField(Packet):
+    fields_desc = [
+        # A Windows time field. See GH#4308
+        UTCTimeField(
+            "Time",
+            None,
+            fmt="<Q",
+            epoch=[1601, 1, 1, 0, 0, 0],
+            custom_scaling=1e7,
+        )
+    ]
+
+
+p = TestUTCTimeField(Time=0)
+assert p.sprintf("%Time%") == 'Mon, 01 Jan 1601 00:00:00  (-11644473600)'
+
+p = TestUTCTimeField(Time=133587912345678900)
+assert p.sprintf("%Time%") == 'Sun, 28 Apr 2024 15:20:34  (1714317634)'
+
 ############
 ############
 + Tests on FieldListField