Overlay a Byte Array already in memory to a Struct in Python

25 Views Asked by At

So I'm creating a tool to read variable length records into memory, and access them as a struct.

Reading this information from disk to a structure seems straight forward. I'm using a helper class from the O'Reilly Python CookBook

And this all works as expected.

Within an existing Struct

Using:

dskHead = DSKHeader(file.read(256))

Reads the data in and I can access elements in the structure by dskHead.numberOfTracks and that's all great.

Further on I read in what will be the sector information which will contain variable length records within the 256 data block.

class TrackInformationBlock(Structure):
    _fields_ = [
        ('<12s','header'),
        ('<4s','unused'),
        ('b','TrackNumber'),
        ('b','TrackSide'),
        ('h','unused2'),
        ('b','sectorSize'),
        ('b','numberOfSectors'),
        ('b','gap3'),
        ('b','filler'),
        ('<232s','sectorTable')
    ]

Again, all fine and working as expected.

the sectorTable is already in memory at this point and that can be broken down by a further struct.

class SectorInformationBlock(Structure):
    _fields_ = [
        ('b','Track'),
        ('b','Side'),
        ('b','SectorID'),
        ('b','SectorSize'),
        ('b','FDC1'),
        ('b','FDC2'),
        ('h','notused')
    ]

determined by the number of sectors.

Since the SectorTable is already in memory (ByteArray),

How can I associate the SectorInformationBlock via iteration over the 232 bytes and be able to refer to each by fieldname?

as opposed to Track.sectorTable[0:8] , Track.sectorTable[9:16] etc?

Only examples I can find is when reading data from disk, not after data is already in memory?

Hope this makes sense, though if you need full context the full project is https://github.com/muckypaws/AmstradDSKExplorer

I prefer consistency and although my solution works, it doesn't quite have that feel of clean Python code.

I am still very much learning this, as I come from an Assembler language background, where I would just simply refer to memory addresses and pointers.

Thank you

Code snippets below for your reference. There are 9 instances of the 8 Byte Sector Information Block. So I would prefer to access on key, i.e. track.sectorTable.sector["C7"]

track.sectorTable.hex() '0000c102000000020000c602000000020000c202000000020000c702000000020000c302000000020000c802000000020000c402000000020000c902000000020000c5020000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'

track.sectorTable b'\x00\x00\xc1\x02\x00\x00\x00\x02\x00\x00\xc6\x02\x00\x00\x00\x02\x00\x00\xc2\x02\x00\x00\x00\x02\x00\x00\xc7\x02\x00\x00\x00\x02\x00\x00\xc3\x02\x00\x00\x00\x02\x00\x00\xc8\x02\x00\x00\x00\x02\x00\x00\xc4\x02\x00\x00\x00\x02\x00\x00\xc9\x02\x00\x00\x00\x02\x00\x00\xc5\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

#
# Helper Class for creating easy structs.
# Nabbed from the O'Reilly Book
#       Python Cookbook 3rd Edition - Recipes for Mastering Python 3
#       ISBN 978-1-449-34037-7
#
class SizedRecord:
    def __init__(self, bytedata):
        self._buffer = memoryview(bytedata)

    @classmethod
    def from_file(cls, f, size_fmt, includes_size=True): 
        sz_nbytes = struct.calcsize(size_fmt)
        sz_bytes = f.read(sz_nbytes)
        sz, = struct.unpack(size_fmt, sz_bytes)
        buf = f.read(sz - includes_size * sz_nbytes) 
        return cls(buf)
    
    def iter_as(self, code):
        if isinstance(code, str):
            s = struct.Struct(code)
            for off in range(0, len(self._buffer), s.size):
                yield s.unpack_from(self._buffer, off) 
        elif isinstance(code, StructureMeta):
            size = code.struct_size
            for off in range(0, len(self._buffer), size):
                data = self._buffer[off:off+size] 
                yield code(data)

class StructField: 
    '''
        Descriptor representing a simple structure field 
    '''
    def __init__(self, format, offset):
        self.format = format
        self.offset = offset
    
    def __get__(self, instance, cls):
        if instance is None: 
            return self
        else:
            r = struct.unpack_from(self.format,
                                    instance._buffer, self.offset)
            return r[0] if len(r) == 1 else r
    
class NestedStruct: 
    '''
        Descriptor representing a nested structure
    '''
    def __init__(self, name, struct_type, offset):
        self.name = name 
        self.struct_type = struct_type 
        self.offset = offset

    def __get__(self, instance, cls): 
        if instance is None:
            return self 
        else:
            data = instance._buffer[self.offset: self.offset+self.struct_type.struct_size]
            result = self.struct_type(data)
            # Save resulting structure back on instance to avoid 
            # further recomputation of this step setattr(instance, self.name, result)
        return result


class StructureMeta(type): 
    '''
        Metaclass that automatically creates StructField descriptors
    '''
    def __init__(self, clsname, bases, clsdict):
        fields = getattr(self, '_fields_', []) 
        byte_order = ''
        offset = 0
        for format, fieldname in fields:
            if isinstance(format, StructureMeta):
                setattr(self,fieldname,
                        NestedStruct(fieldname, format, offset))
                offset += format.struct_size
            else:
                if format.startswith(('<','>','!','@')): 
                    byte_order = format[0]
                    format = format[1:]
                format = byte_order + format
                setattr(self, fieldname, StructField(format, offset)) 
                offset += struct.calcsize(format)
        setattr(self, 'struct_size', offset)

class Structure(metaclass=StructureMeta):
    def __init__(self, bytedata):
        self._buffer = memoryview(bytedata)        

#
# Disk Header Structure
#
class DSKHeader(Structure):
    _fields_ = [
        ('<34s','header'),      
        ('<14s','creator'),
        ('b','numberOfTracks'),
        ('b','numberOfSides'),
        ('h','oldTrackSize'),
        ('<204s','trackSizeTable')
    ]

#
# Define the Sector Information Block Structure
#
class SectorInformationBlock(Structure):
    _fields_ = [
        ('b','Track'),
        ('b','Side'),
        ('b','SectorID'),
        ('b','SectorSize'),
        ('b','FDC1'),
        ('b','FDC2'),
        ('h','notused')
    ]

#
# Define the Track Information Block Structure
#
class TrackInformationBlock(Structure):
    _fields_ = [
        ('<12s','header'),
        ('<4s','unused'),
        ('b','TrackNumber'),
        ('b','TrackSide'),
        ('h','unused2'),
        ('b','sectorSize'),
        ('b','numberOfSectors'),
        ('b','gap3'),
        ('b','filler'),
        #(SectorInformationBlock,'sectorTable[29]')
        ('<232s','sectorTable')
    ]

Dr google, my Python books and ... I'm just not experienced a Python person to solve this...

0

There are 0 best solutions below