linbpq/AEAPactor.c

1629 lines
37 KiB
C

/*
Copyright 2001-2022 John Wiseman G8BPQ
This file is part of LinBPQ/BPQ32.
LinBPQ/BPQ32 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
LinBPQ/BPQ32 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses
*/
//
// DLL to inteface AEA/Timewave TNC in Pactor Mode to BPQ32 switch
//
// Uses BPQ EXTERNAL interface
//
// Version 1.1.1.2 Sept 2010
// Fix CTEXT
// Turn round link when all acked (not all sent)
// Version 1.1.1.3 Sept 2010
// Turn round link if too long in receive
// Version 1.1.1.4 September 2010
// Fix Freq Display after Node reconfig
// Only use AutoConnect APPL for Pactor Connects
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_DEPRECATE
#include "time.h"
//#include <process.h>
//#include <time.h>
#include "CHeaders.h"
#include "tncinfo.h"
#include "bpq32.h"
static char ClassName[]="AEAPACTORSTATUS";
static char WindowTitle[] = "AEA Pactor";
static int RigControlRow = 165;
#define SOH 0x01 // CONTROL CODES
#define ETB 0x17
#define DLE 0x10
#define MaxStreams 26
#define PTOVER 0x1a // Pactor Turnround Char
char OverMsg[3] = " \x1a";
static RECT Rect;
static char status[8][8] = {"STANDBY", "PHASING", "CHGOVER", "IDLE", "TRAFFIC", "ERROR", "RQ", "XXXX"};
struct TNCINFO * CreateTTYInfo(int port, int speed);
BOOL SetupConnection(int);
BOOL CloseConnection(struct TNCINFO * conn);
static BOOL WriteCommBlock(struct TNCINFO * TNC);
BOOL DestroyTTYInfo(int port);
static void CheckRX(struct TNCINFO * TNC);
VOID AEAPoll(int Port);
static VOID ProcessDEDFrame(struct TNCINFO * TNC, UCHAR * rxbuff, int len);
static VOID ProcessTermModeResponse(struct TNCINFO * TNC);
static VOID DoTNCReinit(struct TNCINFO * TNC);
static VOID DoTermModeTimeout(struct TNCINFO * TNC);
VOID ProcessPacket(struct TNCINFO * TNC, UCHAR * rxbuffer, int Len);
VOID ProcessKPacket(struct TNCINFO * TNC, UCHAR * rxbuffer, int Len);
static VOID ProcessAEAPacket(struct TNCINFO * TNC, UCHAR * rxbuffer, size_t Len);
VOID ProcessKNormCommand(struct TNCINFO * TNC, UCHAR * rxbuffer);
static VOID ProcessHostFrame(struct TNCINFO * TNC, UCHAR * rxbuffer, size_t Len);
// Note that AEA host Mode uses SOH/ETB delimiters, with DLE stuffing
static VOID EncodeAndSend(struct TNCINFO * TNC, UCHAR * txbuffer, int Len);
static int DLEEncode(UCHAR * inbuff, UCHAR * outbuff, size_t len);
static int DLEDecode(UCHAR * inbuff, UCHAR * outbuff, size_t len);
int ProcessLine(char * buf, int Port)
{
UCHAR * ptr,* p_cmd;
char * p_ipad = 0;
char * p_port = 0;
unsigned short WINMORport = 0;
int BPQport;
int len=510;
struct TNCINFO * TNC;
char errbuf[256];
strcpy(errbuf, buf);
ptr = strtok(buf, " \t\n\r");
if(ptr == NULL) return (TRUE);
if(*ptr =='#') return (TRUE); // comment
if(*ptr ==';') return (TRUE); // comment
ptr = strtok(NULL, " \t\n\r");
if (_stricmp(buf, "ADDR") == 0) // Winmor Using BPQ32 COnfig
{
BPQport = Port;
p_ipad = ptr;
}
else
if (_stricmp(buf, "APPL") == 0) // Using BPQ32 COnfig
{
BPQport = Port;
p_cmd = ptr;
}
else
if (_stricmp(buf, "PORT") != 0) // Using Old Config
{
// New config without a PORT or APPL - this is a Config Command
strcpy(buf, errbuf);
strcat(buf, "\r");
BPQport = Port;
TNC = TNCInfo[BPQport] = malloc(sizeof(struct TNCINFO));
memset(TNC, 0, sizeof(struct TNCINFO));
TNC->InitScript = malloc(1000);
TNC->InitScript[0] = 0;
goto ConfigLine;
}
else
{
// Old Config from file
BPQport=0;
BPQport = atoi(ptr);
p_cmd = strtok(NULL, " \t\n\r");
if (Port && Port != BPQport)
{
// Want a particular port, and this isn't it
while(TRUE)
{
if (GetLine(buf) == 0)
return TRUE;
if (memcmp(buf, "****", 4) == 0)
return TRUE;
}
}
}
if(BPQport > 0 && BPQport < 33)
{
TNC = TNCInfo[BPQport] = malloc(sizeof(struct TNCINFO));
memset(TNC, 0, sizeof(struct TNCINFO));
TNC->InitScript = malloc(1000);
TNC->InitScript[0] = 0;
if (p_cmd != NULL)
{
if (p_cmd[0] != ';' && p_cmd[0] != '#')
TNC->ApplCmd=_strdup(p_cmd);
}
// Read Initialisation lines
while(TRUE)
{
if (GetLine(buf) == 0)
return TRUE;
ConfigLine:
strcpy(errbuf, buf);
if (memcmp(buf, "****", 4) == 0)
return TRUE;
ptr = strchr(buf, ';');
if (ptr)
{
*ptr++ = 13;
*ptr = 0;
}
if (_memicmp(buf, "WL2KREPORT", 10) == 0)
TNC->WL2K = DecodeWL2KReportLine(buf);
else
strcat (TNC->InitScript, buf);
}
}
return (TRUE);
}
static size_t ExtProc(int fn, int port, PDATAMESSAGE buff)
{
int txlen = 0;
PMSGWITHLEN buffptr;
struct TNCINFO * TNC = TNCInfo[port];
struct STREAMINFO * STREAM;
int Stream;
if (TNC == NULL || TNC->hDevice == 0)
return 0; // Port not open
switch (fn)
{
case 1: // poll
while (TNC->PortRecord->UI_Q) // Release anything accidentally put on UI_Q
{
buffptr = Q_REM(&TNC->PortRecord->UI_Q);
ReleaseBuffer(buffptr);
}
for (Stream = 0; Stream <= MaxStreams; Stream++)
{
if (TNC->Streams[Stream].ReportDISC)
{
TNC->Streams[Stream].ReportDISC = FALSE;
buff->PORT = Stream;
return -1;
}
}
CheckRX(TNC);
AEAPoll(port);
for (Stream = 0; Stream <= MaxStreams; Stream++)
{
if (TNC->Streams[Stream].PACTORtoBPQ_Q !=0)
{
size_t datalen;
buffptr=Q_REM(&TNC->Streams[Stream].PACTORtoBPQ_Q);
datalen = buffptr->Len;
buff->PORT = Stream; // Compatibility with Kam Driver
buff->PID = 0xf0;
memcpy(&buff->L2DATA, &buffptr->Data[0], datalen); // Data goes to + 7, but we have an extra byte
datalen += sizeof(void *) + 4;
PutLengthinBuffer(buff, (int)datalen);
ReleaseBuffer(buffptr);
return (1);
}
}
return 0;
case 2: // send
buffptr = GetBuff();
if (buffptr == 0) return (0); // No buffers, so ignore
// Find TNC Record
Stream = buff->PORT;
if (!TNC->TNCOK)
{
// Send Error Response
buffptr->Len = sprintf(buffptr->Data, "No Connection to PACTOR TNC\r");
C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
return 0;
}
txlen = GetLengthfromBuffer((PDATAMESSAGE)buff) - 8;
buffptr->Len = txlen;
memcpy(buffptr->Data, buff->L2DATA, txlen);
C_Q_ADD(&TNC->Streams[Stream].BPQtoPACTOR_Q, buffptr);
if(TNC->Streams[Stream].Connected)
{
TNC->Streams[Stream].FramesQueued++;
}
return (0);
case 3: // CHECK IF OK TO SEND. Also used to check if TNC is responding
Stream = (int)(size_t)buff;
STREAM = &TNC->Streams[Stream];
if (STREAM->FramesQueued > 4)
return 1 | TNC->HostMode << 8 | STREAM->Disconnecting << 15; // Busy
return TNC->HostMode << 8 | STREAM->Disconnecting << 15; // OK, but lock attach if disconnecting; // OK
case 4: // reinit
return (0);
case 5: // Close
EncodeAndSend(TNC, "OHON", 4); // HOST N
Sleep(50);
SaveWindowPos(port);
CloseCOMPort(TNC->hDevice);
return (0);
case 6: // Scan Control
return 0; // None Yet
}
return 0;
}
VOID * AEAExtInit(EXTPORTDATA * PortEntry)
{
char msg[500];
struct TNCINFO * TNC;
int port;
char * TempScript;
char * ptr;
//
// Will be called once for each Pactor Port
// The COM port number is in IOBASE
//
sprintf(msg,"AEA Pactor %s", PortEntry->PORTCONTROL.SerialPortName);
WritetoConsole(msg);
port=PortEntry->PORTCONTROL.PORTNUMBER;
ReadConfigFile(port, ProcessLine);
TNC = TNCInfo[port];
if (TNC == NULL)
{
// Not defined in Config file
sprintf(msg," ** Error - no info in BPQ32.cfg for this port\n");
WritetoConsole(msg);
return ExtProc;
}
TNC->Port = port;
TNC->Hardware = H_AEA;
TNC->TEXTMODE = FALSE;
PortEntry->MAXHOSTMODESESSIONS = 11; // Default
TNC->InitScript = _strupr(TNC->InitScript);
TNC->PortRecord = PortEntry;
if (PortEntry->PORTCONTROL.PORTCALL[0] == 0)
{
memcpy(TNC->NodeCall, MYNODECALL, 10);
}
else
{
ConvFromAX25(&PortEntry->PORTCONTROL.PORTCALL[0], TNC->NodeCall);
}
if (PortEntry->PORTCONTROL.PORTINTERLOCK && TNC->RXRadio == 0 && TNC->TXRadio == 0)
TNC->RXRadio = TNC->TXRadio = PortEntry->PORTCONTROL.PORTINTERLOCK;
PortEntry->PORTCONTROL.PROTOCOL = 10;
PortEntry->PORTCONTROL.PORTQUALITY = 0;
PortEntry->SCANCAPABILITIES = NONE; // No Scan Interlock
if (PortEntry->PORTCONTROL.PORTPACLEN == 0)
PortEntry->PORTCONTROL.PORTPACLEN = 100;
ptr=strchr(TNC->NodeCall, ' ');
if (ptr) *(ptr) = 0; // Null Terminate
// Set Essential Params and MYCALL
TempScript = malloc(4000);
strcpy(TempScript, "RESTART\r");
strcat(TempScript, "EXPERT ON\r");
strcat(TempScript, "PTHUFF 0\r");
strcat(TempScript, "PT200 ON\r");
strcat(TempScript, "WIDESHFT OFF\r");
strcat(TempScript, "ARQT 30\r");
strcat(TempScript, TNC->InitScript);
free(TNC->InitScript);
TNC->InitScript = TempScript;
// Others go on end so they can't be overriden
strcat(TNC->InitScript, "XMITOK ON\r");
strcat(TNC->InitScript, "XFLOW OFF\r");
strcat(TNC->InitScript, "RXREV OFF\r");
strcat(TNC->InitScript, "FLOW OFF\r");
strcat(TNC->InitScript, "AWLEN 8\r");
strcat(TNC->InitScript, "AUTOBAUD OFF\r");
strcat(TNC->InitScript, "8BITCONV ON\r");
strcat(TNC->InitScript, "ALFPAC OFF\r");
strcat(TNC->InitScript, "ALFDISP OFF\r");
strcat(TNC->InitScript, "ACRRTTY 0\r");
strcat(TNC->InitScript, "HPOLL ON\r");
strcat(TNC->InitScript, "EAS ON\r\r");
strcat(TNC->InitScript, "CONMODE TRANS\r");
strcat(TNC->InitScript, "PTOVER $1A\r\r");
// Set the ax.25 MYCALL
sprintf(msg, "MYCALL %s\r", TNC->NodeCall);
strcat(TNC->InitScript, msg);
#ifndef LINBPQ
CreatePactorWindow(TNC, ClassName, WindowTitle, RigControlRow, PacWndProc, 0, 0, ForcedClose);
CreateWindowEx(0, "STATIC", "Comms State", WS_CHILD | WS_VISIBLE, 10,6,120,20, TNC->hDlg, NULL, hInstance, NULL);
TNC->xIDC_COMMSSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,6,386,20, TNC->hDlg, NULL, hInstance, NULL);
CreateWindowEx(0, "STATIC", "TNC State", WS_CHILD | WS_VISIBLE, 10,28,106,20, TNC->hDlg, NULL, hInstance, NULL);
TNC->xIDC_TNCSTATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,28,520,20, TNC->hDlg, NULL, hInstance, NULL);
CreateWindowEx(0, "STATIC", "Mode", WS_CHILD | WS_VISIBLE, 10,50,80,20, TNC->hDlg, NULL, hInstance, NULL);
TNC->xIDC_MODE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,50,200,20, TNC->hDlg, NULL, hInstance, NULL);
CreateWindowEx(0, "STATIC", "Status", WS_CHILD | WS_VISIBLE, 10,72,110,20, TNC->hDlg, NULL, hInstance, NULL);
TNC->xIDC_STATE = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE, 116,72,144,20, TNC->hDlg, NULL, hInstance, NULL);
CreateWindowEx(0, "STATIC", "TX/RX State", WS_CHILD | WS_VISIBLE,10,94,80,20, TNC->hDlg, NULL, hInstance, NULL);
TNC->xIDC_TXRX = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,116,94,374,20 , TNC->hDlg, NULL, hInstance, NULL);
CreateWindowEx(0, "STATIC", "Buffers", WS_CHILD | WS_VISIBLE,10,116,80,20, TNC->hDlg, NULL, hInstance, NULL);
TNC->xIDC_BUFFERS = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,116,116,374,20 , TNC->hDlg, NULL, hInstance, NULL);
CreateWindowEx(0, "STATIC", "Traffic", WS_CHILD | WS_VISIBLE,10,138,80,20, TNC->hDlg, NULL, hInstance, NULL);
TNC->xIDC_TRAFFIC = CreateWindowEx(0, "STATIC", "RX 0 TX 0", WS_CHILD | WS_VISIBLE,116,138,374,20 , TNC->hDlg, NULL, hInstance, NULL);
TNC->ClientHeight = 213;
TNC->ClientWidth = 500;
TNC->hMenu = CreateMenu();
TNC->hWndMenu = CreatePopupMenu();
MoveWindows(TNC);
#endif
OpenCOMMPort(TNC, PortEntry->PORTCONTROL.SerialPortName, PortEntry->PORTCONTROL.BAUDRATE, FALSE);
WritetoConsole("\n");
return ExtProc;
}
static void CheckRX(struct TNCINFO * TNC)
{
int Length, Len;
// only try to read number of bytes in queue
if (TNC->RXLen == 500)
TNC->RXLen = 0;
Len = ReadCOMBlock(TNC->hDevice, &TNC->RXBuffer[TNC->RXLen], 500 - TNC->RXLen);
if (Len == 0)
return;
TNC->RXLen += Len;
Length = TNC->RXLen;
// AEA uses SOH ETX Framing
if (TNC->RXBuffer[0] != SOH)
{
// Char Mode Frame I think we need to see cmd: on end
// If we think we are in host mode, then to could be noise - just discard.
if (TNC->HostMode)
{
if (TNC->ReinitState == 2)
{
// Just entered host mode, so could be text command on front
unsigned char * ptr = strchr(TNC->RXBuffer, SOH);
if (ptr)
{
int textlen = ptr - TNC->RXBuffer;
TNC->RXLen -= textlen;
Length = TNC->RXLen;
Debugprintf("%s", TNC->RXBuffer);
Debugprintf("AEA Entered Host Mode");
TNC->ReinitState =0;
memmove(TNC->RXBuffer, ptr, Length);
goto hostFrame;
}
}
Debugprintf("AEA Bad Host Frame");
TNC->RXLen = 0; // Ready for next frame
return;
}
TNC->RXBuffer[TNC->RXLen] = 0;
// if (TNC->RXBuffer[TNC->RXLen-2] != ':')
if (strstr(TNC->RXBuffer, "cmd:") == 0)
{
if (strlen(TNC->RXBuffer) < TNC->RXLen)
TNC->RXLen = 0; // NULL in text mode message
return; // Wait for rest of frame
}
// Complete Char Mode Frame
TNC->RXLen = 0; // Ready for next frame
ProcessTermModeResponse(TNC);
return;
}
// Receiving a Host Mode frame
if (TNC->HostMode == 0) // If we are in Term Mode, discard it. Probably in recovery
{
TNC->RXLen = 0; // Ready for next frame
return;
}
hostFrame:
if (Length < 3) // Minimum Frame Sise
return;
if (TNC->RXBuffer[Length-1] != ETB)
return; // Wait till we have a full frame
if (TNC->RXBuffer[Length-2] == DLE && TNC->RXBuffer[Length-3] != DLE)
return; // ??? DLE ETB isn't end of frame, but DLE DLE ETB is
ProcessHostFrame(TNC, TNC->RXBuffer, Length); // Could have multiple packets in buffer
TNC->RXLen = 0; // Ready for next frame
return;
}
static VOID ProcessHostFrame(struct TNCINFO * TNC, UCHAR * rxbuffer, size_t Len)
{
UCHAR * FendPtr;
size_t NewLen;
// Split into Packets. By far the most likely is a single packet, so treat as special case
// Beware of DLE ETB and DLE DLE ETB!
FendPtr = memchr(&rxbuffer[1], ETB, Len-1);
FENDLoop:
if (*(FendPtr - 1) == DLE && *(FendPtr - 2) != DLE)
{
FendPtr++;
FendPtr = memchr(FendPtr, ETB, Len-1);
goto FENDLoop;
}
if (FendPtr == &rxbuffer[Len-1])
{
ProcessAEAPacket(TNC, &rxbuffer[1], Len - 2);
return;
}
// Process the first Packet in the buffer
NewLen = FendPtr - rxbuffer -1;
ProcessAEAPacket(TNC, &rxbuffer[1], NewLen);
// Loop Back
ProcessHostFrame(TNC, FendPtr+1, Len - NewLen - 2);
return;
}
static BOOL WriteCommBlock(struct TNCINFO * TNC)
{
WriteCOMBlock(TNC->hDevice, TNC->TXBuffer, TNC->TXLen);
TNC->Timeout = 50;
return TRUE;
}
VOID AEAPoll(int Port)
{
struct TNCINFO * TNC = TNCInfo[Port];
struct STREAMINFO * STREAM;
UCHAR * Poll = TNC->TXBuffer;
char Status[80];
int Stream;
if (TNC->Timeout)
{
TNC->Timeout--;
if (TNC->Timeout) // Still waiting
return;
// Timed Out
if (TNC->HostMode == 0)
{
DoTermModeTimeout(TNC);
return;
}
// Timed out in host mode - Clear any connection and reinit the TNC
TNC->TNCOK = FALSE;
TNC->HostMode = 0;
TNC->ReinitState = 0;
sprintf(Status,"%s Open but TNC not responding", TNC->PortRecord->PORTCONTROL.SerialPortName);
SetWindowText(TNC->xIDC_COMMSSTATE, Status);
for (Stream = 0; Stream <= MaxStreams; Stream++)
{
if (TNC->PortRecord->ATTACHEDSESSIONS[Stream]) // Connected
{
TNC->Streams[Stream].Connected = FALSE; // Back to Command Mode
TNC->Streams[Stream].ReportDISC = TRUE; // Tell Nod
}
}
}
// if we have just restarted or TNC appears to be in terminal mode, run Initialisation Sequence
if (!TNC->HostMode)
{
DoTNCReinit(TNC);
return;
}
if (TNC->CommandBusy)
goto Poll;
// We don't check for a new attach unless Timeout and CommandBusy are both zero, as we need to send a command.
// If Pactor Session has just been attached, set Pactor Call to the connecting user's callsign
if (TNC->PortRecord->ATTACHEDSESSIONS[0] && TNC->Streams[0].Attached == 0)
{
// New Attach
int calllen;
UCHAR TXMsg[1000];
int datalen;
char Msg[80];
TNC->Streams[0].Attached = TRUE;
TNC->Streams[0].TimeInRX = 0;
calllen = ConvFromAX25(TNC->PortRecord->ATTACHEDSESSIONS[0]->L4USER, TNC->Streams[0].MyCall);
TNC->Streams[0].MyCall[calllen] = 0;
datalen = sprintf(TXMsg, "OMf%s", TNC->Streams[0].MyCall);
EncodeAndSend(TNC, TXMsg, datalen);
TNC->InternalCmd = 'M';
TNC->CommandBusy = TRUE;
sprintf(Status, "In Use by %s", TNC->Streams[0].MyCall);
SetWindowText(TNC->xIDC_TNCSTATE, Status);
// Stop Scanning
sprintf(Msg, "%d SCANSTOP", TNC->Port);
Rig_Command( (TRANSPORTENTRY *) -1, Msg);
// Shouldn't we also take out of standby mode?? PN is Pactor Listen, for monitoring
return;
}
//If sending internal command list, send next element
if (TNC->CmdSet)
{
char * start, * end;
size_t len;
start = TNC->CmdSet;
if (*(start) == 0) // End of Script
{
free(TNC->CmdSave);
TNC->CmdSet = NULL;
}
else
{
end = strchr(start, 13);
len = ++end - start -1; // exclude cr
TNC->CmdSet = end;
EncodeAndSend(TNC, start, (int)len);
TNC->InternalCmd = 'X';
TNC->CommandBusy = TRUE;
return;
}
}
for (Stream = 0; Stream <= MaxStreams; Stream++)
{
STREAM = &TNC->Streams[Stream];
if (STREAM->Attached)
CheckForDetach(TNC, Stream, STREAM, TidyClose, ForcedClose, CloseComplete);
}
if (TNC->NeedPACTOR)
{
TNC->NeedPACTOR--;
if (TNC->NeedPACTOR == 0)
{
EncodeAndSend(TNC, "OPA", 3); // ??Return to packet mode??
TNC->CommandBusy = TRUE;
TNC->CmdSet = TNC->CmdSave = malloc(100);
sprintf(TNC->CmdSet, "OMf%s\rOPt\rOCETRANS\r", TNC->NodeCall); // Queue Back to Pactor Standby
TNC->InternalCmd = 'T';
TNC->TEXTMODE = FALSE;
TNC->IntCmdDelay--;
// Restart Scanning
sprintf(Status, "%d SCANSTART 15", TNC->Port);
Rig_Command( (TRANSPORTENTRY *) -1, Status);
return;
}
}
for (Stream = 0; Stream <= MaxStreams; Stream++)
{
if (TNC->TNCOK && TNC->Streams[Stream].BPQtoPACTOR_Q && TNC->DataBusy == FALSE)
{
int datalen;
UCHAR TXMsg[1000];
PMSGWITHLEN buffptr;
UCHAR * MsgPtr;
char Status[80];
if (TNC->Streams[Stream].Connected)
{
if (Stream == 0)
{
// Limit amount in TX
if (TNC->Streams[0].bytesTXed - TNC->Streams[0].BytesAcked > 200)
continue;
// If in IRS state for too long, force turnround
if (TNC->TXRXState == 'R')
{
if (TNC->Streams[0].TimeInRX++ > 15)
{
EncodeAndSend(TNC, "OAG", 3);
TNC->InternalCmd = 'A';
TNC->CommandBusy = TRUE;
}
else
goto GetStatus;
}
TNC->Streams[0].TimeInRX = 0;
}
buffptr=Q_REM(&TNC->Streams[Stream].BPQtoPACTOR_Q);
TNC->Streams[Stream].FramesQueued--;
datalen=buffptr->Len;
MsgPtr = buffptr->Data;
if (TNC->SwallowSignon)
{
TNC->SwallowSignon = FALSE;
if (strstr(MsgPtr, "Connected")) // Discard *** connected
{
ReleaseBuffer(buffptr);
return;
}
}
// If in CONV, and data looks binary, switch to TRAN
TNC->NeedTurnRound = TRUE; // Sending data, so need turnround at end
if (TNC->TEXTMODE)
{
int i;
UCHAR j;
for (i = 0; i < datalen; i++)
{
j = MsgPtr[i];
if (j > 127 || j == 26 || j < 10)
{
TNC->TEXTMODE = FALSE;
EncodeAndSend(TNC, "OCETRANS", 8);
Debugprintf("Switching to TRANS");
TNC->CommandBusy = TRUE;
TNC->InternalCmd = 'A';
break;
}
}
}
sprintf(TXMsg, "%c", Stream + ' ');
memcpy(&TXMsg[1], buffptr->Data, datalen);
EncodeAndSend(TNC, TXMsg, datalen + 1);
ReleaseBuffer(buffptr);
TNC->Streams[Stream].bytesTXed += datalen;
Debugprintf("Stream %d Sending %d, BytesTXED now %d", Stream, datalen, TNC->Streams[Stream].bytesTXed);
TNC->Timeout = 0;
TNC->DataBusy = TRUE;
if (Stream == 0)
ShowTraffic(TNC);
if (STREAM->Disconnecting && TNC->Streams[Stream].BPQtoPACTOR_Q == 0)
TidyClose(TNC, 0);
return;
}
else
{
buffptr=Q_REM(&TNC->Streams[Stream].BPQtoPACTOR_Q);
datalen=buffptr->Len;
MsgPtr = buffptr->Data;
// Command. Do some sanity checking and look for things to process locally
datalen--; // Exclude CR
MsgPtr[datalen] = 0; // Null Terminate
_strupr(MsgPtr);
if ((Stream == 0) && memcmp(MsgPtr, "RADIO ", 6) == 0)
{
sprintf(&MsgPtr[40], "%d %s", TNC->Port, &MsgPtr[6]);
if (Rig_Command(TNC->PortRecord->ATTACHEDSESSIONS[0]->L4CROSSLINK, &MsgPtr[40]))
{
ReleaseBuffer(buffptr);
}
else
{
buffptr->Len = sprintf(buffptr->Data, "%s", &MsgPtr[40]);
C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
}
return;
}
if (memcmp(MsgPtr, "MODE CONV", 9) == 0)
{
TNC->TEXTMODE = TRUE;
buffptr->Len = sprintf((UCHAR *)buffptr->Data,"AEA} Ok\r");
C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr);
EncodeAndSend(TNC, "OCECONV", 7);
TNC->CommandBusy = TRUE;
return;
}
if (memcmp(MsgPtr, "MODE TRANS", 9) == 0)
{
TNC->TEXTMODE = FALSE;
buffptr->Len = sprintf(buffptr->Data,"AEA} Ok\r");
C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr);
EncodeAndSend(TNC, "OCETRANS", 8);
TNC->CommandBusy = TRUE;
return;
}
if (MsgPtr[0] == 'C' && MsgPtr[1] == ' ' && datalen > 2) // Connect
{
memcpy(TNC->Streams[Stream].RemoteCall, &MsgPtr[2], 9);
TNC->Streams[Stream].Connecting = TRUE;
// If Stream 0, Convert C CALL to PACTOR CALL
if (Stream == 0)
{
datalen = sprintf(TXMsg, "%cPG%s", Stream + '@', TNC->Streams[0].RemoteCall);
sprintf(Status, "%s Connecting to %s",
TNC->Streams[0].MyCall, TNC->Streams[0].RemoteCall);
SetWindowText(TNC->xIDC_TNCSTATE, Status);
}
else
datalen = sprintf(TXMsg, "%cCO %s", Stream + '@', TNC->Streams[Stream].RemoteCall);
EncodeAndSend(TNC, TXMsg, datalen);
TNC->InternalCmd = 'C'; // So we dont send the reply to the user.
ReleaseBuffer(buffptr);
TNC->Streams[Stream].Connecting = TRUE;
return;
}
if (memcmp(MsgPtr, "DISCONNECT", datalen) == 0) // Disconnect
{
if (Stream == 0)
{
EncodeAndSend(TNC, "ODI", 3); // ??Return to packet mode??
TNC->NeedPACTOR = 50;
TNC->CommandBusy = TRUE;
}
else
{
sprintf(TXMsg, "%cDI", Stream + '@');
EncodeAndSend(TNC, TXMsg, 3);
TNC->CmdStream = Stream;
}
TNC->Streams[Stream].Connecting = FALSE;
TNC->Streams[Stream].ReportDISC = TRUE;
ReleaseBuffer(buffptr);
return;
}
// Other Command ??
if (Stream > 0)
datalen = sprintf(TXMsg, "C20%s", MsgPtr);
else
datalen = sprintf(TXMsg, "O%s", MsgPtr);
EncodeAndSend(TNC, TXMsg, datalen);
ReleaseBuffer(buffptr);
TNC->InternalCmd = 0;
TNC->CommandBusy = TRUE;
TNC->CmdStream = Stream;
}
}
}
GetStatus:
// Need to poll data and control channel (for responses to commands)
// Also check status if we have data buffered (for flow control)
if (TNC->TNCOK)
{
if (TNC->IntCmdDelay <= 0)
{
EncodeAndSend(TNC, "OOP", 3);
TNC->InternalCmd = 'S';
TNC->CommandBusy = TRUE;
TNC->IntCmdDelay = 9; // Every second
return;
}
else
TNC->IntCmdDelay--;
}
Poll:
// Nothing doing - send Poll (but not too often)
TNC->PollDelay++;
if (TNC->PollDelay < 3)
return;
TNC->PollDelay = 0;
EncodeAndSend(TNC, "OGG", 3); // Poll
return;
}
static VOID DoTNCReinit(struct TNCINFO * TNC)
{
UCHAR * Poll = TNC->TXBuffer;
if (TNC->ReinitState == 1) // Forcing back to Term
TNC->ReinitState = 0; // Got Response, so must be back in term mode
if (TNC->ReinitState == 0)
{
// Just Starting - Send a CR to see if in Terminal or Host Mode
char Status[80];
sprintf(Status,"%s Initialising TNC", TNC->PortRecord->PORTCONTROL.SerialPortName);
SetWindowText(TNC->xIDC_COMMSSTATE, Status);
Poll[0] = 13;
TNC->TXLen = 1;
TNC->RXLen = 0;
WriteCommBlock(TNC);
return;
}
if (TNC->ReinitState == 2) // In Term State, Sending Initialisation Commands
{
char * start, * end;
size_t len;
start = TNC->InitPtr;
if (*(start) == 0) // End of Script
{
// Put into Host Mode
Poll[0] = 0x11; // XON
Poll[1] = 0x18; // CAN
Poll[2] = 0x03; // COM
memcpy(&Poll[3], "HOST Y\r", 7);
TNC->TXLen = 10;
WriteCommBlock(TNC);
TNC->Timeout = 0;
TNC->CommandBusy = FALSE;
TNC->DataBusy = FALSE;
TNC->HostMode = TRUE; // Should now be in Host Mode
TNC->NeedPACTOR = 50; // Need to Send PACTOR command after 5 secs
return;
}
end = strchr(start, 13);
len = ++end - start;
TNC->InitPtr = end;
memcpy(Poll, start, len);
TNC->TXLen = (int)len;
WriteCommBlock(TNC);
return;
}
}
static VOID DoTermModeTimeout(struct TNCINFO * TNC)
{
UCHAR * Poll = TNC->TXBuffer;
if (TNC->ReinitState == 0)
{
//Checking if in Terminal Mode - Try to set back to Term Mode
TNC->ReinitState = 1;
EncodeAndSend(TNC, "OHON", 4); // HOST N
TNC->Timeout = 20;
return;
}
if (TNC->ReinitState == 1)
{
// Forcing back to Term Mode
TNC->ReinitState = 0;
DoTNCReinit(TNC); // See if worked
return;
}
}
static VOID ProcessTermModeResponse(struct TNCINFO * TNC)
{
UCHAR * Poll = TNC->TXBuffer;
Debugprintf("AEA Initstate %d Response %s", TNC->ReinitState, TNC->RXBuffer);
if (TNC->ReinitState == 0 || TNC->ReinitState == 1)
{
// Testing if in Term Mode. It is, so can now send Init Commands
TNC->InitPtr = TNC->InitScript;
TNC->ReinitState = 2;
DoTNCReinit(TNC); // Send First Command
return;
}
if (TNC->ReinitState == 2)
{
// Sending Init Commands
DoTNCReinit(TNC); // Send Next Command
return;
}
}
static VOID ProcessAEAPacket(struct TNCINFO * TNC, UCHAR * Msg, size_t Len)
{
PMSGWITHLEN buffptr;
char * Buffer = &Msg[1]; // Data portion of frame
char * Call;
char Status[80];
int Stream = 0;
int Opcode;
struct STREAMINFO * STREAM;
// Any valid frame is an ACK
TNC->TNCOK = TRUE;
TNC->Timeout = 0;
Len = DLEDecode(Msg, Msg, Len); // Remove KISS transparency
Stream = Msg[0] & 15;
Opcode = Msg[0] >> 4;
STREAM = &TNC->Streams[Stream];
if (Msg[0] == 'O' && Msg[1] == 'G' && Msg[2] == 'G')
{
if (Msg[3] == 0)
{
// OK Response
sprintf(Status,"%s TNC link OK", TNC->PortRecord->PORTCONTROL.SerialPortName);
SetWindowText(TNC->xIDC_COMMSSTATE, Status);
return;
}
}
if (Msg[0] == '/') // 2F
{
char * Eptr;
// Echoed Data
Len--;
Eptr = memchr(Buffer, 0x1c, Len);
if (Eptr)
Debugprintf("Echoed 1c followed by %x", *(++Eptr));
TNC->Streams[0].BytesAcked += (int)Len;
Debugprintf("Ack for %d, BytesAcked now %d", Len, TNC->Streams[0].BytesAcked);
ShowTraffic(TNC);
// If nothing more to send, turn round link
if ((TNC->Streams[0].BPQtoPACTOR_Q == 0) && TNC->NeedTurnRound &&
(TNC->Streams[0].BytesAcked >= TNC->Streams[0].bytesTXed)) // Nothing following and all acked
{
Debugprintf("AEA Sent = Acked - sending Turnround");
if (TNC->TEXTMODE == 0)
{
// In Trans - switch back
TNC->TEXTMODE = TRUE;
EncodeAndSend(TNC, "OCECONV", 7);
Debugprintf("Switching to CONV");
TNC->CommandBusy = TRUE;
TNC->InternalCmd = 'A';
TNC->NeedTRANS = TRUE;
}
TNC->NeedTurnRound = FALSE;
EncodeAndSend(TNC, OverMsg, 2);
TNC->Timeout = 0; // No response to data
}
return;
}
if (Opcode == 3)
{
// Received Data
// Pass to Appl
buffptr = GetBuff();
if (buffptr == NULL) return; // No buffers, so ignore
Len--; // Remove Header
buffptr->Len = Len; // Length
TNC->Streams[Stream].bytesRXed += (int)Len;
memcpy(&buffptr->Data[0], Buffer, Len);
C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
if (Stream == 0)
ShowTraffic(TNC);
return;
}
if (Opcode == 4)
{
// Link Status or Command Response
TNC->CommandBusy = FALSE;
if (TNC->InternalCmd)
{
// Process it
if (TNC->InternalCmd == 'S') // Status
{
// if (Msg[3] == 'P' && Msg[4] == 'G')
{
SetWindowText(TNC->xIDC_STATE, status[Msg[5] - 0x30]);
TNC->TXRXState = Msg[6];
if (Msg[6] == 'S')
SetWindowText(TNC->xIDC_TXRX, "Sender");
else
SetWindowText(TNC->xIDC_TXRX, "Receiver");
Msg[12] = 0;
SetWindowText(TNC->xIDC_MODE, Msg);
// Testing.. I think ZF returns buffers
EncodeAndSend(TNC, "OZF", 3);
TNC->InternalCmd = 'Z';
TNC->CommandBusy = TRUE;
}
return;
}
if (TNC->InternalCmd == 'Z') // Buffers?
{
Msg[Len] = 0;
SetWindowText(TNC->xIDC_BUFFERS, &Msg[3]);
return;
}
return;
}
// Reply to Manual command - Pass to Appl
Stream = TNC->CmdStream;
STREAM = &TNC->Streams[Stream];
buffptr = GetBuff();
if (buffptr == NULL) return; // No buffers, so ignore
Buffer[Len - 1] = 13;
Buffer[Len] = 0;
buffptr->Len = sprintf(&buffptr->Data[0],"AEA} %s", Buffer);
C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
return;
}
if (Opcode == 5)
{
// Link Messages (Connect, etc)
if (Stream == 15)
{
// SOH $5F X X $00 ETB data acknowledgement
if (Msg[1] == 'X' && Msg[2] == 'X' && Msg[3] == 0)
{
TNC->DataBusy = FALSE;
if (TNC->NeedTRANS) // Sent CTRL/Z in conv mode - switch back to trans
{
TNC->NeedTRANS = FALSE;
TNC->TEXTMODE = FALSE;
EncodeAndSend(TNC, "OCETRANS", 8);
Debugprintf("Switching to TRANS");
TNC->CommandBusy = TRUE;
TNC->InternalCmd = 'A';
}
else
EncodeAndSend(TNC, "OGG", 3); // Send another Poll
}
return;
}
if (strstr(Buffer, "DISCONNECTED") || strstr(Buffer, "Timeout"))
{
if ((TNC->Streams[Stream].Connecting | TNC->Streams[Stream].Connected) == 0)
{
// Not connected or Connecting. Probably response to going into Pactor Listen Mode
return;
}
if (STREAM->Connecting && STREAM->Disconnecting == FALSE)
{
// Connect Failed
buffptr = GetBuff();
if (buffptr == 0) return; // No buffers, so ignore
buffptr->Len = sprintf(&buffptr->Data[0], "*** Failure with %s\r", TNC->Streams[Stream].RemoteCall);
C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
TNC->Streams[Stream].Connecting = FALSE;
TNC->Streams[Stream].Connected = FALSE; // In case!
TNC->Streams[Stream].FramesQueued = 0;
return;
}
// Connected, or Disconnecting - Release Session
TNC->Streams[Stream].Connecting = FALSE;
TNC->Streams[Stream].Connected = FALSE; // Back to Command Mode
TNC->Streams[Stream].FramesQueued = 0;
if (STREAM->Disconnecting == FALSE)
STREAM->ReportDISC = TRUE; // Tell Node
STREAM->Disconnecting = FALSE;
// Need to reset Pactor Call in case it was changed
TNC->NeedPACTOR = 20;
if (Stream == 0)
{
// Claar any data from buffers
EncodeAndSend(TNC, "OTC", 3); // Clear buffers
TNC->NeedPACTOR = 20;
TNC->InternalCmd = 'T';
TNC->CommandBusy = TRUE;
}
return;
}
Call = strstr(Buffer, "NNECTED to");
if (Call)
{
Call+=11; // To Callsign
if (Stream == 0)
{
Buffer[Len-2] = 0;
}
TNC->Streams[Stream].bytesRXed = TNC->Streams[Stream].bytesTXed = TNC->Streams[Stream].BytesAcked = 0;
TNC->Streams[Stream].ConnectTime = time(NULL);
if (Stream == 0)
{
// Stop Scanner
char Msg[80];
sprintf(Msg, "%d SCANSTOP", TNC->Port);
Rig_Command( (TRANSPORTENTRY *) -1, Msg);
ShowTraffic(TNC);
}
if (TNC->PortRecord->ATTACHEDSESSIONS[Stream] == 0)
{
// Incoming Connect
ProcessIncommingConnect(TNC, Call, Stream, TRUE);
if (Stream == 0)
{
struct WL2KInfo * WL2K = TNC->WL2K;
char FreqAppl[10] = ""; // Frequecy-specific application
if (TNC->RIG && TNC->RIG != &TNC->DummyRig)
{
// If Scan Entry has a Appl, save it
if (TNC->RIG->FreqPtr && TNC->RIG->FreqPtr[0]->APPL[0])
strcpy(FreqAppl, &TNC->RIG->FreqPtr[0]->APPL[0]);
}
// We are going to Send something, so turn link round
EncodeAndSend(TNC, "OAG", 3);
TNC->InternalCmd = 'A';
TNC->CommandBusy = TRUE;
sprintf(Status, "%s Connected to %s Inbound", TNC->Streams[0].RemoteCall, TNC->NodeCall);
SetWindowText(TNC->xIDC_TNCSTATE, Status);
// If an autoconnect APPL is defined, send it
if (FreqAppl[0]) // Frequency specific APPL overrides TNC APPL
{
buffptr = GetBuff();
if (buffptr == 0) return; // No buffers, so ignore
buffptr->Len = sprintf(&buffptr->Data[0], "%s\r", FreqAppl);
C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
TNC->SwallowSignon = TRUE;
return;
}
if (TNC->ApplCmd)
{
buffptr = GetBuff();
if (buffptr == 0) return; // No buffers, so ignore
buffptr->Len = sprintf(&buffptr->Data[0], "%s\r", TNC->ApplCmd);
C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
TNC->SwallowSignon = TRUE;
return;
}
}
if (FULL_CTEXT && HFCTEXTLEN == 0)
{
char CTBuff[300];
int Len = CTEXTLEN, CTPaclen = 50;
int Next = 0;
CTBuff[0] = Stream + ' ';
while (Len > CTPaclen) // CTEXT Paclen
{
memcpy(&CTBuff[1], &CTEXTMSG[Next], CTPaclen);
EncodeAndSend(TNC, CTBuff, CTPaclen + 1);
Next += CTPaclen;
Len -= CTPaclen;
}
memcpy(&CTBuff[1], &CTEXTMSG[Next], Len);
EncodeAndSend(TNC, CTBuff, Len + 1);
}
return;
}
else
{
// Connect Complete
buffptr = GetBuff();
if (buffptr == 0) return; // No buffers, so ignore
buffptr->Len = sprintf(&buffptr->Data[0], "*** Connected to %s\r", Call);;
C_Q_ADD(&TNC->Streams[Stream].PACTORtoBPQ_Q, buffptr);
TNC->Streams[Stream].Connecting = FALSE;
TNC->Streams[Stream].Connected = TRUE; // Subsequent data to data channel
if (Stream == 0)
{
sprintf(Status, "%s Connected to %s Outbound", TNC->NodeCall, TNC->Streams[0].RemoteCall);
SetWindowText(TNC->xIDC_TNCSTATE, Status);
UpdateMH(TNC, Call, '+', 'O');
}
return;
}
}
}
}
int DLEDecode(UCHAR * inbuff, UCHAR * outbuff, size_t len)
{
unsigned int i, txptr = 0;
UCHAR c;
for (i=0; i<len; i++)
{
c = inbuff[i];
if (c == DLE)
c = inbuff[++i];
outbuff[txptr++] = c;
}
return txptr;
}
static VOID EncodeAndSend(struct TNCINFO * TNC, UCHAR * txbuffer, int Len)
{
// Send A Packet With DLE Encoding Encoding
TNC->TXLen = DLEEncode(txbuffer, TNC->TXBuffer, Len);
WriteCommBlock(TNC);
}
static int DLEEncode(UCHAR * inbuff, UCHAR * outbuff, size_t len)
{
unsigned int i, txptr = 0;
UCHAR c;
outbuff[0] = SOH;
txptr=1;
for (i=0; i<len; i++)
{
c = inbuff[i];
switch (c)
{
case DLE:
case SOH:
case ETB:
outbuff[txptr++] = DLE;
// Debugprintf("Escaping %x", c);
}
outbuff[txptr++] = c;
}
outbuff[txptr++] = ETB;
return txptr;
}
VOID TidyClose(struct TNCINFO * TNC, int Stream)
{
if (Stream == 0) // Pactor Stream
{
EncodeAndSend(TNC, "ODI", 3); // Disconnect
TNC->InternalCmd = 'P';
TNC->CommandBusy = TRUE;
}
else
{
UCHAR TXMsg[10];
sprintf(TXMsg, "%cDI", Stream + '@');
EncodeAndSend(TNC, TXMsg, 4); // Send twice - must force a disconnect
}
}
VOID ForcedClose(struct TNCINFO * TNC, int Stream)
{
TidyClose(TNC, Stream); // Send DI again - can't do much else
}
VOID CloseComplete(struct TNCINFO * TNC, int Stream)
{
TNC->NeedPACTOR = 50;
}