Utilities for byte order manipulation and bit-level operations for handling different endianness and extracting components from primitive types.
Reverse the byte order of primitive types to handle different endianness.
/** Reverse byte order of a Short (16-bit) */
expect fun Short.reverseByteOrder(): Short
/** Reverse byte order of an Int (32-bit) */
expect fun Int.reverseByteOrder(): Int
/** Reverse byte order of a Long (64-bit) */
expect fun Long.reverseByteOrder(): Long
/** Reverse byte order of a Float (32-bit) */
expect fun Float.reverseByteOrder(): Float
/** Reverse byte order of a Double (64-bit) */
expect fun Double.reverseByteOrder(): Double
/** Reverse byte order of an unsigned Short (16-bit) */
fun UShort.reverseByteOrder(): UShort
/** Reverse byte order of an unsigned Int (32-bit) */
fun UInt.reverseByteOrder(): UInt
/** Reverse byte order of an unsigned Long (64-bit) */
fun ULong.reverseByteOrder(): ULongUsage Examples:
import io.ktor.utils.io.bits.*
fun byteOrderReversalExamples() {
// Reverse signed integers
val short: Short = 0x1234
val reversedShort = short.reverseByteOrder() // 0x3412
val int: Int = 0x12345678
val reversedInt = int.reverseByteOrder() // 0x78563412
val long: Long = 0x123456789ABCDEF0L
val reversedLong = long.reverseByteOrder() // 0xF0DEBC9A78563412L
// Reverse floating point numbers
val float: Float = 3.14f
val reversedFloat = float.reverseByteOrder()
val double: Double = 2.718281828
val reversedDouble = double.reverseByteOrder()
// Reverse unsigned integers
val uShort: UShort = 0x1234u
val reversedUShort = uShort.reverseByteOrder() // 0x3412u
val uInt: UInt = 0x12345678u
val reversedUInt = uInt.reverseByteOrder() // 0x78563412u
val uLong: ULong = 0x123456789ABCDEF0uL
val reversedULong = uLong.reverseByteOrder() // 0xF0DEBC9A78563412uL
println("Original short: 0x${short.toString(16)}")
println("Reversed short: 0x${reversedShort.toString(16)}")
}Extract individual bytes and components from larger primitive types.
/** Extract high byte from a Short */
val Short.highByte: Byte
/** Extract low byte from a Short */
val Short.lowByte: Byte
/** Extract high short from an Int */
val Int.highShort: Short
/** Extract low short from an Int */
val Int.lowShort: Short
/** Extract high int from a Long */
val Long.highInt: Int
/** Extract low int from a Long */
val Long.lowInt: IntUsage Examples:
import io.ktor.utils.io.bits.*
fun byteExtractionExamples() {
// Extract bytes from Short
val shortValue: Short = 0x1234
val highByte = shortValue.highByte // 0x12
val lowByte = shortValue.lowByte // 0x34
println("Short 0x${shortValue.toString(16)}: high=0x${highByte.toUByte().toString(16)}, low=0x${lowByte.toUByte().toString(16)}")
// Extract shorts from Int
val intValue: Int = 0x12345678
val highShort = intValue.highShort // 0x1234
val lowShort = intValue.lowShort // 0x5678
println("Int 0x${intValue.toString(16)}: high=0x${highShort.toUShort().toString(16)}, low=0x${lowShort.toUShort().toString(16)}")
// Extract ints from Long
val longValue: Long = 0x123456789ABCDEF0L
val highInt = longValue.highInt // 0x12345678
val lowInt = longValue.lowInt // 0x9ABCDEF0.toInt()
println("Long 0x${longValue.toString(16)}: high=0x${highInt.toUInt().toString(16)}, low=0x${lowInt.toUInt().toString(16)}")
// Reconstruct values
val reconstructedShort = ((highByte.toInt() and 0xFF) shl 8) or (lowByte.toInt() and 0xFF)
println("Reconstructed short: 0x${reconstructedShort.toString(16)} (original: 0x${shortValue.toString(16)})")
}Common patterns for handling different byte orders in network protocols and file formats.
Usage Examples:
import io.ktor.utils.io.*
import io.ktor.utils.io.bits.*
suspend fun endiannessHandlingExamples() {
// Reading big-endian data from a little-endian system
val channel = ByteReadChannel(byteArrayOf(0x12, 0x34, 0x56, 0x78))
val rawInt = channel.readInt()
val bigEndianInt = if (isLittleEndian()) {
rawInt.reverseByteOrder()
} else {
rawInt
}
println("Big-endian int: 0x${bigEndianInt.toString(16)}")
// Writing little-endian data
val writeChannel = ByteChannel()
val value = 0x12345678
val littleEndianValue = if (isBigEndian()) {
value.reverseByteOrder()
} else {
value
}
writeChannel.writeInt(littleEndianValue)
writeChannel.flush()
}
// Platform detection functions (examples)
fun isLittleEndian(): Boolean {
val test = 0x12345678
val bytes = ByteArray(4)
bytes[0] = (test and 0xFF).toByte()
bytes[1] = ((test shr 8) and 0xFF).toByte()
bytes[2] = ((test shr 16) and 0xFF).toByte()
bytes[3] = ((test shr 24) and 0xFF).toByte()
return bytes[0] == 0x78.toByte()
}
fun isBigEndian(): Boolean = !isLittleEndian()Using byte order operations for implementing network protocols and binary formats.
Usage Examples:
import io.ktor.utils.io.*
import io.ktor.utils.io.bits.*
// Example: Reading a binary protocol header
suspend fun readProtocolHeader(channel: ByteReadChannel): ProtocolHeader {
// Protocol uses big-endian format
val magic = channel.readInt().let {
if (isLittleEndian()) it.reverseByteOrder() else it
}
val version = channel.readShort().let {
if (isLittleEndian()) it.reverseByteOrder() else it
}
val flags = channel.readShort() // Flags might not need byte order conversion
val length = channel.readInt().let {
if (isLittleEndian()) it.reverseByteOrder() else it
}
return ProtocolHeader(magic, version, flags, length)
}
// Example: Writing a binary protocol header
suspend fun writeProtocolHeader(channel: ByteWriteChannel, header: ProtocolHeader) {
// Write in big-endian format
val magic = if (isLittleEndian()) header.magic.reverseByteOrder() else header.magic
val version = if (isLittleEndian()) header.version.reverseByteOrder() else header.version
val length = if (isLittleEndian()) header.length.reverseByteOrder() else header.length
channel.writeInt(magic)
channel.writeShort(version)
channel.writeShort(header.flags)
channel.writeInt(length)
}
data class ProtocolHeader(
val magic: Int,
val version: Short,
val flags: Short,
val length: Int
)Advanced bit manipulation using byte order operations.
Usage Examples:
import io.ktor.utils.io.bits.*
fun bitManipulationExamples() {
val value = 0x12345678
// Extract individual bytes
val byte0 = (value and 0xFF).toByte() // 0x78
val byte1 = ((value shr 8) and 0xFF).toByte() // 0x56
val byte2 = ((value shr 16) and 0xFF).toByte() // 0x34
val byte3 = ((value shr 24) and 0xFF).toByte() // 0x12
// Use high/low extraction
val lowShort = value.lowShort // 0x5678
val highShort = value.highShort // 0x1234
// Swap bytes within shorts
val swappedLow = lowShort.reverseByteOrder() // 0x7856
val swappedHigh = highShort.reverseByteOrder() // 0x3412
// Reconstruct with swapped bytes
val reconstructed = (swappedHigh.toInt() shl 16) or (swappedLow.toInt() and 0xFFFF)
println("Original: 0x${value.toString(16)}")
println("Reconstructed: 0x${reconstructed.toString(16)}")
// Working with floating point bit patterns
val floatValue = 3.14f
val floatBits = floatValue.toBits()
val reversedBits = floatBits.reverseByteOrder()
val reversedFloat = Float.fromBits(reversedBits)
println("Float: $floatValue -> bits: 0x${floatBits.toString(16)} -> reversed: $reversedFloat")
}Operations for working with memory layouts and packed structures.
Usage Examples:
import io.ktor.utils.io.*
import io.ktor.utils.io.bits.*
// Example: Packed structure with explicit byte order
suspend fun packedStructureExample() {
val channel = ByteChannel()
// Write packed structure (little-endian)
val timestamp = System.currentTimeMillis()
val value = 42.5f
val count = 1000
// Write as little-endian regardless of platform
channel.writeLong(if (isBigEndian()) timestamp.reverseByteOrder() else timestamp)
channel.writeFloat(if (isBigEndian()) value.reverseByteOrder() else value)
channel.writeInt(if (isBigEndian()) count.reverseByteOrder() else count)
channel.flush()
// Read back the structure
val readTimestamp = channel.readLong().let {
if (isBigEndian()) it.reverseByteOrder() else it
}
val readValue = channel.readFloat().let {
if (isBigEndian()) it.reverseByteOrder() else it
}
val readCount = channel.readInt().let {
if (isBigEndian()) it.reverseByteOrder() else it
}
println("Read: timestamp=$readTimestamp, value=$readValue, count=$readCount")
}
// Helper for working with byte arrays and byte order
fun ByteArray.toIntLE(offset: Int = 0): Int {
return ((this[offset + 3].toInt() and 0xFF) shl 24) or
((this[offset + 2].toInt() and 0xFF) shl 16) or
((this[offset + 1].toInt() and 0xFF) shl 8) or
(this[offset].toInt() and 0xFF)
}
fun ByteArray.toIntBE(offset: Int = 0): Int {
return ((this[offset].toInt() and 0xFF) shl 24) or
((this[offset + 1].toInt() and 0xFF) shl 16) or
((this[offset + 2].toInt() and 0xFF) shl 8) or
(this[offset + 3].toInt() and 0xFF)
}// Convert to network byte order
val networkInt = if (isLittleEndian()) value.reverseByteOrder() else value
// Convert from network byte order
val hostInt = if (isLittleEndian()) networkInt.reverseByteOrder() else networkInt// Little-endian file format
val fileValue = if (isBigEndian()) value.reverseByteOrder() else value
// Big-endian file format
val fileValue = if (isLittleEndian()) value.reverseByteOrder() else value// Always write in specific byte order
fun writePortableInt(channel: ByteWriteChannel, value: Int, littleEndian: Boolean) {
val ordered = when {
littleEndian && isBigEndian() -> value.reverseByteOrder()
!littleEndian && isLittleEndian() -> value.reverseByteOrder()
else -> value
}
channel.writeInt(ordered)
}