first commit in new repo

This commit is contained in:
Torsten Harenberg
2025-02-02 19:19:16 +01:00
commit 255137345f
10 changed files with 2009 additions and 0 deletions

320
tcpserver.go Normal file
View File

@@ -0,0 +1,320 @@
package main
import (
"bufio"
"context"
"errors"
"fmt"
"github.com/TwiN/go-color"
"log"
"net"
"regexp"
"strings"
"time"
)
// Chunks splits a string into chunks of chunkSize length
// by topskip taken from https://stackoverflow.com/questions/18556693/slice-string-into-letters
func Chunks(s string, chunkSize int) []string {
if len(s) == 0 {
return nil
}
if chunkSize >= len(s) {
return []string{s}
}
var chunks []string = make([]string, 0, (len(s)-1)/chunkSize+1)
currentLen := 0
currentStart := 0
for i := range s {
if currentLen == chunkSize {
chunks = append(chunks, s[currentStart:i])
currentLen = 0
currentStart = i
}
currentLen++
}
chunks = append(chunks, s[currentStart:])
return chunks
}
// HandleConnection processes incoming connections
func handleTCPCmdConnection(conn net.Conn) {
defer func() {
s.Status &^= StatusTCPCmdActive
conn.Close()
}()
s.Status |= StatusTCPCmdActive
// Sending "IAMLIVE" every 60 seconds
alivectx, alivecancel := context.WithCancel(context.Background())
go func(conn net.Conn, ctx context.Context) {
for ctx.Err() == nil {
if s.VARAMode {
s.Command.Response.Enqueue("IAMALIVE")
}
time.Sleep(60 * time.Second)
}
}(conn, alivectx)
defer alivecancel()
/*
bufferctx, buffercancel := context.WithCancel(context.Background())
go func(conn net.Conn, ctx context.Context) {
for ctx.Err() == nil {
if s.VARAMode {
s.Command.Response.Enqueue(fmt.Sprintf("BUFFER %d", s.Data.Data.GetLen()))
}
time.Sleep(10 * time.Second)
}
}(conn, bufferctx)
defer buffercancel()
*/
s.Protocol <- fmt.Sprintf(color.InGreen("TCP Cmd Connection established with %s\n"), conn.RemoteAddr())
s.Status |= StatusTCPCmdActive
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func(ctx context.Context) {
for ctx.Err() == nil {
msg, err := s.Command.Response.DequeueOrWaitContext(ctx)
if err != nil {
s.Protocol <- fmt.Sprintf(color.InRed("End of Cmd Data Connection %s\n"), conn.RemoteAddr())
return
} else {
s.Protocol <- fmt.Sprintf(color.InCyan("Response: %s\n"), msg)
conn.Write([]byte(fmt.Sprintf("%s\r", msg)))
}
}
}(ctx)
reader := bufio.NewReader(conn)
for {
// Read incoming message
message, err := reader.ReadString('\r')
if err != nil {
//s.Protocol <- fmt.Sprintf(color.InRed("Connection closed by %s\n"), conn.RemoteAddr())
s.Status &^= StatusTCPCmdActive
alivecancel()
//buffercancel()
cancel()
conn.Close()
break
}
message = strings.TrimSpace(message)
re := regexp.MustCompile("[[:^ascii:]]")
message = re.ReplaceAllLiteralString(message, "") // prevent any non-ASCII from being sent
if message == "" { // do not send an empty message to the controller
continue
}
// Process the VARA TNC protocol commands
if s.VARAMode {
translatedmsg, predefanswer, err := processVARACommand(message)
if err != nil {
s.Protocol <- color.InRed(fmt.Sprintf("Error translating VARA command %s: %s\n", message, err.Error()))
log.Println(fmt.Sprintf("Error translating VARA command %s: %s\n", message, err.Error()))
s.Command.Response.Enqueue("WRONG")
} else {
if translatedmsg != "" {
s.Protocol <- fmt.Sprintf(color.InPurple("(V) Got: %s Sending: %s\n"), message, translatedmsg)
s.Command.Cmd.Enqueue(translatedmsg)
}
if predefanswer != "" {
s.Command.Response.Enqueue(predefanswer)
} else {
s.Command.Response.Enqueue("OK")
}
}
} else { // TCP Mode == passthrough
s.Protocol <- fmt.Sprintf(color.InPurple("(T) Sending: %s\n"), message)
s.Command.Cmd.Enqueue(message)
}
}
}
func handleTCPDataConnection(conn net.Conn) {
defer func() {
s.Status &^= StatusTCPDataActive
conn.Close()
}()
s.Protocol <- fmt.Sprintf(color.InGreen("TCP Data Connection established with %s\n"), conn.RemoteAddr())
s.Status |= StatusTCPDataActive
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
for {
msg, err := s.Data.Response.DequeueOrWaitContext(ctx, 1)
if err != nil {
s.Protocol <- fmt.Sprintf(color.InRed("End of TCP Data Connection %s\n"), conn.RemoteAddr())
return
} else {
//TODO: VARA
if !s.DaemonMode {
s.FromPactor.Enqueue(msg)
}
conn.Write(msg)
}
}
}()
reader := bufio.NewReader(conn)
for {
var temp []byte
buf := make([]byte, 1024)
n, err := reader.Read(buf)
if n > 0 {
temp = append(temp, buf[:n]...)
}
if err != nil {
s.Status &^= StatusTCPDataActive
cancel()
conn.Close()
return
}
//TODO: muss das nicht weg?
/* if temp == 0x04 {
break
}
*/
if !s.DaemonMode {
s.ToPactor.Enqueue(temp)
}
if n > 255 {
// we can dump at max 255 bytes into
for _, ck := range Chunks(string(temp), 255) {
s.Data.Data.Enqueue([]byte(ck))
}
} else {
s.Data.Data.Enqueue(temp)
}
}
}
// Process VARA commands and return appropriate WA8DED command and a pre-defined answer
func processVARACommand(command string) (string, string, error) {
switch {
case strings.HasPrefix(command, "CONNECT"):
re, err := regexp.MatchString(`CONNECT \w+ \w+`, command)
if err != nil || !re {
return "", "", errors.New("Error matching regex")
}
c := strings.Split(command, " ")
return fmt.Sprintf("C %s", c[2]), "", nil
case strings.HasPrefix(command, "DISCONNECT"):
return "D", "", nil
case strings.HasPrefix(command, "ABORT"):
return "DD", "", nil
// case strings.HasPrefix(command, "VERSION"):
// return "", nil
case strings.HasPrefix(command, "LISTEN"):
// Handle listen
if strings.HasSuffix(command, "ON") {
return "%L 1", "", nil
}
if strings.HasSuffix(command, "OFF") {
return "%L 0", "", nil
}
return "", "", errors.New("Neither ON nor OFF after LISTEN")
case strings.HasPrefix(command, "MYCALL"):
m := ""
// Handle MYCALL
s := strings.Split(command, " ")
if len(s) > 1 {
// send own callsign to PACTOR controller
m = s[1]
} else {
return "", "", errors.New("Invalid MYCALL command")
}
return fmt.Sprintf("%s%s", "I ", m), "", nil
case strings.HasPrefix(command, "COMPRESSION"):
// Handle COMPRESSION
return "", "", nil
case strings.HasPrefix(command, "BW"):
// Has no meaning for PACTOR
return "", "", nil
case strings.HasPrefix(command, "CHAT"):
// Has no meaning for PACTOR
return "", "", nil
case strings.HasPrefix(command, "WINLINK SESSION"):
// Has no meaning for PACTOR, just return "OK"
return "", "", nil
case strings.HasPrefix(command, "P2P SESSION"):
// Has no meaning for PACTOR, just return "OK"
return "", "", nil
case strings.HasPrefix(command, "CWID"):
// Has no meaning for PACTOR, just return "OK"
return "", "", nil
case strings.HasPrefix(command, "PUBLIC"):
// Has no meaning for PACTOR, just return "OK"
return "", "", nil
case strings.HasPrefix(command, "VERSION"):
return "", "VERSION 1.0.0", nil
default:
// Handle unrecognized commands
return "", "", errors.New("Unknown command")
}
}
func tcpCmdServer(Config *Userconfig) {
// Start listening for connections
listener, err := net.Listen("tcp", Config.ServerAddress)
if err != nil {
s.Protocol <- fmt.Sprintf(color.InWhiteOverRed("Error starting server: %v\n"), err)
log.Println(err)
return
}
defer listener.Close()
s.Protocol <- fmt.Sprintf("TCP Protocol Server listening on %s\n", Config.ServerAddress)
for {
// Accept incoming connection
conn, err := listener.Accept()
if err != nil {
s.Protocol <- fmt.Sprintf("Error accepting connection: %v\n", err)
continue
}
// Handle connection in a separate goroutine
go handleTCPCmdConnection(conn)
}
}
func tcpDataServer(Config *Userconfig) {
listener, err := net.Listen("tcp", Config.DataAddress)
if err != nil {
s.Protocol <- fmt.Sprintf(color.InWhiteOverRed("Error starting server: %v\n"), err)
log.Println(err)
return
}
defer listener.Close()
s.Protocol <- fmt.Sprintf("TCP Data Server listening on %s\n", Config.DataAddress)
for {
// Accept incoming connection
conn, err := listener.Accept()
if err != nil {
s.Protocol <- fmt.Sprintf("Error accepting connection: %v\n", err)
continue
}
// Handle connection in a separate goroutine
go handleTCPDataConnection(conn)
}
}