Files
VerseVDI-Data-Plane/gateway/input.go

172 lines
5.7 KiB
Go

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
}