Skip to content

Instantly share code, notes, and snippets.

@JonathonReinhart
Last active November 4, 2022 13:39
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save JonathonReinhart/b6f355f13021cd8ec5d0101e0e6675b2 to your computer and use it in GitHub Desktop.
Save JonathonReinhart/b6f355f13021cd8ec5d0101e0e6675b2 to your computer and use it in GitHub Desktop.
Using Python ctypes to manipulate binary data
#!/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()
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