go-i3/byteorder.go

64 lines
2.1 KiB
Go
Raw Permalink Normal View History

2023-10-22 16:19:10 +02:00
package i3
import (
"encoding/binary"
"io"
"strings"
)
// detectByteOrder sends messages to i3 to determine the byte order it uses.
// For details on this technique, see:
// https://build.i3wm.org/docs/ipc.html#_appendix_a_detecting_byte_order_in_memory_safe_languages
func detectByteOrder(conn io.ReadWriter) (binary.ByteOrder, error) {
const (
// targetLen is 0x00 01 01 00 in both, big and little endian
targetLen = 65536 + 256
// SUBSCRIBE was introduced in 3.e (2010-03-30)
prefixSubscribe = "[]"
// RUN_COMMAND was always present
prefixCmd = "nop byte-order detection. padding: "
)
// 2. Send a big endian encoded message of type SUBSCRIBE:
payload := []byte(prefixSubscribe + strings.Repeat(" ", targetLen-len(prefixSubscribe)))
if err := binary.Write(conn, binary.BigEndian, &header{magic, uint32(len(payload)), messageTypeSubscribe}); err != nil {
return nil, err
}
if _, err := conn.Write(payload); err != nil {
return nil, err
}
// 3. Send a byte order independent RUN_COMMAND message:
payload = []byte(prefixCmd + strings.Repeat("a", targetLen-len(prefixCmd)))
if err := binary.Write(conn, binary.BigEndian, &header{magic, uint32(len(payload)), messageTypeRunCommand}); err != nil {
return nil, err
}
if _, err := conn.Write(payload); err != nil {
return nil, err
}
// 4. Receive a message header, decode the message type as big endian:
var header [14]byte
if _, err := io.ReadFull(conn, header[:]); err != nil {
return nil, err
}
if messageType(binary.BigEndian.Uint32(header[10:14])) == messageReplyTypeCommand {
order := binary.LittleEndian // our big endian message was not answered
// Read remaining payload
_, err := io.ReadFull(conn, make([]byte, order.Uint32(header[6:10])))
return order, err
}
order := binary.BigEndian // our big endian message was answered
// Read remaining payload
if _, err := io.ReadFull(conn, make([]byte, order.Uint32(header[6:10]))); err != nil {
return order, err
}
// Slurp the pending RUN_COMMAND reply.
sock := &socket{conn: conn, order: order}
_, err := sock.recvMsg()
return binary.BigEndian, err
}