0

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 Answer 1

2

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)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.