From 1639235a48dbed75c2563c9a497b41c31a2a1bae Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch
Date: Mon, 8 Aug 2022 04:39:20 +0200 Subject: Start X11 and web frontends for xC For this, we needed a wire protocol. After surveying available options, it was decided to implement an XDR-like protocol code generator in portable AWK. It now has two backends, per each of: - xF, the X11 frontend, is in C, and is meant to be the primary user interface in the future. - xP, the web frontend, relies on a protocol proxy written in Go, and is meant for use on-the-go (no pun intended). They are very much work-in-progress proofs of concept right now, and the relay protocol is certain to change. --- xP/xP.go | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 xP/xP.go (limited to 'xP/xP.go') diff --git a/xP/xP.go b/xP/xP.go new file mode 100644 index 0000000..9b5df8f --- /dev/null +++ b/xP/xP.go @@ -0,0 +1,186 @@ +package main + +import ( + "context" + "encoding/binary" + "encoding/json" + "fmt" + "html/template" + "io" + "log" + "net" + "net/http" + "os" + "time" + + "golang.org/x/net/websocket" +) + +var ( + addressBind string + addressConnect string +) + +func clientToRelay( + ctx context.Context, ws *websocket.Conn, conn net.Conn) bool { + var j string + if err := websocket.Message.Receive(ws, &j); err != nil { + log.Println("Command receive failed: " + err.Error()) + return false + } + + log.Printf("?> %s\n", j) + + var m RelayCommandMessage + if err := json.Unmarshal([]byte(j), &m); err != nil { + log.Println("Command unmarshalling failed: " + err.Error()) + return false + } + + b, ok := m.AppendTo(make([]byte, 4)) + if !ok { + log.Println("Command serialization failed") + return false + } + binary.BigEndian.PutUint32(b[:4], uint32(len(b)-4)) + if _, err := conn.Write(b); err != nil { + log.Println("Command send failed: " + err.Error()) + return false + } + + log.Printf("-> %v\n", b) + return true +} + +func relayToClient( + ctx context.Context, ws *websocket.Conn, conn net.Conn) bool { + var length uint32 + if err := binary.Read(conn, binary.BigEndian, &length); err != nil { + log.Println("Event receive failed: " + err.Error()) + return false + } + b := make([]byte, length) + if _, err := io.ReadFull(conn, b); err != nil { + log.Println("Event receive failed: " + err.Error()) + return false + } + + log.Printf(" %v\n", b) + + var m RelayEventMessage + if after, ok := m.ConsumeFrom(b); !ok { + log.Println("Event deserialization failed") + return false + } else if len(after) != 0 { + log.Println("Event deserialization failed: trailing data") + return false + } + + j, err := json.Marshal(&m) + if err != nil { + log.Println("Event marshalling failed: " + err.Error()) + return false + } + if err := websocket.Message.Send(ws, string(j)); err != nil { + log.Println("Event send failed: " + err.Error()) + return false + } + + log.Printf("<- %s\n", j) + return true +} + +func errorToClient(ws *websocket.Conn, err error) bool { + j, err := json.Marshal(&RelayEventMessage{ + EventSeq: 0, + Data: RelayEventData{ + Interface: RelayEventDataError{ + Event: RelayEventError, + CommandSeq: 0, + Error: err.Error(), + }, + }, + }) + if err != nil { + log.Println("Event marshalling failed: " + err.Error()) + return false + } + if err := websocket.Message.Send(ws, string(j)); err != nil { + log.Println("Event send failed: " + err.Error()) + return false + } + return true +} + +func handleWebSocket(ws *websocket.Conn) { + conn, err := net.Dial("tcp", addressConnect) + if err != nil { + errorToClient(ws, err) + return + } + + // We don't need to intervene, so it's just two separate pipes so far. + ctx, cancel := context.WithCancel(ws.Request().Context()) + go func() { + for clientToRelay(ctx, ws, conn) { + } + cancel() + }() + go func() { + for relayToClient(ctx, ws, conn) { + } + cancel() + }() + <-ctx.Done() +} + +var staticHandler = http.FileServer(http.Dir(".")) + +var page = template.Must(template.New("/").Parse(` + +
+