README.md in device_input-0.0.1.1 vs README.md in device_input-0.0.3.1

- old
+ new

@@ -1,15 +1,119 @@ -# Background +# Device Input -We want to read events from e.g. `/dev/input/event0` in Ruby. +*for the Linux kernel* +We want to read events from e.g. `/dev/input/event0` in Ruby. For example, +if you want to see what's happening "on the wire" when you press a special +function key on a laptop. While this code can be used for the purpose of +malicious keystroke logging, it is not well suited for it and does not provide +the root privileges in order to read `/dev/input`. Once you've got the +privilege to read `/dev/input` it's *game over* anyway. + +## Rationale + +`/dev/input/eventX` is just a character device. Can't we read it with simple +Unix tooling? Yes and no. First of all, a character device just means that +it passes bytes (not necessarily characters or strings) from userspace into +the kernel. Secondarily, the messages (defined as C structs) are in fact +binary and not strings or conventional characters. + +Since these are C structs (analagous to a binary message), we need to be able +to delimit individual messages and decode them. We can't simply read a byte +at a time and try to make sense of it. In fact, on my system, +`/dev/input/event0` refuses any read that is not a multiple of the struct / +message size, so we need to know the message size before even attempting a +read(), without even a decode(). + +To determine the message size, we need to know the data structure. For a +long time, it was pretty simple: events are 16 bytes: + +* timestamp - 8 bytes +* type - 1 byte +* code - 1 byte +* value - 2 bytes + +However, this is only true for 32-bit platforms. On 64-bit platforms, event +timestamps became 16 bytes, increasing events from 16 to 24 bytes. This is +because a timestamp is defined as two `long`s, and `long`s are bigger on +64-bit platforms. It's easy to remember: + +* 32-bit platform: 32-bit `long` (4 bytes) +* 64-bit platform: 64-bit `long` (8 bytes) + +`read(/dev/input/event0, 16)` will fail on a 64-bit machine. + +Your tooling must be aware of this distinction and choose the correct +underlying data types just to be able to delimit messages and perform a +successful read. This software does that, decodes the message, maps the +encoded values to friendly strings for display, and provides both library and +executable code to assist in examining kernel input events. + +# Installation + +Install the gem: + +``` +$ gem install device_input # sudo as necessary +``` + +Or, if using [Bundler](http://bundler.io/), add to your `Gemfile`: + +``` +gem 'device_input', '~> 0.0' +``` + +# Usage + +## Executable + +``` +$ sudo devsniff /dev/input/event0 +``` + +When the `f` key is pressed: + +``` +Misc:ScanCode:33 +Key:F:1 +Sync:Sync:0 +``` + +And released: +``` +Misc:ScanCode:33 +Key:F:0 +Sync:Sync:0 +``` + +## Library + +``` +require 'device_input' + +DeviceInput.read_from('/dev/input/event0') do |event| + puts event +end +``` + +An event has: + +* `#data` - a Struct of ints (class name Data) +* `#time` - a Time, accurate to usecs +* `#type` - a String, possibly UNK-X where X is the integer from `#data` +* `#code` - a String, possibly UNK-X-Y where X and Y are from `#data` +* `#value` - a Fixnum (signed) + +# Research + ## Kernel docs * https://www.kernel.org/doc/Documentation/input/input.txt * https://www.kernel.org/doc/Documentation/input/event-codes.txt -These events are defined as C structs with a fixed size in bytes. +These events are defined as C structs with a fixed size in bytes. See more +about these structs towards the end of this document. ## Kernel structs from https://www.kernel.org/doc/Documentation/input/input.txt @@ -32,46 +136,43 @@ __u16 code; __s32 value; }; ``` -What's a `timeval`? -https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/time.h#n15 +What's a [`timeval`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/time.h#n15)? ``` struct timeval { __kernel_time_t tv_sec; /* seconds */ __kernel_suseconds_t tv_usec; /* microseconds */ }; ``` -What's a `__kernel_time_t`? -https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/posix_types.h#n88 +What's a [`__kernel_time_t`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/posix_types.h#n88)? ``` typedef long __kernel_long_t; # ... typedef __kernel_long_t __kernel_suseconds_t; # ... typedef __kernel_long_t __kernel_time_t; ``` -What's a `__u16`? We're pretty sure it's an unsigned 16 bit integer. -Likewise `__s32` should be a signed 32-bit integer: +What's a [`__u16`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/int-l64.h#n23)? +We're pretty sure it's an unsigned 16 bit integer. Likewise `__s32` should +be a signed 32-bit integer: -https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/int-l64.h#n23 - ``` typedef unsigned short __u16; typedef __signed__ int __s32; ``` Why is the value signed? It's meant to be able to communicate an "analog" range, say -127 to +127 as determined by the position of a joystick. -Let's review: +## Review `input_event` * time (timeval) - tv_sec (long) @@ -82,20 +183,28 @@ Flattened: `SEC` `USEC` `TYPE` `CODE` `VALUE` How many bytes is a `long`? Well, it's platform-dependent. On a 32-bit platform, you get 32 bits (4 bytes). On a 64-bit platform you get 64 bits -(8 bytes). +(8 bytes). This means that the event is 16 bytes on a 32-bit machine and +24 bytes on a 64-bit machine. Software will need to accommodate. -This means that the event is 16 bytes on a 32-bit machine and 24 bytes on a -64-bit machine. Software will need to accommodate. +## Ruby tools -We can use `RbConfig` and `Array#pack` to help us read these binary structs: +We can use `RbConfig` and `Array#pack`/`String#unpack` to help us read these +binary structs: ``` FIELD C RbConfig Pack +--- --- --- --- +tv_sec long long l! tv_usec long long l! -tv_usec long long l! type __u16 uint16_t S code __u16 uint16_t S value __s32 int32_t l ``` + +# Acknowledgments + +* Inspired by https://github.com/prullmann/libdevinput (don't use it) + - also the source of the [event code labels](lib/device_input/codes.rb) +* Thanks to al2o3-cr from #ruby on Freenode for feedback