Skip to content
Gulliby-developmentPublic

About

Show the byte layout of binary structs with padding, suggest a leaner field order, and fail CI on wasted space

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

structpad

structpad is an offline command-line tool for people who design binary records, network packets, and FFI structures. It parses a small struct description language, reports field offsets and padding for the selected ABI, suggests a lower-padding field order, and can fail a CI job when padding exceeds a limit. It reads input without changing it and uses only the Python standard library at runtime.

CI License: MIT

Install

Install the package with Python 3.11 or newer:

pip install .

You can run the command as structpad or use the module form:

python -m structpad --help

Usage

Save this example as net.sp:

// wire structs
struct Packet {
    u8 tag;
    u32 length;
    u16 flags;
    char name[10];
    ptr next;
}

packed struct Wire {
    u8 kind;
    u32 value;
}

struct Frame {
    u8 version;
    struct Packet head;
    struct Wire tail[2];
    bool ok;
}

Show each struct's field offsets, sizes, alignment, and padding:

structpad layout net.sp

For the example above, the layout is:

struct Packet: size 32, align 8, padding 7
offset   size  field
     0      1  tag: u8
     1      3  (padding)
     4      4  length: u32
     8      2  flags: u16
    10     10  name: char[10]
    20      4  (padding)
    24      8  next: ptr

struct Wire: size 5, align 1, padding 0, packed
offset   size  field
     0      1  kind: u8
     1      4  value: u32

struct Frame: size 56, align 8, padding 12
offset   size  field
     0      1  version: u8
     1      7  (padding)
     8     32  head: struct Packet
    40     10  tail: struct Wire[2]
    50      1  ok: bool
    51      5  (padding)

Suggest an order with less padding:

structpad suggest net.sp
struct Packet: size 32, already compact

struct Wire: packed, size 5, nothing to reorder

struct Frame: size 56 -> 48 (saves 8)
  struct Packet head;
  u8 version;
  struct Wire tail[2];
  bool ok;

Check structs against a padding limit; the default limit is zero bytes:

structpad check --max-padding 8 net.sp
Frame: 12 bytes of padding (limit 8)
1 of 3 structs over the padding limit

The check exits with status 1 when any struct exceeds the limit.

--ignore NAME is available only with check and may be repeated; --ignore=NAME is also supported. Ignored structs are omitted from the padding check but remain included in the summary's total. The summary ends with the number of distinct ignored names, so duplicates do not increase that count.

structpad check --ignore Packet net.sp
Frame: 12 bytes of padding (limit 0)
1 of 3 structs over the padding limit (1 ignored)

Ignoring both Packet and Frame prints ok, 3 structs (2 ignored) and exits with status 0. Invalid names such as 1x produce invalid --ignore "1x"; a name not defined in the file produces unknown struct "Nope" in --ignore. Both errors exit with status 2.

--struct NAME is available only with layout and may be repeated; --struct=NAME is also supported. Only the named structs are printed, once each and in file order, with the same numbers as in the full listing, because nested structs are still resolved from the whole file.

structpad layout --struct Frame --struct Wire net.sp
struct Wire: size 5, align 1, padding 0, packed
offset   size  field
     0      1  kind: u8
     1      4  value: u32

struct Frame: size 56, align 8, padding 12
offset   size  field
     0      1  version: u8
     1      7  (padding)
     8     32  head: struct Packet
    40     10  tail: struct Wire[2]
    50      1  ok: bool
    51      5  (padding)

An invalid name such as 1x produces invalid --struct "1x", a name not defined in the file produces unknown struct "Nope" in --struct, and --struct with suggest or check produces suggest does not take --struct or check does not take --struct. All of these exit with status 2.

--format json is available only with layout; --format=json is also supported and --format text is the default. It prints the same structs as the text listing (also with --struct) as one JSON array, indented by two spaces, with the keys in the order shown below. fields lists the fields with the offset and size of each one and gaps lists every run of padding bytes, including the tail, so the sizes in gaps add up to padding. For struct Pair { u8 a; u32 b; }:

structpad layout --format json pair.sp
[
  {
    "name": "Pair",
    "size": 8,
    "align": 4,
    "padding": 3,
    "packed": false,
    "fields": [
      {
        "name": "a",
        "type": "u8",
        "offset": 0,
        "size": 1
      },
      {
        "name": "b",
        "type": "u32",
        "offset": 4,
        "size": 4
      }
    ],
    "gaps": [
      {
        "offset": 1,
        "size": 3
      }
    ]
  }
]

An invalid value such as xml produces invalid --format "xml" (use text or json), and --format with suggest or check produces suggest does not take --format or check does not take --format. Both exit with status 2.

Select the ABI with --abi lp64 (the default) or --abi ilp32; for example:

structpad layout --abi ilp32 net.sp

Use - as the file argument to read from standard input. The display name in diagnostics is <stdin>.

Input language

A file contains one or more struct definitions. Each field is a primitive type, an array, or a previously defined struct:

struct Header {
    u8 kind;
    u32 length;
    char name[10];
}

Names begin with an ASCII letter or underscore. Array lengths are decimal values from 1 through 65536. Use packed struct Name { to give all fields byte alignment. Blank lines, comments beginning with //, and a UTF-8 byte-order mark are accepted. A struct must contain at least one field, and nested structs must be defined earlier in the file.

The lp64 ABI uses 8-byte pointers; ilp32 uses 4-byte pointers. Both ABIs align 64-bit integers and floats to 8 bytes under lp64; under ilp32 they keep their 8-byte size but use 4-byte alignment. Each field starts at an offset aligned to its type, and the final struct size is rounded up to the struct alignment. Padding is the space between fields and at the end.

The pointer-width integers usize and isize have the size and alignment of ptr: 8 and 8 under lp64, 4 and 4 under ilp32. They are the types to use for lengths and offsets that follow the platform, such as size_t in C or usize in Rust. For struct Buf { u8 tag; usize len; isize off; u16 flags; }, structpad layout --abi ilp32 prints:

struct Buf: size 16, align 4, padding 5
offset   size  field
     0      1  tag: u8
     1      3  (padding)
     4      4  len: usize
     8      4  off: isize
    12      2  flags: u16
    14      2  (padding)

Exit codes

  • 0: command succeeded, or check found no struct over the padding limit.
  • 1: check found at least one struct over the padding limit.
  • 2: usage or input error.

Development

Run the test suite and linter from the project root:

python -m pytest -q
python -m ruff check .

License

MIT, see LICENSE.


Built with BAKED, an open-source coding agent.

About

Show the byte layout of binary structs with padding, suggest a leaner field order, and fail CI on wasted space

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages