Last active
November 4, 2022 13:39
-
-
Save JonathonReinhart/b6f355f13021cd8ec5d0101e0e6675b2 to your computer and use it in GitHub Desktop.
Using Python ctypes to manipulate binary data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from __future__ import print_function | |
from tempfile import TemporaryFile | |
from binascii import hexlify | |
from ctypes import * | |
class StructHelper(object): | |
def __get_value_str(self, name, fmt='{}'): | |
val = getattr(self, name) | |
if isinstance(val, Array): | |
val = list(val) | |
return fmt.format(val) | |
def __str__(self): | |
result = '{}:\n'.format(self.__class__.__name__) | |
maxname = max(len(name) for name, type_ in self._fields_) | |
for name, type_ in self._fields_: | |
value = getattr(self, name) | |
result += ' {name:<{width}}: {value}\n'.format( | |
name = name, | |
width = maxname, | |
value = self.__get_value_str(name), | |
) | |
return result | |
def __repr__(self): | |
return '{name}({fields})'.format( | |
name = self.__class__.__name__, | |
fields = ', '.join( | |
'{}={}'.format(name, self.__get_value_str(name, '{!r}')) for name, _ in self._fields_) | |
) | |
@classmethod | |
def _typeof(cls, field): | |
"""Get the type of a field | |
Example: A._typeof(A.fld) | |
Inspired by stackoverflow.com/a/6061483 | |
""" | |
for name, type_ in cls._fields_: | |
if getattr(cls, name) is field: | |
return type_ | |
raise KeyError | |
@classmethod | |
def read_from(cls, f): | |
result = cls() | |
if f.readinto(result) != sizeof(cls): | |
raise EOFError | |
return result | |
def get_bytes(self): | |
"""Get raw byte string of this structure | |
ctypes.Structure implements the buffer interface, so it can be used | |
directly anywhere the buffer interface is implemented. | |
https://stackoverflow.com/q/1825715 | |
""" | |
# Works for either Python2 or Python3 | |
return bytearray(self) | |
# Python 3 only! Don't try this in Python2, where bytes() == str() | |
#return bytes(self) | |
################################################################################ | |
class Vehicle(LittleEndianStructure, StructHelper): | |
""" | |
Define a little-endian structure, and add our StructHelper mixin. | |
C structure definition: | |
__attribute__((packed)) | |
struct Vehicle | |
{ | |
uint16_t doors; | |
uint32_t price; | |
uint32_t miles; | |
uint16_t air_pressure[4]; | |
char name[16]; | |
} | |
""" | |
# Tell ctypes that this structure is "packed", | |
# i.e. no padding is inserted between fields for alignment | |
_pack_ = 1 | |
# Lay out the fields, in order | |
_fields_ = [ | |
('doors', c_uint16), | |
('price', c_uint32), | |
('miles', c_uint32), | |
('air_pressure', c_uint16 * 4), | |
('name', c_char * 16), | |
] | |
def main(): | |
# First, let's create an object | |
car = Vehicle( | |
doors = 2, | |
price = 15000, | |
miles = 33700, | |
air_pressure = Vehicle._typeof(Vehicle.air_pressure)(31, 30, 31, 29), | |
name = b'Brad', | |
) | |
# Print the representation of the object | |
print('repr(car):', repr(car)) | |
# Print the object as a nicely-formatted string | |
print('car:', car) | |
with TemporaryFile() as f: | |
# Write the object to a file: | |
f.write(car) | |
print("Wrote car to file ({} bytes)".format(f.tell())) | |
# Read the file back into a new object | |
f.seek(0) | |
car2 = Vehicle.read_from(f) | |
print('car2:', car2) | |
# Get the raw bytes of the object | |
buf = car.get_bytes() | |
print('Buffer ({}) (hex): {}'.format(type(buf), hexlify(buf))) | |
# Create an object from some bytes | |
car3 = Vehicle.from_buffer_copy(buf) | |
print('car3:', car3) | |
if __name__ == '__main__': | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
repr(car): Vehicle(doors=2, price=15000L, miles=33700L, air_pressure=[31, 30, 31, 29], name='Brad') | |
car: Vehicle: | |
doors : 2 | |
price : 15000 | |
miles : 33700 | |
air_pressure: [31, 30, 31, 29] | |
name : Brad | |
Wrote car to file (34 bytes) | |
car2: Vehicle: | |
doors : 2 | |
price : 15000 | |
miles : 33700 | |
air_pressure: [31, 30, 31, 29] | |
name : Brad | |
Buffer (<type 'bytearray'>) (hex): 0200983a0000a48300001f001e001f001d0042726164000000000000000000000000 | |
car3: Vehicle: | |
doors : 2 | |
price : 15000 | |
miles : 33700 | |
air_pressure: [31, 30, 31, 29] | |
name : Brad |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment