361 lines
8.9 KiB
Go
361 lines
8.9 KiB
Go
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}
|
|
}
|
|
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
|
|
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()
|
|
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 {
|
|
err := s.FromPactor.Enqueue(msg)
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}
|
|
_, err = conn.Write(msg)
|
|
if err != nil {
|
|
writeDebug(err.Error(), 0)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
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()
|
|
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
|
|
}
|
|
|
|
// 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 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
|
|
}
|
|
|
|
// Handle connection in a separate goroutine
|
|
go handleTCPDataConnection(conn)
|
|
}
|
|
}
|