package gateway import ( "encoding/binary" "errors" "unicode/utf8" ) var ErrInputMalformed = errors.New("input event malformed") const ( gatewayInputHeaderSize = 6 gatewayInputKeyboard = 1 gatewayInputMouse = 2 gatewayInputRelative = 3 gatewayInputUTF8 = 4 gatewayInputController = 5 gatewayInputAbsolute = 6 gatewayInputScroll = 7 ) func EncodeInputEvent(event InputEvent) ([]byte, error) { switch event.Device { case "keyboard": if event.Code < 1 || event.Code > 0xffff || len(event.Payload) > 1 { return nil, ErrInputMalformed } encoded := make([]byte, gatewayInputHeaderSize+4) copy(encoded, "VGI1") encoded[4], encoded[5], encoded[6] = gatewayInputKeyboard, 4, 0 if event.Pressed { encoded[6] = 1 } if len(event.Payload) == 1 { encoded[7] = event.Payload[0] } binary.BigEndian.PutUint16(encoded[8:10], uint16(event.Code)) return encoded, nil case "mouse-button": if event.Code < 1 || event.Code > 5 || len(event.Payload) != 0 { return nil, ErrInputMalformed } encoded := make([]byte, gatewayInputHeaderSize+3) copy(encoded, "VGI1") encoded[4], encoded[5], encoded[7] = gatewayInputMouse, 3, byte(event.Code) if event.Pressed { encoded[6] = 1 } return encoded, nil case "mouse-relative": if event.Pressed || event.Code != 0 || len(event.Payload) != 4 { return nil, ErrInputMalformed } encoded := make([]byte, gatewayInputHeaderSize+4) copy(encoded, "VGI1") encoded[4], encoded[5] = gatewayInputRelative, 4 copy(encoded[6:], event.Payload) return encoded, nil case "utf8": if event.Pressed || event.Code != 0 || len(event.Payload) == 0 || len(event.Payload) > utf8.UTFMax || !utf8.Valid(event.Payload) || utf8.RuneCount(event.Payload) != 1 { return nil, ErrInputMalformed } encoded := make([]byte, gatewayInputHeaderSize+len(event.Payload)) copy(encoded, "VGI1") encoded[4], encoded[5] = gatewayInputUTF8, byte(len(event.Payload)) copy(encoded[6:], event.Payload) return encoded, nil case "controller": if event.Code < 0 || event.Code > 15 || len(event.Payload) != 16 { return nil, ErrInputMalformed } active := binary.BigEndian.Uint16(event.Payload[:2]) if (!event.Pressed && anyNonzero(event.Payload)) || (event.Pressed && active == 0) { return nil, ErrInputMalformed } encoded := make([]byte, gatewayInputHeaderSize+17) copy(encoded, "VGI1") encoded[4], encoded[5], encoded[6] = gatewayInputController, 17, byte(event.Code) copy(encoded[7:], event.Payload) return encoded, nil case "mouse-absolute": if event.Pressed || event.Code != 0 || !validAbsolutePayload(event.Payload) { return nil, ErrInputMalformed } encoded := make([]byte, gatewayInputHeaderSize+8) copy(encoded, "VGI1") encoded[4], encoded[5] = gatewayInputAbsolute, 8 copy(encoded[6:], event.Payload) return encoded, nil case "mouse-scroll": if event.Pressed || event.Code != 0 || len(event.Payload) != 4 { return nil, ErrInputMalformed } encoded := make([]byte, gatewayInputHeaderSize+4) copy(encoded, "VGI1") encoded[4], encoded[5] = gatewayInputScroll, 4 copy(encoded[6:], event.Payload) return encoded, nil default: return nil, ErrInputMalformed } } func DecodeInputEvent(data []byte) (InputEvent, error) { if len(data) < gatewayInputHeaderSize || len(data) > 1179 || string(data[:4]) != "VGI1" || len(data) != gatewayInputHeaderSize+int(data[5]) { return InputEvent{}, ErrInputMalformed } kind, body := data[4], data[gatewayInputHeaderSize:] switch kind { case gatewayInputKeyboard: if len(body) != 4 || body[0] > 1 || binary.BigEndian.Uint16(body[2:4]) == 0 { return InputEvent{}, ErrInputMalformed } return InputEvent{Device: "keyboard", Code: int32(binary.BigEndian.Uint16(body[2:4])), Pressed: body[0] == 1, Payload: []byte{body[1]}}, nil case gatewayInputMouse: if len(body) != 3 || body[0] > 1 || body[1] < 1 || body[1] > 5 || body[2] != 0 { return InputEvent{}, ErrInputMalformed } return InputEvent{Device: "mouse-button", Code: int32(body[1]), Pressed: body[0] == 1}, nil case gatewayInputRelative: if len(body) != 4 { return InputEvent{}, ErrInputMalformed } return InputEvent{Device: "mouse-relative", Payload: append([]byte(nil), body...)}, nil case gatewayInputUTF8: if len(body) == 0 || len(body) > utf8.UTFMax || !utf8.Valid(body) || utf8.RuneCount(body) != 1 { return InputEvent{}, ErrInputMalformed } return InputEvent{Device: "utf8", Payload: append([]byte(nil), body...)}, nil case gatewayInputController: if len(body) != 17 || body[0] > 15 { return InputEvent{}, ErrInputMalformed } payload := append([]byte(nil), body[1:]...) active := binary.BigEndian.Uint16(payload[:2]) if active == 0 && anyNonzero(payload[2:]) { return InputEvent{}, ErrInputMalformed } return InputEvent{Device: "controller", Code: int32(body[0]), Pressed: active != 0, Payload: payload}, nil case gatewayInputAbsolute: if !validAbsolutePayload(body) { return InputEvent{}, ErrInputMalformed } return InputEvent{Device: "mouse-absolute", Payload: append([]byte(nil), body...)}, nil case gatewayInputScroll: if len(body) != 4 { return InputEvent{}, ErrInputMalformed } return InputEvent{Device: "mouse-scroll", Payload: append([]byte(nil), body...)}, nil default: return InputEvent{}, ErrInputMalformed } } func validAbsolutePayload(payload []byte) bool { if len(payload) != 8 { return false } x, y := binary.BigEndian.Uint16(payload[:2]), binary.BigEndian.Uint16(payload[2:4]) width, height := binary.BigEndian.Uint16(payload[4:6]), binary.BigEndian.Uint16(payload[6:8]) return width != 0 && height != 0 && x < width && y < height } func anyNonzero(data []byte) bool { for _, value := range data { if value != 0 { return true } } return false }