Compare commits

...

5 Commits

Author SHA1 Message Date
626c98ba94 Implement Frames 2026-05-20 11:17:17 +02:00
64f902ba9d Use fake server for engine connect and disconnect tests 2026-05-20 11:16:59 +02:00
36192cbd89 Hold off on table-driven-tests for now.
I feel like that gets a bit too abstract, too quickly.
2026-05-20 11:03:09 +02:00
040e5aefe7 Implement PacketEngine struct and test 2026-05-19 12:59:16 +02:00
c6bbc531fc Update license 2026-05-19 10:48:57 +02:00
7 changed files with 189 additions and 98 deletions

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2026 m Copyright (c) 2026 Michael Smith
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including associated documentation files (the "Software"), to deal in the Software without restriction, including

View File

@@ -1,33 +1,66 @@
package main package agwpe
import "encoding/binary" import (
"fmt"
"net"
"sync"
"time"
)
type Frame struct { type Config struct {
Port byte Timeout time.Duration
Reserved0 [3]byte
DataKind byte
Reserved1 byte
PID byte
Reserved2 byte
CallFrom [10]byte
CallTo [10]byte
DataLen uint32
User [4]byte
} }
func (f *Frame) Serialize() []byte { type Option func(*Config)
buf := make([]byte, 36)
buf[0] = f.Port type PacketEngine struct {
// Ignore reserved bytes 1 to 3 address string
buf[4] = f.DataKind cfg Config
// Ignore reserved byte 5 Ready bool
buf[6] = f.PID conn net.Conn
// Ignore reserved byte 7 mu sync.Mutex
copy(buf[8:18], f.CallFrom[:]) }
copy(buf[18:28], f.CallTo[:])
binary.LittleEndian.PutUint32(buf[28:32], f.DataLen) func WithTimeout(d time.Duration) Option {
copy(buf[32:36], f.User[:]) return func(c *Config) {
c.Timeout = d
return buf }
}
func NewPacketEngine(address string, opts ...Option) (*PacketEngine, error) {
defaultCfg := Config{
Timeout: 5 * time.Second,
}
for _, opt := range opts {
opt(&defaultCfg)
}
return &PacketEngine{
address: address,
cfg: defaultCfg,
Ready: false,
}, nil
}
func (pe *PacketEngine) Connect() error {
conn, err := net.DialTimeout("tcp", pe.address, pe.cfg.Timeout)
if err != nil {
return err
}
pe.conn = conn
fmt.Println("Connected to", pe.address)
return nil
}
func (pe *PacketEngine) Disconnect() error {
if pe.conn == nil {
return fmt.Errorf("Not connected!")
}
pe.conn.Close()
pe.conn = nil
fmt.Println("Disonnected from", pe.address)
return nil
} }

View File

@@ -1 +1,79 @@
agwpe_test.go package agwpe
import (
"net"
"testing"
"time"
)
// NOTE(m): Use more table-driven-tests? At the moment this feels less abstract.
func TestNewPacketEngine(t *testing.T) {
// Default configuration
engine, err := NewPacketEngine("localhost:1234")
if err != nil {
t.Errorf("Did not expect error: %v", err)
}
if engine.cfg.Timeout != 5*time.Second {
t.Errorf("Expected 5 second timeout, got %v", engine.cfg.Timeout)
}
if engine.Ready {
t.Error("Expected engine that is not ready")
}
// Custom timeout
engine, err = NewPacketEngine("localhost:1234", WithTimeout(30*time.Second))
if err != nil {
t.Errorf("Did not expect error: %v", err)
}
if engine.cfg.Timeout != 30*time.Second {
t.Errorf("Expected 30 second timeout, got %v", engine.cfg.Timeout)
}
}
func TestPacketEngine_Connect(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
serverAddr := listener.Addr().String()
// Successful connect
engine, _ := NewPacketEngine(serverAddr)
err = engine.Connect()
if err != nil {
t.Errorf("Did not expect error: %v", err)
}
// Invalid address
engine, _ = NewPacketEngine("invalid-address")
err = engine.Connect()
if err == nil {
t.Error("Expected an error")
}
}
func TestPacketEngine_Disconnect(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
serverAddr := listener.Addr().String()
engine, _ := NewPacketEngine(serverAddr)
engine.Connect()
// Disconnect connected engine
err = engine.Disconnect()
if err != nil {
t.Errorf("Did not expect error: %v", err)
}
// Call to Disconnect() on disconnected packet engine should return an error
err = engine.Disconnect()
if err == nil {
t.Error("Expected an error")
}
}

35
frame.go Normal file
View File

@@ -0,0 +1,35 @@
package agwpe
import (
"encoding/binary"
)
type FrameHeader struct {
Port byte
Reserved0 [3]byte
DataKind byte
Reserved1 byte
PID byte
Reserved2 byte
CallFrom [10]byte
CallTo [10]byte
DataLen uint32
User [4]byte
}
func (f *FrameHeader) Serialize() []byte {
buf := make([]byte, 36)
buf[0] = f.Port
// Ignore reserved bytes 1 to 3
buf[4] = f.DataKind
// Ignore reserved byte 5
buf[6] = f.PID
// Ignore reserved byte 7
copy(buf[8:18], f.CallFrom[:])
copy(buf[18:28], f.CallTo[:])
binary.LittleEndian.PutUint32(buf[28:32], f.DataLen)
copy(buf[32:36], f.User[:])
return buf
}

13
frame_test.go Normal file
View File

@@ -0,0 +1,13 @@
package agwpe
import (
"testing"
)
func TestNewFrameHeader(t *testing.T) {
}
func TestFrameHeader_Serialize(t *testing.T) {
}

2
go.mod
View File

@@ -1,3 +1,3 @@
module go-agwpe/agwpe module git.smith.eu/m/go-agwpe
go 1.24.5 go 1.24.5

68
main.go
View File

@@ -1,68 +0,0 @@
package main
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"time"
)
func main() {
address := "172.16.0.4:8000"
timeout := 5 * time.Second
send_buf := new(bytes.Buffer)
recv_buf := make([]byte, 1024)
conn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
fmt.Printf("Failed to connect: %v\n", err)
return
}
defer conn.Close()
fmt.Printf("Connected to %s\n", address)
rFrame := Frame{
Port: 0x01,
DataKind: 0x52,
}
err = binary.Write(send_buf, binary.LittleEndian, rFrame)
if err != nil {
fmt.Println("Binary write error:", err)
}
byteSlice := send_buf.Bytes()
_, err = conn.Write(byteSlice)
if err != nil {
fmt.Println("Error sending frame to server:", err)
return
}
n, err := conn.Read(recv_buf)
if err != nil {
fmt.Println("Error reading:", err)
return
}
fmt.Printf("Received %d bytes:\n", n)
reader := bytes.NewReader(recv_buf)
var f Frame
err = binary.Read(reader, binary.LittleEndian, &f)
if err != nil {
fmt.Println("Error decoding:", err)
return
}
fmt.Println("Response frame:")
fmt.Printf("Port: %d\n", f.Port)
fmt.Printf("DataKind: %c\n", f.DataKind)
fmt.Printf("PID: 0x%02X\n", f.PID)
fmt.Printf("CallFrom: %s\n", f.CallFrom)
fmt.Printf("CallTo: %s\n", f.CallTo)
fmt.Printf("DataLen: %d\n", f.DataLen)
major_verion := (uint16(recv_buf[37]) << 8) | uint16(recv_buf[36])
minor_verion := (uint16(recv_buf[41]) << 8) | uint16(recv_buf[40])
fmt.Printf("AGWPE version: %d.%d\n", major_verion, minor_verion)
}