1. Fixed ByteFIFO (fifo.go): Changed Dequeue(), DequeueOrWait(), and DequeueOrWaitContext() to always copy() data into a new byte slice instead of just slicing the buffer. 2. Fixed TCP server logic (tcpserver.go): Changed the send goroutine to poll for data availability (without dequeueing) using GetLen(), then sleep briefly to batch incoming data, and finally dequeue all available data at once. This avoids the race condition where we were splitting dequeue operations and losing bytes in between.
387 lines
9.9 KiB
Go
387 lines
9.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/TwiN/go-color"
|
|
)
|
|
|
|
// 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}
|
|
}
|
|
chunks := 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
|
|
s.Command.Cmd.Enqueue("DD") // force stop all connetions if TCP connection is closing
|
|
err := conn.Close()
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}()
|
|
|
|
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)
|
|
_, err = conn.Write([]byte(fmt.Sprintf("%s\r", msg)))
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}
|
|
}
|
|
}(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()
|
|
err = conn.Close()
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
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
|
|
err := conn.Close()
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}()
|
|
|
|
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()
|
|
|
|
// Clear only the outgoing data buffer (Data.Data) to discard any unsent data from previous connection
|
|
// But keep Data.Response so we don't lose any data received from the modem that should be sent to the client
|
|
s.Data.Data.Clear()
|
|
|
|
go func() {
|
|
for ctx.Err() == nil {
|
|
writeDebug("TCP Data send goroutine: waiting for data...", 1)
|
|
|
|
// Wait until buffer has at least 1 byte without dequeueing
|
|
for {
|
|
if ctx.Err() != nil {
|
|
writeDebug("TCP Data send goroutine: context cancelled, exiting", 1)
|
|
return
|
|
}
|
|
|
|
bufLen := s.Data.Response.GetLen()
|
|
if bufLen > 0 {
|
|
writeDebug(fmt.Sprintf("TCP Data send goroutine: buffer has %d bytes", bufLen), 1)
|
|
// Sleep briefly to allow more data to arrive
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Dequeue ALL available data
|
|
bufLen = s.Data.Response.GetLen()
|
|
msg, err := s.Data.Response.Dequeue(bufLen)
|
|
if err == nil {
|
|
writeDebug(fmt.Sprintf("TCP Data send goroutine: sending %d bytes to client", len(msg)), 1)
|
|
//TODO: VARA
|
|
if !s.DaemonMode {
|
|
err := s.FromPactor.Enqueue(msg)
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}
|
|
_, err = conn.Write(msg)
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}
|
|
break
|
|
}
|
|
|
|
// Sleep a bit before checking again
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
}
|
|
}()
|
|
reader := bufio.NewReader(conn)
|
|
for {
|
|
buf := make([]byte, 1024)
|
|
n, err := reader.Read(buf)
|
|
temp := buf[:n]
|
|
if err != nil {
|
|
s.Status &^= StatusTCPDataActive
|
|
cancel()
|
|
err := conn.Close()
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
return
|
|
}
|
|
//TODO: muss das nicht weg?
|
|
/* if temp == 0x04 {
|
|
break
|
|
}
|
|
*/
|
|
if !s.DaemonMode {
|
|
err := s.ToPactor.Enqueue(temp)
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}
|
|
if n > 255 {
|
|
// we can dump at max 255 bytes into
|
|
for _, ck := range Chunks(string(temp), 255) {
|
|
err := s.Data.Data.Enqueue([]byte(ck))
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}
|
|
} else {
|
|
err := s.Data.Data.Enqueue(temp)
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 func() {
|
|
err := listener.Close()
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}()
|
|
|
|
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
|
|
}
|
|
|
|
// don't handle in goroutine as you normally would. There shouldn't be more than one connection.
|
|
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 func() {
|
|
err := listener.Close()
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}()
|
|
|
|
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
|
|
}
|
|
|
|
// don't handle in goroutine as you normally would. There shouldn't be more than one connection.
|
|
handleTCPDataConnection(conn)
|
|
}
|
|
}
|