initial commit

This commit is contained in:
2025-10-15 18:44:51 -05:00
commit 084284370c
9 changed files with 347 additions and 0 deletions

66
cmd/cli/main.go Normal file
View File

@@ -0,0 +1,66 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/jwetzell/showbridge-go"
"github.com/urfave/cli/v3"
)
func main() {
cmd := &cli.Command{
Name: "showbridge",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Value: "./config.json",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
configPath := c.String("config")
if configPath == "" {
return fmt.Errorf("config value cannot be empty")
}
config, err := readConfig(configPath)
if err != nil {
return err
}
fmt.Printf("%+v\n", config)
router, err := showbridge.NewRouter(ctx, config)
if err != nil {
return err
}
router.Run()
return nil
},
}
err := cmd.Run(context.Background(), os.Args)
if err != nil {
panic(err)
}
}
func readConfig(configPath string) (showbridge.Config, error) {
configBytes, err := os.ReadFile(configPath)
if err != nil {
return showbridge.Config{}, err
}
config := showbridge.Config{}
err = json.Unmarshal(configBytes, &config)
if err != nil {
return showbridge.Config{}, err
}
return config, nil
}

5
config.go Normal file
View File

@@ -0,0 +1,5 @@
package showbridge
type Config struct {
Protocols []ProtocolConfig `json:"protocols"`
}

11
config.json Normal file
View File

@@ -0,0 +1,11 @@
{
"protocols": [
{
"type": "tcp.client",
"params": {
"host": "127.0.0.1",
"port": 8000
}
}
]
}

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/jwetzell/showbridge-go
go 1.25.1
require github.com/urfave/cli/v3 v3.4.1

10
go.sum Normal file
View File

@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

44
protocol.go Normal file
View File

@@ -0,0 +1,44 @@
package showbridge
import (
"context"
"fmt"
"sync"
)
type Protocol interface {
Run(context.Context) error
}
type ProtocolConfig struct {
Type string `json:"type"`
Params map[string]any `json:"params"`
}
type ProtocolRegistration struct {
Type string `json:"type"`
New func(map[string]any) (Protocol, error)
}
func RegisterProtocol(proto ProtocolRegistration) {
if proto.Type == "" {
panic("protocol type is missing")
}
if proto.New == nil {
panic("missing ProtocolInfo.New")
}
protocolRegistryMu.Lock()
defer protocolRegistryMu.Unlock()
if _, ok := protocolRegistry[string(proto.Type)]; ok {
panic(fmt.Sprintf("protocol already registered: %s", proto.Type))
}
protocolRegistry[string(proto.Type)] = proto
}
var (
protocolRegistryMu sync.RWMutex
protocolRegistry = make(map[string]ProtocolRegistration)
)

43
router.go Normal file
View File

@@ -0,0 +1,43 @@
package showbridge
import (
"context"
"fmt"
)
type Router struct {
Context context.Context
ProtocolInstances []Protocol
}
func NewRouter(ctx context.Context, config Config) (*Router, error) {
router := Router{
Context: ctx,
ProtocolInstances: []Protocol{},
}
for _, protocolDecl := range config.Protocols {
protocolInfo, ok := protocolRegistry[protocolDecl.Type]
if !ok {
return nil, fmt.Errorf("problem loading protocol registration for protocol type: %s", protocolDecl.Type)
}
protocolInstance, err := protocolInfo.New(protocolDecl.Params)
if err != nil {
return nil, err
}
router.ProtocolInstances = append(router.ProtocolInstances, protocolInstance)
}
return &router, nil
}
func (r *Router) Run() {
for _, protocolInstance := range r.ProtocolInstances {
protocolInstance.Run(r.Context)
}
}

85
tcp-client.go Normal file
View File

@@ -0,0 +1,85 @@
package showbridge
import (
"context"
"fmt"
"net"
"time"
)
type TCPClient struct {
Host string
Port uint16
}
func init() {
RegisterProtocol(ProtocolRegistration{
Type: "tcp.client",
New: func(params map[string]any) (Protocol, error) {
host, ok := params["host"]
if !ok {
return nil, fmt.Errorf("tcp client requires a host parameter")
}
hostString, ok := host.(string)
if !ok {
return nil, fmt.Errorf("tcp client host must be uint16")
}
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("tcp client requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("tcp client port must be uint16")
}
return TCPClient{Host: hostString, Port: uint16(portNum)}, nil
},
})
}
func (ts TCPClient) Run(ctx context.Context) error {
for {
client, err := net.Dial("tcp", fmt.Sprintf(":%d", ts.Port))
if err != nil {
fmt.Println(err)
time.Sleep(time.Second * 2)
continue
}
buffer := make([]byte, 1024)
select {
case <-ctx.Done():
return nil
default:
READ:
for {
select {
case <-ctx.Done():
return nil
default:
byteCount, err := client.Read(buffer)
if err != nil {
fmt.Println("connection closed")
break READ
}
if byteCount > 0 {
fmt.Println(buffer[0:byteCount])
}
}
}
}
}
}

78
tcp-server.go Normal file
View File

@@ -0,0 +1,78 @@
package showbridge
import (
"context"
"fmt"
"net"
)
type TCPServer struct {
Port uint16
}
func init() {
RegisterProtocol(ProtocolRegistration{
Type: "tcp.server",
New: func(params map[string]any) (Protocol, error) {
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("tcp server requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("tcp server port must be uint16")
}
return TCPServer{Port: uint16(portNum)}, nil
},
})
}
func (ts TCPServer) HandleClient(ctx context.Context, client net.Conn) {
fmt.Printf("handling connection %s\n", client.RemoteAddr())
buffer := make([]byte, 1024)
for {
select {
case <-ctx.Done():
return
default:
byteCount, err := client.Read(buffer)
if err != nil {
if err.Error() == "EOF" {
fmt.Println("connection closed")
}
return
}
if byteCount > 0 {
fmt.Println(buffer[0:byteCount])
}
}
}
}
func (ts TCPServer) Run(ctx context.Context) error {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", ts.Port))
if err != nil {
return err
}
for {
select {
case <-ctx.Done():
return nil
default:
client, err := listener.Accept()
if err != nil {
return err
}
go ts.HandleClient(ctx, client)
}
}
}