Add an xC relay protocol analyzer
This commit is contained in:
134
xR/xR.go
Normal file
134
xR/xR.go
Normal file
@@ -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))
|
||||
}
|
||||
Reference in New Issue
Block a user