commit b8138c046261e2a714be32b19b399f67504f86f2 Author: Jim Colderwood Date: Fri May 24 23:07:40 2024 +0100 init diff --git a/README.md b/README.md new file mode 100644 index 0000000..5df55cb --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# USRPSERV +**This is a work in progress!** +Very early stages, currently parses between MMDVM and Allstar using USRP. diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..b013274 --- /dev/null +++ b/config/config.go @@ -0,0 +1,28 @@ +package config + +import ( + "encoding/json" + "os" +) + +type Config struct { + LocalAddr UDP `json:"localAddr"` + RemoteAddr UDP `json:"remoteAddr"` +} + +type UDP struct { + IP string `json:"ip"` + Port int `json:"port"` +} + +func ParseConfig(file string) (*Config, error) { + raw, err := os.ReadFile(file) + if err != nil { + return nil, err + } + var config Config + if err = json.Unmarshal(raw, &config); err != nil { + return nil, err + } + return &config, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2f671c8 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/usrpserv + +go 1.22.2 diff --git a/main.go b/main.go new file mode 100644 index 0000000..930b641 --- /dev/null +++ b/main.go @@ -0,0 +1,73 @@ +package main + +import ( + config2 "github.com/usrpserv/config" + "github.com/usrpserv/usrp" + "log" + "net" + "time" +) + +func main() { + config, err := config2.ParseConfig("config.json") + if err != nil { + log.Fatal(err) + } + client := usrp.NewClient(net.UDPAddr{Port: config.RemoteAddr.Port, IP: net.ParseIP(config.RemoteAddr.IP)}, + net.UDPAddr{Port: config.LocalAddr.Port, IP: net.ParseIP(config.LocalAddr.IP)}) + defer client.Sock.Close() + connectedIP := make(map[string]net.UDPAddr, 1) + listenAddr := net.UDPAddr{ + Port: 4810, + IP: net.ParseIP("0.0.0.0"), + } + netListener, err := net.ListenUDP("udp", &listenAddr) + if err != nil { + log.Fatal(err) + } + buffer := make([]byte, 500) + clientBuffer := make([]byte, 500) + frame := usrp.NewFrame() + go func() { + for { + n, clientAddr, err := netListener.ReadFromUDP(buffer) + if err != nil { + log.Println(err) + continue + } + _, ok := connectedIP[clientAddr.String()] + if !ok { + log.Println("Adding new client:", clientAddr.String()) + connectedIP[clientAddr.String()] = *clientAddr + } + if frame.Metadata == 0 { + if err = frame.Init(buffer, n); err != nil { + log.Println(err) + } + continue + } + if err = frame.Parse(buffer, n); err != nil { + log.Println(err) + } + if err = client.WriteFrame(buffer, n); err != nil { + log.Println(err) + } + if frame.PTT == 0 { + log.Println("END OF TRANSMISSION") + } + } + }() + c := func(buffer []byte, size int) { + for _, c := range connectedIP { + _, err := netListener.WriteToUDP(buffer[:size], &c) + if err != nil { + log.Println(err) + continue + } + } + } + client.Poll(clientBuffer, &c) + for { + time.Sleep(time.Millisecond * 250) + } +} diff --git a/usrp/client.go b/usrp/client.go new file mode 100644 index 0000000..b06ed5b --- /dev/null +++ b/usrp/client.go @@ -0,0 +1,36 @@ +package usrp + +import ( + "log" + "net" +) + +func NewClient(addr net.UDPAddr, laddr net.UDPAddr) Client { + var client Client + var err error + client.Sock, err = net.DialUDP("udp4", &laddr, &addr) + if err != nil { + log.Fatal(err) + } + return client +} + +func (c *Client) WriteFrame(buffer []byte, n int) error { + n, err := c.Sock.Write(buffer[:n]) + if err != nil || n == 0 { + return err + } + return nil +} + +func (c *Client) Poll(buffer []byte, callback *func([]byte, int)) { + go func() { + for { + n, err := c.Sock.Read(buffer) + if err != nil { + log.Println(err) + } + (*callback)(buffer, n) + } + }() +} diff --git a/usrp/proto.go b/usrp/proto.go new file mode 100644 index 0000000..c5704d9 --- /dev/null +++ b/usrp/proto.go @@ -0,0 +1,53 @@ +package usrp + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "time" +) + +func NewFrame() Frame { + var frame Frame + frame.Time = time.Now() + return frame +} + +func (f *Frame) Init(buff []byte, n int) error { + if err := IsValid(buff); err != nil { + return err + } + /* Can't initialize existing frame */ + if f.Metadata == 2 { + return errors.New("frame is already initialized") + } + f.Metadata = int8(buff[23]) + if f.Metadata == 2 { + fmt.Println("meta data") + callLen := buff[33] + f.Callsign = string(buff[46 : 46+callLen]) + } + f.Seq = binary.LittleEndian.Uint32(buff[4:9]) + return nil +} + +func IsValid(buff []byte) error { + if bytes.Compare(buff[0:4], []byte{'U', 'S', 'R', 'P'}) != 0 { + return errors.New("not a valid USRP packet") + } + return nil +} + +func (f *Frame) Parse(buff []byte, n int) error { + if err := IsValid(buff); err != nil { + return err + } + f.Seq = binary.BigEndian.Uint32(buff[4:9]) + if len(buff[32:n]) > 320 { + return errors.New("too many bytes") + } + f.PTT = int8(buff[15]) + copy(f.PCM.Data[:], buff[32:]) + return nil +} diff --git a/usrp/types.go b/usrp/types.go new file mode 100644 index 0000000..dbef0b3 --- /dev/null +++ b/usrp/types.go @@ -0,0 +1,28 @@ +package usrp + +import ( + "net" + "time" +) + +const ( + METADATA int8 = 2 +) + +type Client struct { + UDPAddr net.UDPAddr + Sock *net.UDPConn +} + +type Frame struct { + Seq uint32 + Metadata int8 + Callsign string + Time time.Time + PCM PCM + PTT int8 +} + +type PCM struct { + Data [320]byte +}