// Copyright (c) 2025, Přemysl Eric Janouch // 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)) }