I'm using ctypes to convert from a binary data buffer
log = DataFromBuffe.from_buffer(buffer)
in my class i have
class DataFromBuffe(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('id', ctypes.c_char * 1),
('name', ctypes.c_char * 30),
('value', ctypes.c_double),
('size', ctypes.c_uint16),
('date', type(datetime.datetime))
]
But I have two problems?
1 - How can I work with datetime? Fild 'date' is not working.
2 - Field 'size', for some reason is BigEndian. Is it possible change structure just for this field?
1 - How can I work with datetime? Fild 'date' is not working.
Your date
field must be a ctypes
type (or a type inheriting from a ctypes
type). This means you have to find a way to express a date as a number (int, float, double, whatever you want, but it can not be a non-ctypes python type).
In this example I used the well known Unix Epoch (which can be represented on a ctypes.c_uint32
)
class DataFromBuffer(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('id', ctypes.c_char * 1),
('name', ctypes.c_char * 30),
('value', ctypes.c_double),
('size', ctypes.c_uint16),
('date', ctypes.c_uint32), # date as a 32-bit unsigned int.
]
# snip
now_date_time = datetime.datetime.now()
now_int = int(now_date_time.timestamp()) # now as an integer (seconds from the unix epoch)
print(f"Now - datetime: {now_date_time!s}; int: {now_int}")
test_buffer = (b"A" + # id
# snip
now_int.to_bytes(4, "little") # date
)
As for the conversion to a datetime, I simply added a function member to the structure so it can convert the date (a ctypes.c_uint32
) to a datetime:
def date_to_datetime(self) -> datetime.datetime:
"""Get the date field as a python datetime.
"""
return datetime.datetime.fromtimestamp(self.date)
2 - Field 'size', for some reason is BigEndian. Is it possible change structure just for this field?
No it's not possible. A possible way is to have a function or property to access the field as you want it to be (performing some sort of conversion under the hood):
def real_size(self) -> int:
"""Get the correct value for the size field (endianness conversion).
"""
# note: there multiple way of doing this: bytearray.reverse() or struct.pack and unpack, etc.
high = self.size & 0xff
low = (self.size & 0xff00) >> 8
return high | low
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import ctypes
import math
import datetime
class DataFromBuffer(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
('id', ctypes.c_char * 1),
('name', ctypes.c_char * 30),
('value', ctypes.c_double),
('size', ctypes.c_uint16),
('date', ctypes.c_uint32),
]
def date_to_datetime(self) -> datetime.datetime:
"""Get the date field as a python datetime.
"""
return datetime.datetime.fromtimestamp(self.date)
def real_size(self) -> int:
"""Get the correct value for the size field (endianness conversion).
"""
# note: there multiple way of doing this: bytearray.reverse() or struct.pack and unpack, etc.
high = self.size & 0xff
low = (self.size & 0xff00) >> 8
return high | low
if __name__ == '__main__':
name = b"foobar"
now_date_time = datetime.datetime.now()
now_int = int(now_date_time.timestamp()) # now as an integer (seconds from the unix epoch)
print(f"Now - datetime: {now_date_time!s}; int: {now_int}")
test_buffer = (b"A" + # id
name + (30 - len(name)) * b"\x00" + # name (padded with needed \x00)
bytes(ctypes.c_double(math.pi)) + # PI as double
len(name).to_bytes(2, "big") + # size (let's pretend it's the name length)
now_int.to_bytes(4, "little") # date (unix epoch)
)
assert ctypes.sizeof(DataFromBuffer) == len(test_buffer)
data = DataFromBuffer.from_buffer(bytearray(test_buffer))
print(f"date: {data.date}; as datetime: {data.date_to_datetime()}")
print(f"size: {data.size} ({data.size:#x}); real size: {data.real_size()} ({data.real_size():#x})")
output:
Now - datetime: 2019-07-31 14:52:21.193023; int: 1564577541
date: 1564577541; as datetime: 2019-07-31 14:52:21
size: 1536 (0x600); real size: 6 (0x6)
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments