Byte Order (Endianness)¶
Byte order, also called endianness, determines how multi-byte values are arranged in memory. PDC Struct supports all common byte order formats with automatic propagation to nested structures.
What is Byte Order?¶
When storing a 16-bit value like 0x1234 in memory:
Little-endian (least significant byte first):
Big-endian (most significant byte first):
Supported Byte Orders¶
PDC Struct provides three byte order options via the ByteOrder enum:
from pdc_struct import ByteOrder
# Little-endian (x86, ARM in little mode)
ByteOrder.LITTLE_ENDIAN # '<' in struct format
# Big-endian (network byte order, some ARM/MIPS)
ByteOrder.BIG_ENDIAN # '>' in struct format
# Native (matches current platform)
ByteOrder.NATIVE # '=' in struct format
Setting Byte Order¶
Byte order is specified in StructConfig:
from pdc_struct import StructModel, StructConfig, StructMode, ByteOrder
from pdc_struct.c_types import UInt16, UInt32
class Packet(StructModel):
sequence: UInt16
timestamp: UInt32
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.BIG_ENDIAN # Network byte order
)
packet = Packet(sequence=0x1234, timestamp=0x56789ABC)
data = packet.to_bytes()
# Bytes are in big-endian order
print(data.hex()) # '123456789abc'
Default Byte Order¶
If not specified, byte order defaults to the system's native endianness:
class Example(StructModel):
value: UInt16
struct_config = StructConfig() # Uses system byte order
# On x86/x64 (little-endian), defaults to LITTLE_ENDIAN
# On some ARM/MIPS (big-endian), defaults to BIG_ENDIAN
Common Use Cases¶
Network Protocols (Big-Endian)¶
Network protocols typically use big-endian (network byte order):
from ipaddress import IPv4Address
from pdc_struct import StructModel, StructConfig, StructMode, ByteOrder
from pdc_struct.c_types import UInt16
class UDPHeader(StructModel):
"""UDP header per RFC 768."""
source_port: UInt16
dest_port: UInt16
length: UInt16
checksum: UInt16
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.BIG_ENDIAN # Network byte order
)
header = UDPHeader(
source_port=53, # DNS
dest_port=12345,
length=100,
checksum=0xABCD
)
Little-Endian Systems¶
Most modern computers use little-endian:
from pdc_struct import StructModel, StructConfig, StructMode, ByteOrder
from pdc_struct.c_types import UInt16, UInt32
class FileHeader(StructModel):
"""Custom file format header."""
magic: UInt16 # File type identifier
version: UInt16
file_size: UInt32
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.LITTLE_ENDIAN # Most common for files
)
Platform-Native Byte Order¶
Use NATIVE when interfacing with platform-specific APIs:
class SystemStruct(StructModel):
"""Matches system's native byte order."""
flags: UInt32
pointer: UInt32
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.NATIVE # Matches platform
)
Byte Order Propagation¶
By default, byte order automatically propagates to nested structs. This ensures consistent endianness throughout the entire structure.
Automatic Propagation (Default)¶
from pdc_struct import StructModel, StructConfig, StructMode, ByteOrder
from pdc_struct.c_types import UInt16
class Inner(StructModel):
x: UInt16
y: UInt16
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.LITTLE_ENDIAN # Inner's preference
)
class Outer(StructModel):
id: UInt16
data: Inner # Nested struct
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.BIG_ENDIAN, # Override
propagate_byte_order=True # Default - propagates to Inner
)
outer = Outer(id=1, data=Inner(x=2, y=3))
# Both outer and inner fields use BIG_ENDIAN
# Inner's byte_order setting is overridden
Disabling Propagation¶
Set propagate_byte_order=False to let nested structs use their own byte order:
class Outer(StructModel):
id: UInt16
data: Inner
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.BIG_ENDIAN,
propagate_byte_order=False # Each struct uses its own byte order
)
# Outer fields: BIG_ENDIAN
# Inner fields: LITTLE_ENDIAN (from Inner's config)
This is useful for mixing protocols or wrapping foreign binary formats.
Checking Byte Order¶
Format String¶
The format string shows the byte order prefix:
class Example(StructModel):
value: UInt16
struct_config = StructConfig(
byte_order=ByteOrder.LITTLE_ENDIAN
)
print(Example.struct_format_string()) # '<H' (little-endian uint16)
# Byte order prefixes:
# '<' = Little-endian
# '>' = Big-endian
# '=' = Native
Runtime Detection¶
import sys
# Check system byte order
if sys.byteorder == "little":
print("System is little-endian")
else:
print("System is big-endian")
Examples¶
Network Packet with Mixed Endianness¶
from pdc_struct import StructModel, StructConfig, StructMode, ByteOrder
from pdc_struct.c_types import UInt16, UInt32
class PayloadData(StructModel):
"""Payload uses little-endian (application data)."""
sensor_id: UInt16
reading: UInt32
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.LITTLE_ENDIAN
)
class NetworkPacket(StructModel):
"""Header uses big-endian (network order)."""
protocol_id: UInt16
sequence: UInt16
payload: PayloadData
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.BIG_ENDIAN,
propagate_byte_order=False # Payload keeps its own byte order
)
packet = NetworkPacket(
protocol_id=0x1234,
sequence=42,
payload=PayloadData(sensor_id=5, reading=12345)
)
# Header fields in big-endian, payload in little-endian
Cross-Platform File Format¶
from pdc_struct import StructModel, StructConfig, StructMode, ByteOrder
from pydantic import Field
from pdc_struct.c_types import UInt16, UInt32
class FileHeader(StructModel):
"""Always big-endian for cross-platform compatibility."""
magic: UInt16 = 0x4D59 # 'MY' in hex
version: UInt16
record_count: UInt32
name: str = Field(max_length=32)
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.BIG_ENDIAN # Platform-independent
)
# Works the same on all platforms
header = FileHeader(version=1, record_count=100, name="dataset-2025")
# Write to file
with open("data.bin", "wb") as f:
f.write(header.to_bytes())
# Read on any platform (converts automatically)
with open("data.bin", "rb") as f:
loaded = FileHeader.from_bytes(f.read(FileHeader.struct_size()))
ARM/x86 Interoperability¶
# C code on ARM (big-endian) writes:
# struct sensor_data {
# uint16_t device_id;
# uint32_t timestamp;
# int16_t temperature;
# } __attribute__((packed));
# Python on x86 (little-endian) reads:
from pdc_struct import StructModel, StructConfig, StructMode, ByteOrder
from pdc_struct.c_types import UInt16, UInt32, Int16
class SensorData(StructModel):
device_id: UInt16
timestamp: UInt32
temperature: Int16
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.BIG_ENDIAN # Match ARM's byte order
)
# PDC Struct handles conversion automatically
with open("/dev/sensor", "rb") as f:
data = f.read(SensorData.struct_size())
sensor = SensorData.from_bytes(data)
print(f"Temp: {sensor.temperature / 100:.1f}°C")
DYNAMIC Mode and Byte Order¶
In DYNAMIC mode, the byte order is stored in the header:
from pdc_struct import StructModel, StructConfig, StructMode, ByteOrder
from pdc_struct.c_types import UInt16
class Message(StructModel):
msg_id: UInt16
value: UInt16
struct_config = StructConfig(
mode=StructMode.DYNAMIC,
byte_order=ByteOrder.BIG_ENDIAN
)
msg = Message(msg_id=1, value=0x1234)
data = msg.to_bytes()
# Header byte 1, bit 0 indicates endianness
# Unpacking reads the header and uses correct byte order
restored = Message.from_bytes(data)
assert restored.value == 0x1234 # Correct regardless of system
The header's endianness flag ensures correct unpacking even across different platforms.
BitFields and Byte Order¶
BitFields respect byte order for multi-byte widths (16-bit and 32-bit):
from pdc_struct import BitFieldModel, StructModel, StructConfig, StructMode, ByteOrder
from pdc_struct.models.bit_field import Bit
class Flags(BitFieldModel):
flag0: bool = Bit(0)
flag15: bool = Bit(15)
struct_config = StructConfig(bit_width=16) # 2 bytes
class Packet(StructModel):
flags: Flags
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.BIG_ENDIAN # Affects how 16-bit flags are stored
)
8-bit bitfields (1 byte) are unaffected by byte order.
Best Practices¶
- Network protocols → Use
ByteOrder.BIG_ENDIAN(network byte order) - Cross-platform files → Use
ByteOrder.BIG_ENDIANorByteOrder.LITTLE_ENDIANexplicitly - Platform-specific → Use
ByteOrder.NATIVEwhen interfacing with OS APIs - Mixed protocols → Set
propagate_byte_order=Falseand specify per-struct - Document your choice → Add comments explaining why you chose a specific byte order
Common Pitfalls¶
Forgetting to Set Byte Order¶
# Bad - defaults to native, not portable
class NetworkHeader(StructModel):
sequence: UInt16
struct_config = StructConfig(mode=StructMode.C_COMPATIBLE)
# Byte order depends on platform!
# Good - explicit byte order
class NetworkHeader(StructModel):
sequence: UInt16
struct_config = StructConfig(
mode=StructMode.C_COMPATIBLE,
byte_order=ByteOrder.BIG_ENDIAN # Explicit
)
Mixing Byte Orders Unintentionally¶
# Problematic - nested struct has different byte order
class Inner(StructModel):
value: UInt32
struct_config = StructConfig(
byte_order=ByteOrder.LITTLE_ENDIAN
)
class Outer(StructModel):
header: UInt32
data: Inner
struct_config = StructConfig(
byte_order=ByteOrder.BIG_ENDIAN,
propagate_byte_order=False # Inner keeps little-endian - intended?
)
Summary¶
Byte order is critical for:
- Network protocols - Use big-endian (network byte order)
- File formats - Choose explicitly for cross-platform compatibility
- Hardware interfaces - Match device's byte order
- Nested structs - Use propagation for consistency
Key points:
- Explicitly set byte order for portability
- Use
propagate_byte_order=True(default) for consistent endianness - Big-endian for networks, little-endian for most files/systems
- DYNAMIC mode stores byte order in header for automatic handling
For more information:
- Modes - How byte order works in C_COMPATIBLE vs DYNAMIC
- Nested Structs - Byte order propagation in nested structures
- Types - Which types are affected by byte order