#pragma once enum packet_type { PACKET_IOCTL, PACKET_WRITE, }; enum ioctl_dir { IOCTL_NONE, IOCTL_IOR, IOCTL_IOW, IOCTL_IOWR, }; enum ioctl_arg { IOCTL_ARG_INT, IOCTL_ARG_OTHER, // All bets off, can't assure portability }; /** * Lackluster "network transparent" ioctl call * Implements enough to have remote uinput interface. */ struct ioctl { /** * Because the 32bit encoding of ioctl message is not stable ABI interface between CPU architectures, * we'll write the raw _IOC arguments on the wire and reconstruct the 32bit encoding on uinputd. */ uint8_t dir; // enum ioctl_dir, we can't use bitmask directly, as it's not portable uint8_t type; // type of ioctl, e.g. UINPUT_IOCTL_BASE uint8_t nr; // command code (nr), e.g. 100 of UINPUT_IOCTL_BASE which means UI_SET_EVBIT /** * Ioctl ABI also includes argument size part, which is mainly used for typechecking. * However since the arguments are not exact size types, but again CPU architecture dependant sizes, and even * compiler dependant for structs (even though they use __u32 and friends, the padding may differ), we can't * really do generic network abstraction. * * To avoid creating wrapper/shim over uinput/evdev (or for other linux interfaces if ever needed to be expanded), we * use another type enum for ioctl argument type, for structs all bets are off, they may work or not work at all. * (e.g. the ioctl will fail since it won't pass typecheck due to different sized of structs in between client <-> server) */ uint8_t arg; // enum ioctl_arg, ^ read above union { uint32_t integer; uint16_t bytes; // max ioctl parameter size can be 16kB -1 }; // payload, for IOCTL_ARG_OTHER, read the amount of bytes indicated by 'bytes' }; /** * Network transparent write call * * The call itself is portable, the underlying data may not be. * E.g. structs used by evdev aren't packed, and some of them contain non explicit types such as POSIX struct timeval. */ struct write { uint64_t bytes; // amount of bytes to be written // payload, read the amount of bytes indicated by 'bytes' }; /** * Binary specification for uinputd messages. * All fields must be in little-endian. * * All packet operations are applied into the open uinput fd. */ struct packet { uint8_t type; // enum packet_type union { struct ioctl ioctl; struct write write; }; } __attribute__((packed)) __attribute__((scalar_storage_order("little-endian")));