diff options
Diffstat (limited to 'xR/xR.go')
-rw-r--r-- | xR/xR.go | 134 |
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)) +} |