xK/xR/xR.go
Přemysl Eric Janouch 80af5c22d6
All checks were successful
Alpine 3.21 Success
Arch Linux AUR Success
OpenBSD 7.6 Success
Add an xC relay protocol analyzer
2025-05-15 14:14:53 +02:00

135 lines
2.9 KiB
Go

// 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))
}