Byte Order
Byte order (endianness) determines how multi-byte values are stored in memory.
Big-Endian vs Little-Endian
Consider the integer 0x12345678 stored in a 4-byte buffer:
Most significant byte first (network byte order)
BIG-ENDIAN
[0]
12
[1]
34
[2]
56
[3]
78
Least significant byte first (x86/x64 native)
LITTLE-ENDIAN
[0]
78
[1]
56
[2]
34
[3]
12
Specifying Byte Order
import com.ditchoom.buffer.ByteOrder
// Big-endian (default)
val bigEndian = PlatformBuffer.allocate(
size = 1024,
byteOrder = ByteOrder.BIG_ENDIAN
)
// Little-endian
val littleEndian = PlatformBuffer.allocate(
size = 1024,
byteOrder = ByteOrder.LITTLE_ENDIAN
)
Checking Byte Order
val buffer = PlatformBuffer.allocate(1024)
println(buffer.byteOrder) // BIG_ENDIAN
When to Use Each
Big-Endian (BIG_ENDIAN)
- Network protocols: TCP/IP, HTTP, TLS
- File formats: PNG, JPEG, PDF
- Default for ByteBuffer
// Network packet parsing
val buffer = PlatformBuffer.allocate(1024, byteOrder = ByteOrder.BIG_ENDIAN)
buffer.writeShort(80) // Port number in network byte order
Little-Endian (LITTLE_ENDIAN)
- x86/x64 native: Intel/AMD processors
- Windows file formats: BMP, WAV
- Some protocols: USB, PCIe
// Windows BMP file parsing
val buffer = PlatformBuffer.allocate(1024, byteOrder = ByteOrder.LITTLE_ENDIAN)
buffer.writeInt(fileSize)
Common Protocol Byte Orders
| Protocol/Format | Byte Order |
|---|---|
| TCP/IP | Big-Endian |
| HTTP | Big-Endian |
| MQTT | Big-Endian |
| WebSocket | Big-Endian |
| USB | Little-Endian |
| BMP | Little-Endian |
| WAV | Little-Endian |
| Protocol Buffers | Little-Endian (varint) |
Example: Protocol Parsing
// MQTT uses big-endian for packet length
val mqttBuffer = PlatformBuffer.allocate(1024, byteOrder = ByteOrder.BIG_ENDIAN)
// Write packet type and length
mqttBuffer.writeByte(0x30) // PUBLISH packet
mqttBuffer.writeShort(topicLength.toShort())
mqttBuffer.writeString(topic)
Mixing Byte Orders
If you need to read/write different byte orders in the same buffer, read the largest primitive and swap bytes:
// Read a little-endian int from a big-endian buffer
// Uses one memory read (readInt) instead of 4 separate readByte() calls
fun ReadBuffer.readLittleEndianInt(): Int {
val bigEndian = readInt()
return ((bigEndian and 0xFF) shl 24) or
((bigEndian and 0xFF00) shl 8) or
((bigEndian and 0xFF0000) ushr 8) or
((bigEndian and 0xFF000000.toInt()) ushr 24)
}
// Similarly for Long (swap all 8 bytes)
fun ReadBuffer.readLittleEndianLong(): Long {
val bigEndian = readLong()
return ((bigEndian and 0xFF) shl 56) or
((bigEndian and 0xFF00) shl 40) or
((bigEndian and 0xFF0000) shl 24) or
((bigEndian and 0xFF000000) shl 8) or
((bigEndian and 0xFF00000000) ushr 8) or
((bigEndian and 0xFF0000000000) ushr 24) or
((bigEndian and 0xFF000000000000) ushr 40) or
((bigEndian ushr 56) and 0xFF)
}
Best Practices
- Use big-endian for network code - it's the standard
- Match the protocol spec - always check documentation
- Be explicit - specify byte order even when using default
- Document byte order - especially at API boundaries
- Read largest primitive - swap bytes after rather than reading byte-by-byte