summaryrefslogtreecommitdiff
path: root/xP/xP.go
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2022-08-08 04:39:20 +0200
committerPřemysl Eric Janouch <p@janouch.name>2022-09-05 14:26:00 +0200
commit1639235a48dbed75c2563c9a497b41c31a2a1bae (patch)
tree18193b72fa47e6bcac1358289ac9c36ed00c70ac /xP/xP.go
parent2160d037943ef0a3adbf4c6e30a91ee0f205c3f3 (diff)
downloadxK-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.go186
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())
+}