From 47cacaf16b075d97e8f08fb18f613ad389397370 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Thu, 2 Oct 2025 00:08:26 +0100 Subject: [PATCH] First commit with a basic mockserver for BeamNG stuff --- cmd/cmd.go | 29 ++++++++++++++++++++++++ cmd/replay.go | 35 ++++++++++++++++++++++++++++ go.mod | 13 +++++++++++ go.sum | 12 ++++++++++ main.go | 7 ++++++ mockserver/replay.go | 54 ++++++++++++++++++++++++++++++++++++++++++++ mockserver/server.go | 20 ++++++++++++++++ 7 files changed, 170 insertions(+) create mode 100644 cmd/cmd.go create mode 100644 cmd/replay.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 mockserver/replay.go create mode 100644 mockserver/server.go diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..d966fa4 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,29 @@ +// Package cmd will be the CLI of our BeamNG OG mockserver +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "bngmock", + Short: "CLI for a BeamNG OG mockserver", + Long: ``, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + rootCmd.PersistentFlags().StringP("address", "a", "127.0.0.1", "Address for the UDP server") + rootCmd.PersistentFlags().IntP("port", "p", 4444, "Port for the UDP server") +} diff --git a/cmd/replay.go b/cmd/replay.go new file mode 100644 index 0000000..96af5f9 --- /dev/null +++ b/cmd/replay.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "fmt" + + "github.com/ESilva15/BeamNGMockOg/mockserver" + + "github.com/spf13/cobra" +) + +func replayAction(cmd *cobra.Command, args []string) { + inputFile, _ := cmd.Flags().GetString("input") + address, _ := cmd.Flags().GetString("address") + port, _ := cmd.Flags().GetInt("port") + + if err := mockserver.Replay(address, port, inputFile); err != nil { + fmt.Printf("Something went wrong while playing the file: %v", err) + } +} + +// shelf +var replayCmd = &cobra.Command{ + Use: "replay", + Short: "replay -i ", + Long: `replay will store the data from the given UDP server to the + filepath given by -i`, + Args: nil, + Run: replayAction, +} + +func init() { + rootCmd.AddCommand(replayCmd) + + replayCmd.PersistentFlags().StringP("input", "i", "nofile.bin", "input file for serving") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9515a31 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/ESilva15/BeamNGMockOg + +go 1.23.2 + +require ( + github.com/ESilva15/gobngsdk v0.0.2 + github.com/spf13/cobra v1.10.1 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..96b5fc2 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/ESilva15/gobngsdk v0.0.2 h1:N6stNOE14Wg80BAZMFu/Qd9Qa/J0DmExCfdPoDf7lco= +github.com/ESilva15/gobngsdk v0.0.2/go.mod h1:cKLaZRgM0tGGXDvosaSjHnxeyC5Y6rg7zCLqEUJnOzw= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..ced3099 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/ESilva15/BeamNGMockOg/cmd" + +func main() { + cmd.Execute() +} diff --git a/mockserver/replay.go b/mockserver/replay.go new file mode 100644 index 0000000..039cd1b --- /dev/null +++ b/mockserver/replay.go @@ -0,0 +1,54 @@ +// Package mockserver is the core of this program and it will have the API +// to record binary data from the UDP server and then be able to mock it +package mockserver + +import ( + "bytes" + "encoding/binary" + "encoding/gob" + "log" + "os" + "time" + + sdk "github.com/ESilva15/gobngsdk" +) + +// Replay replays a given file in a UDP server : +func Replay(address string, port int, fp string) error { + bin, err := os.Open(fp) + if err != nil { + log.Fatal("error opening file:", err) + } + + addr, conn, err := openUDPServer(address, port) + if err != nil { + return err + } + + ticker := time.NewTicker(time.Second / 60) + defer ticker.Stop() + + dec := gob.NewDecoder(bin) + for { + var og sdk.Outgauge + if err := dec.Decode(&og); err != nil { + log.Fatal("failed to read more data:", err) + break + } + + buf := new(bytes.Buffer) + if err := binary.Write(buf, binary.LittleEndian, &og); err != nil { + log.Fatal("serialization failed:", err) + break + } + + if _, err := conn.WriteToUDP(buf.Bytes(), addr); err != nil { + log.Fatal("send failed:", err) + break + } + + <-ticker.C + } + + return conn.Close() +} diff --git a/mockserver/server.go b/mockserver/server.go new file mode 100644 index 0000000..6495653 --- /dev/null +++ b/mockserver/server.go @@ -0,0 +1,20 @@ +package mockserver + +import ( + "fmt" + "net" +) + +func openUDPServer(addr string, port int) (*net.UDPAddr, *net.UDPConn, error) { + udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port)) + if err != nil { + return nil, nil, err + } + + conn, err := net.ListenUDP("udp", nil) + if err != nil { + return nil, nil, err + } + + return udpAddr, conn, nil +}