Skip to content

Latest commit

 

History

History
162 lines (127 loc) · 3.93 KB

File metadata and controls

162 lines (127 loc) · 3.93 KB

structfmt

Python lib for easy work with binary struct in Fluent API style

Features

  • Creates format string for byte structures with fluent-like code
  • Create FormattedStruct's which similar to struct.Struct's, but create namedtuples on unpack
  • Map unpacked values by user defined mappers functions

Interface

struct_format() # returns StructFormatter for build format string
struct_named_formatter() # returns StructNamedFormatter for build FormattedStruct

# Bytes ordering:
    .native_alignment_endian()
    .native_endian()
    .little_endian()
    .big_endian()
    .network_endian()

# Supported types:
    .bool()
    .byte()
    .bytes()
    .double()
    .float()
    .int8()
    .int16()
    .int32()
    .int64()
    .long() # C-long, similar to int32 !!!
    .native_pointer()
    .pascal_bytes() #array of bytes which first element defines array length
    .size_t()
    .ssize_t()
    .uint8()
    .uint16()
    .uint32()
    .uint64()
    .ulong() #C-unsigned long, similar to uint32 !!!

    .half_precision() # Python 3.6 feature

    .skip_bytes(n) - skips n bytes
    .skip_to_offset(offset) - skips all bytes to specified offset_
FormattedStruct interface

Behaviour similar to struct.Struct

   pack(self, *items):
   pack_into(self, buffer, offset, *items):
   unpack(self, buffer):
   unpack_from(self, buffer, offset=0):
   iter_unpack(self, buffer):
   size

Examples:

StructFormatter

Create format string defines bytes represents two int32 and one int64 numbers, and array of 3 bytes with little endian byte order

# [int32][int32][int64][array:[byte][byte][byte]]
import structfmt


format_string = (
    structfmt.struct_format()
    .little_endian()
    .int32(2) # 2 - count of fields of type int32
    .uint64()
    ).build_format_string()

print(format_string) #prints: "<2iQ"
StructNamedFormatter

Create formatted struct with two int32 fields and one int8 field

# [int32][int8]
import structfmt


s = (structfmt.struct_named_format("name")
     .little_endian()
     .int32("width", "height") # specify names of int32 fields
     .int8("color") # all names must be unique!
     ).build_formatted_struct()

packed = s.pack(123, 456, 2)
unpacked = s.unpack(packed)

print(unpacked.width) # prints '123'
print(unpacked.height) # prints '456'
print(unpacked.color) # prints '2'
Mapper

Create formatted struct with two int32 fields and one int8 field. Set mapper for last field

import structfmt


colors = {
    1 : "Red",
    2 : "Green",
    3 : "Blue"
}

byte_to_color = lambda x: colors[x]

s = (structfmt.struct_named_format("name")
     .little_endian()
     .int32("width", "height")
     .int8("color", mapper=byte_to_color)
     ).build_formatted_struct()

packed = s.pack(123, 456, 2)
unpacked = s.unpack(packed)

print(unpacked.width) # prints '123'
print(unpacked.height) # prints '456'
print(unpacked.color) # prints 'Green'

Ethernet frame

Parse begin of Ethernet frame, contains two Mac addresses and Frame type. Use mappers.

import structfmt


# returns string which represents MAC-address by sequence of 6 bytes
def bytes_to_mac_string(b): 
    hexcode = list(map(lambda x: format(x, '02x'), b))
    return "{}:{}:{}:{}:{}:{}".format(*hexcode)

packet_types = {
    0x0008: 'IPv4',
    0x0091: 'VlanTagged'
  }

# define FormattedStruct represents begin of Ethernet frame
ethernet_frame = (
    structfmt.struct_named_format("Ethernet")
    .little_endian()
    .bytes("MacDestination", 6, mapper=bytes_to_mac_string)
    .bytes("MacSource", 6, mapper=bytes_to_mac_string)
    .int16("PacketType", mapper=lambda x: packet_types[x])
).build_formatted_struct()

frame_in_bytes = b'\x80\x00\x20\x7a\x3f\x3e\x80\x00\x20\x20\x3a\xae\x08\x00'

decoded = ethernet_frame.unpack(frame_in_bytes)
print(decoded.MacDestination) # prints '80:a0:20:7a:3f:3e'
print(decoded.MacSource) # prints '80:6a:24:20:3a:ae'
print(decoded.PacketType) # prints 'IPv4'