aboutsummaryrefslogtreecommitdiff
path: root/xR/xR.go
diff options
context:
space:
mode:
Diffstat (limited to 'xR/xR.go')
-rw-r--r--xR/xR.go134
1 files changed, 134 insertions, 0 deletions
diff --git a/xR/xR.go b/xR/xR.go
new file mode 100644
index 0000000..a26832d
--- /dev/null
+++ b/xR/xR.go
@@ -0,0 +1,134 @@
+// Copyright (c) 2025, Přemysl Eric Janouch <p@janouch.name>
+// SPDX-License-Identifier: 0BSD
+
+package main
+
+import (
+ "encoding/binary"
+ "encoding/json"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "os"
+ "time"
+)
+
+var (
+ debug = flag.Bool("debug", false, "enable debug output")
+ version = flag.Bool("version", false, "show version and exit")
+ projectName = "xR"
+ projectVersion = "?"
+)
+
+func now() string {
+ return time.Now().UTC().Format(time.RFC3339Nano)
+}
+
+func relayReadFrame(r io.Reader) bool {
+ var length uint32
+ if err := binary.Read(
+ r, binary.BigEndian, &length); errors.Is(err, io.EOF) {
+ return false
+ } else if err != nil {
+ log.Fatalln("Event receive failed: " + err.Error())
+ }
+ b := make([]byte, length)
+ if _, err := io.ReadFull(r, b); errors.Is(err, io.EOF) {
+ return false
+ } else if err != nil {
+ log.Fatalln("Event receive failed: " + err.Error())
+ }
+
+ m := struct {
+ When string `json:"when"`
+ Binary []byte `json:"raw"`
+ RelayEventMessage
+ }{
+ When: now(),
+ Binary: b,
+ }
+
+ if after, ok := m.RelayEventMessage.ConsumeFrom(b); !ok {
+ log.Println("Event deserialization failed")
+ } else if len(after) != 0 {
+ log.Println("Event deserialization failed: trailing data")
+ return true
+ }
+
+ j, err := json.Marshal(m)
+ if err != nil {
+ log.Fatalln("Event marshalling failed: " + err.Error())
+ }
+ fmt.Printf("%s\n", j)
+ return true
+}
+
+func run(addressConnect string) {
+ conn, err := net.Dial("tcp", addressConnect)
+ if err != nil {
+ log.Println("Connection failed: " + err.Error())
+ return
+ }
+ defer conn.Close()
+
+ // We can only support this one protocol version
+ // that proto.go has been generated for.
+ m := RelayCommandMessage{CommandSeq: 0, Data: RelayCommandData{
+ Variant: &RelayCommandDataHello{Version: RelayVersion},
+ }}
+
+ b, ok := m.AppendTo(make([]byte, 4))
+ if !ok {
+ log.Fatalln("Command serialization failed")
+ }
+ binary.BigEndian.PutUint32(b[:4], uint32(len(b)-4))
+ if _, err := conn.Write(b); err != nil {
+ log.Fatalln("Command send failed: " + err.Error())
+ }
+
+ // You can differentiate the direction by the presence
+ // of .data.command or .data.event.
+ if *debug {
+ j, err := json.Marshal(struct {
+ When string `json:"when"`
+ Binary []byte `json:"raw"`
+ RelayCommandMessage
+ }{
+ When: now(),
+ Binary: b,
+ RelayCommandMessage: m,
+ })
+ if err != nil {
+ log.Fatalln("Command marshalling failed: " + err.Error())
+ }
+ fmt.Printf("%s\n", j)
+ }
+
+ for relayReadFrame(conn) {
+ }
+}
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(flag.CommandLine.Output(),
+ "Usage: %s [OPTION...] CONNECT\n\n", os.Args[0])
+ flag.PrintDefaults()
+ }
+ flag.Parse()
+ if *version {
+ fmt.Printf("%s %s (relay protocol version %d)\n",
+ projectName, projectVersion, RelayVersion)
+ return
+ }
+
+ if flag.NArg() != 1 {
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ // TODO(p): This program should be able to run as a filter as well.
+ run(flag.Arg(0))
+}