Skip to content

Commit bcc307b

Browse files
authored
feat: hid rpc channel (#755)
* feat: use hidRpcChannel to save bandwidth * chore: simplify handshake of hid rpc * add logs * chore: add timeout when writing to hid endpoints * fix issues * chore: show hid rpc version * refactor hidrpc marshal / unmarshal * add queues for keyboard / mouse event * chore: change logging level of JSONRPC send event to trace * minor changes related to logging * fix: nil check * chore: add comments and remove unused code * add useMouse * chore: log msg data only when debug or trace mode * chore: make tslint happy * chore: unlock keyboardStateLock before calling onKeysDownChange * chore: remove keyPressReportApiAvailable * chore: change version handle * chore: clean up unused functions * remove comments
1 parent e8ef82e commit bcc307b

21 files changed

+1292
-216
lines changed

hidrpc.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package kvm
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/jetkvm/kvm/internal/hidrpc"
8+
"github.com/jetkvm/kvm/internal/usbgadget"
9+
)
10+
11+
func handleHidRPCMessage(message hidrpc.Message, session *Session) {
12+
var rpcErr error
13+
14+
switch message.Type() {
15+
case hidrpc.TypeHandshake:
16+
message, err := hidrpc.NewHandshakeMessage().Marshal()
17+
if err != nil {
18+
logger.Warn().Err(err).Msg("failed to marshal handshake message")
19+
return
20+
}
21+
if err := session.HidChannel.Send(message); err != nil {
22+
logger.Warn().Err(err).Msg("failed to send handshake message")
23+
return
24+
}
25+
session.hidRPCAvailable = true
26+
case hidrpc.TypeKeypressReport, hidrpc.TypeKeyboardReport:
27+
keysDownState, err := handleHidRPCKeyboardInput(message)
28+
if keysDownState != nil {
29+
session.reportHidRPCKeysDownState(*keysDownState)
30+
}
31+
rpcErr = err
32+
case hidrpc.TypePointerReport:
33+
pointerReport, err := message.PointerReport()
34+
if err != nil {
35+
logger.Warn().Err(err).Msg("failed to get pointer report")
36+
return
37+
}
38+
rpcErr = rpcAbsMouseReport(pointerReport.X, pointerReport.Y, pointerReport.Button)
39+
case hidrpc.TypeMouseReport:
40+
mouseReport, err := message.MouseReport()
41+
if err != nil {
42+
logger.Warn().Err(err).Msg("failed to get mouse report")
43+
return
44+
}
45+
rpcErr = rpcRelMouseReport(mouseReport.DX, mouseReport.DY, mouseReport.Button)
46+
default:
47+
logger.Warn().Uint8("type", uint8(message.Type())).Msg("unknown HID RPC message type")
48+
}
49+
50+
if rpcErr != nil {
51+
logger.Warn().Err(rpcErr).Msg("failed to handle HID RPC message")
52+
}
53+
}
54+
55+
func onHidMessage(data []byte, session *Session) {
56+
scopedLogger := hidRPCLogger.With().Bytes("data", data).Logger()
57+
scopedLogger.Debug().Msg("HID RPC message received")
58+
59+
if len(data) < 1 {
60+
scopedLogger.Warn().Int("length", len(data)).Msg("received empty data in HID RPC message handler")
61+
return
62+
}
63+
64+
var message hidrpc.Message
65+
66+
if err := hidrpc.Unmarshal(data, &message); err != nil {
67+
scopedLogger.Warn().Err(err).Msg("failed to unmarshal HID RPC message")
68+
return
69+
}
70+
71+
scopedLogger = scopedLogger.With().Str("descr", message.String()).Logger()
72+
73+
t := time.Now()
74+
75+
r := make(chan interface{})
76+
go func() {
77+
handleHidRPCMessage(message, session)
78+
r <- nil
79+
}()
80+
select {
81+
case <-time.After(1 * time.Second):
82+
scopedLogger.Warn().Msg("HID RPC message timed out")
83+
case <-r:
84+
scopedLogger.Debug().Dur("duration", time.Since(t)).Msg("HID RPC message handled")
85+
}
86+
}
87+
88+
func handleHidRPCKeyboardInput(message hidrpc.Message) (*usbgadget.KeysDownState, error) {
89+
switch message.Type() {
90+
case hidrpc.TypeKeypressReport:
91+
keypressReport, err := message.KeypressReport()
92+
if err != nil {
93+
logger.Warn().Err(err).Msg("failed to get keypress report")
94+
return nil, err
95+
}
96+
keysDownState, rpcError := rpcKeypressReport(keypressReport.Key, keypressReport.Press)
97+
return &keysDownState, rpcError
98+
case hidrpc.TypeKeyboardReport:
99+
keyboardReport, err := message.KeyboardReport()
100+
if err != nil {
101+
logger.Warn().Err(err).Msg("failed to get keyboard report")
102+
return nil, err
103+
}
104+
keysDownState, rpcError := rpcKeyboardReport(keyboardReport.Modifier, keyboardReport.Keys)
105+
return &keysDownState, rpcError
106+
}
107+
108+
return nil, fmt.Errorf("unknown HID RPC message type: %d", message.Type())
109+
}
110+
111+
func reportHidRPC(params any, session *Session) {
112+
if session == nil {
113+
logger.Warn().Msg("session is nil, skipping reportHidRPC")
114+
return
115+
}
116+
117+
if !session.hidRPCAvailable || session.HidChannel == nil {
118+
logger.Warn().Msg("HID RPC is not available, skipping reportHidRPC")
119+
return
120+
}
121+
122+
var (
123+
message []byte
124+
err error
125+
)
126+
switch params := params.(type) {
127+
case usbgadget.KeyboardState:
128+
message, err = hidrpc.NewKeyboardLedMessage(params).Marshal()
129+
case usbgadget.KeysDownState:
130+
message, err = hidrpc.NewKeydownStateMessage(params).Marshal()
131+
default:
132+
err = fmt.Errorf("unknown HID RPC message type: %T", params)
133+
}
134+
135+
if err != nil {
136+
logger.Warn().Err(err).Msg("failed to marshal HID RPC message")
137+
return
138+
}
139+
140+
if message == nil {
141+
logger.Warn().Msg("failed to marshal HID RPC message")
142+
return
143+
}
144+
145+
if err := session.HidChannel.Send(message); err != nil {
146+
logger.Warn().Err(err).Msg("failed to send HID RPC message")
147+
}
148+
}
149+
150+
func (s *Session) reportHidRPCKeyboardLedState(state usbgadget.KeyboardState) {
151+
if !s.hidRPCAvailable {
152+
writeJSONRPCEvent("keyboardLedState", state, s)
153+
}
154+
reportHidRPC(state, s)
155+
}
156+
157+
func (s *Session) reportHidRPCKeysDownState(state usbgadget.KeysDownState) {
158+
if !s.hidRPCAvailable {
159+
writeJSONRPCEvent("keysDownState", state, s)
160+
}
161+
reportHidRPC(state, s)
162+
}

internal/hidrpc/hidrpc.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package hidrpc
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/jetkvm/kvm/internal/usbgadget"
7+
)
8+
9+
// MessageType is the type of the HID RPC message
10+
type MessageType byte
11+
12+
const (
13+
TypeHandshake MessageType = 0x01
14+
TypeKeyboardReport MessageType = 0x02
15+
TypePointerReport MessageType = 0x03
16+
TypeWheelReport MessageType = 0x04
17+
TypeKeypressReport MessageType = 0x05
18+
TypeMouseReport MessageType = 0x06
19+
TypeKeyboardLedState MessageType = 0x32
20+
TypeKeydownState MessageType = 0x33
21+
)
22+
23+
const (
24+
Version byte = 0x01 // Version of the HID RPC protocol
25+
)
26+
27+
// GetQueueIndex returns the index of the queue to which the message should be enqueued.
28+
func GetQueueIndex(messageType MessageType) int {
29+
switch messageType {
30+
case TypeHandshake:
31+
return 0
32+
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardLedState, TypeKeydownState:
33+
return 1
34+
case TypePointerReport, TypeMouseReport, TypeWheelReport:
35+
return 2
36+
default:
37+
return 3
38+
}
39+
}
40+
41+
// Unmarshal unmarshals the HID RPC message from the data.
42+
func Unmarshal(data []byte, message *Message) error {
43+
l := len(data)
44+
if l < 1 {
45+
return fmt.Errorf("invalid data length: %d", l)
46+
}
47+
48+
message.t = MessageType(data[0])
49+
message.d = data[1:]
50+
return nil
51+
}
52+
53+
// Marshal marshals the HID RPC message to the data.
54+
func Marshal(message *Message) ([]byte, error) {
55+
if message.t == 0 {
56+
return nil, fmt.Errorf("invalid message type: %d", message.t)
57+
}
58+
59+
data := make([]byte, len(message.d)+1)
60+
data[0] = byte(message.t)
61+
copy(data[1:], message.d)
62+
63+
return data, nil
64+
}
65+
66+
// NewHandshakeMessage creates a new handshake message.
67+
func NewHandshakeMessage() *Message {
68+
return &Message{
69+
t: TypeHandshake,
70+
d: []byte{Version},
71+
}
72+
}
73+
74+
// NewKeyboardReportMessage creates a new keyboard report message.
75+
func NewKeyboardReportMessage(keys []byte, modifier uint8) *Message {
76+
return &Message{
77+
t: TypeKeyboardReport,
78+
d: append([]byte{modifier}, keys...),
79+
}
80+
}
81+
82+
// NewKeyboardLedMessage creates a new keyboard LED message.
83+
func NewKeyboardLedMessage(state usbgadget.KeyboardState) *Message {
84+
return &Message{
85+
t: TypeKeyboardLedState,
86+
d: []byte{state.Byte()},
87+
}
88+
}
89+
90+
// NewKeydownStateMessage creates a new keydown state message.
91+
func NewKeydownStateMessage(state usbgadget.KeysDownState) *Message {
92+
data := make([]byte, len(state.Keys)+1)
93+
data[0] = state.Modifier
94+
copy(data[1:], state.Keys)
95+
96+
return &Message{
97+
t: TypeKeydownState,
98+
d: data,
99+
}
100+
}

internal/hidrpc/message.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package hidrpc
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// Message ..
8+
type Message struct {
9+
t MessageType
10+
d []byte
11+
}
12+
13+
// Marshal marshals the message to a byte array.
14+
func (m *Message) Marshal() ([]byte, error) {
15+
return Marshal(m)
16+
}
17+
18+
func (m *Message) Type() MessageType {
19+
return m.t
20+
}
21+
22+
func (m *Message) String() string {
23+
switch m.t {
24+
case TypeHandshake:
25+
return "Handshake"
26+
case TypeKeypressReport:
27+
if len(m.d) < 2 {
28+
return fmt.Sprintf("KeypressReport{Malformed: %v}", m.d)
29+
}
30+
return fmt.Sprintf("KeypressReport{Key: %d, Press: %v}", m.d[0], m.d[1] == uint8(1))
31+
case TypeKeyboardReport:
32+
if len(m.d) < 2 {
33+
return fmt.Sprintf("KeyboardReport{Malformed: %v}", m.d)
34+
}
35+
return fmt.Sprintf("KeyboardReport{Modifier: %d, Keys: %v}", m.d[0], m.d[1:])
36+
case TypePointerReport:
37+
if len(m.d) < 9 {
38+
return fmt.Sprintf("PointerReport{Malformed: %v}", m.d)
39+
}
40+
return fmt.Sprintf("PointerReport{X: %d, Y: %d, Button: %d}", m.d[0:4], m.d[4:8], m.d[8])
41+
case TypeMouseReport:
42+
if len(m.d) < 3 {
43+
return fmt.Sprintf("MouseReport{Malformed: %v}", m.d)
44+
}
45+
return fmt.Sprintf("MouseReport{DX: %d, DY: %d, Button: %d}", m.d[0], m.d[1], m.d[2])
46+
default:
47+
return fmt.Sprintf("Unknown{Type: %d, Data: %v}", m.t, m.d)
48+
}
49+
}
50+
51+
// KeypressReport ..
52+
type KeypressReport struct {
53+
Key byte
54+
Press bool
55+
}
56+
57+
// KeypressReport returns the keypress report from the message.
58+
func (m *Message) KeypressReport() (KeypressReport, error) {
59+
if m.t != TypeKeypressReport {
60+
return KeypressReport{}, fmt.Errorf("invalid message type: %d", m.t)
61+
}
62+
63+
return KeypressReport{
64+
Key: m.d[0],
65+
Press: m.d[1] == uint8(1),
66+
}, nil
67+
}
68+
69+
// KeyboardReport ..
70+
type KeyboardReport struct {
71+
Modifier byte
72+
Keys []byte
73+
}
74+
75+
// KeyboardReport returns the keyboard report from the message.
76+
func (m *Message) KeyboardReport() (KeyboardReport, error) {
77+
if m.t != TypeKeyboardReport {
78+
return KeyboardReport{}, fmt.Errorf("invalid message type: %d", m.t)
79+
}
80+
81+
return KeyboardReport{
82+
Modifier: m.d[0],
83+
Keys: m.d[1:],
84+
}, nil
85+
}
86+
87+
// PointerReport ..
88+
type PointerReport struct {
89+
X int
90+
Y int
91+
Button uint8
92+
}
93+
94+
func toInt(b []byte) int {
95+
return int(b[0])<<24 + int(b[1])<<16 + int(b[2])<<8 + int(b[3])<<0
96+
}
97+
98+
// PointerReport returns the point report from the message.
99+
func (m *Message) PointerReport() (PointerReport, error) {
100+
if m.t != TypePointerReport {
101+
return PointerReport{}, fmt.Errorf("invalid message type: %d", m.t)
102+
}
103+
104+
if len(m.d) != 9 {
105+
return PointerReport{}, fmt.Errorf("invalid message length: %d", len(m.d))
106+
}
107+
108+
return PointerReport{
109+
X: toInt(m.d[0:4]),
110+
Y: toInt(m.d[4:8]),
111+
Button: uint8(m.d[8]),
112+
}, nil
113+
}
114+
115+
// MouseReport ..
116+
type MouseReport struct {
117+
DX int8
118+
DY int8
119+
Button uint8
120+
}
121+
122+
// MouseReport returns the mouse report from the message.
123+
func (m *Message) MouseReport() (MouseReport, error) {
124+
if m.t != TypeMouseReport {
125+
return MouseReport{}, fmt.Errorf("invalid message type: %d", m.t)
126+
}
127+
128+
return MouseReport{
129+
DX: int8(m.d[0]),
130+
DY: int8(m.d[1]),
131+
Button: uint8(m.d[2]),
132+
}, nil
133+
}

0 commit comments

Comments
 (0)