first commit in new repo
This commit is contained in:
320
tcpserver.go
Normal file
320
tcpserver.go
Normal 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)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user