diff options
author | Přemysl Eric Janouch <p@janouch.name> | 2022-08-08 04:39:20 +0200 |
---|---|---|
committer | Přemysl Eric Janouch <p@janouch.name> | 2022-09-05 14:26:00 +0200 |
commit | 1639235a48dbed75c2563c9a497b41c31a2a1bae (patch) | |
tree | 18193b72fa47e6bcac1358289ac9c36ed00c70ac /xP/xP.go | |
parent | 2160d037943ef0a3adbf4c6e30a91ee0f205c3f3 (diff) | |
download | xK-1639235a48dbed75c2563c9a497b41c31a2a1bae.tar.gz xK-1639235a48dbed75c2563c9a497b41c31a2a1bae.tar.xz xK-1639235a48dbed75c2563c9a497b41c31a2a1bae.zip |
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.
Diffstat (limited to 'xP/xP.go')
-rw-r--r-- | xP/xP.go | 186 |
1 files changed, 186 insertions, 0 deletions
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(`<!DOCTYPE html> +<html> +<head> + <title>xP</title> + <meta charset="utf-8" /> + <link rel="stylesheet" href="xP.css" /> +</head> +<body> + <script src="mithril.js"> + </script> + <script> + let proxy = '{{ . }}' + </script> + <script src="xP.js"> + </script> +</body> +</html>`)) + +func handleDefault(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + staticHandler.ServeHTTP(w, r) + return + } + + wsURI := fmt.Sprintf("ws://%s/ws", r.Host) + if err := page.Execute(w, wsURI); err != nil { + log.Println("Template execution failed: " + err.Error()) + } +} + +func main() { + if len(os.Args) != 3 { + log.Fatalf("usage: %s BIND CONNECT\n", os.Args[0]) + } + + addressBind, addressConnect = os.Args[1], os.Args[2] + + http.Handle("/ws", websocket.Handler(handleWebSocket)) + http.Handle("/", http.HandlerFunc(handleDefault)) + + s := &http.Server{ + Addr: addressBind, + ReadTimeout: 60 * time.Second, + WriteTimeout: 60 * time.Second, + MaxHeaderBytes: 32 << 10, + } + log.Fatal(s.ListenAndServe()) +} |