1850 lines
43 KiB
C
1850 lines
43 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
|
|
*/
|
|
|
|
//
|
|
// Runs FLARQ-like protocol over UI Packets
|
|
//
|
|
|
|
#define _CRT_SECURE_NO_DEPRECATE
|
|
|
|
#include "CHeaders.h"
|
|
|
|
extern int (WINAPI FAR *GetModuleFileNameExPtr)();
|
|
extern int (WINAPI FAR *EnumProcessesPtr)();
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
#include "tncinfo.h"
|
|
|
|
#include "bpq32.h"
|
|
|
|
#define MAXARQ 10
|
|
|
|
#define DLE 0x10
|
|
#define SOH 1
|
|
#define STX 2
|
|
#define EOT 4
|
|
|
|
#define FEND 0xC0
|
|
#define FESC 0xDB
|
|
#define TFEND 0xDC
|
|
#define TFESC 0xDD
|
|
|
|
#define TIMESTAMP 352
|
|
|
|
#define CONTIMEOUT 1200
|
|
|
|
#define AGWHDDRLEN sizeof(struct AGWHEADER)
|
|
|
|
extern int (WINAPI FAR *GetModuleFileNameExPtr)();
|
|
|
|
//int ResetExtDriver(int num);
|
|
|
|
int SemHeldByAPI;
|
|
|
|
static int ProcessReceivedData(int bpqport);
|
|
static int ProcessLine(char * buf, int Port);
|
|
static VOID ProcessFLDigiPacket(struct TNCINFO * TNC, char * Message, int Len);
|
|
VOID ProcessFLDigiKISSPacket(struct TNCINFO * TNC, char * Message, int Len);
|
|
struct TNCINFO * GetSessionKey(char * key, struct TNCINFO * TNC);
|
|
static VOID SendARQData(struct TNCINFO * TNC, PMSGWITHLEN Buffer, int Stream);
|
|
VOID SendRPBeacon(struct TNCINFO * TNC);
|
|
unsigned int CalcCRC(UCHAR * ptr, int Len);
|
|
static VOID ARQTimer(struct TNCINFO * TNC);
|
|
VOID QueueAndSend(struct TNCINFO * TNC, struct ARQINFO * ARQ, SOCKET sock, char * Msg, int MsgLen);
|
|
VOID SaveAndSend(struct TNCINFO * TNC, struct ARQINFO * ARQ, SOCKET sock, char * Msg, int MsgLen);
|
|
static VOID ProcessARQStatus(struct TNCINFO * TNC, int Stream, struct ARQINFO * ARQ, char *Input);
|
|
VOID CheckFLDigiData(struct TNCINFO * TNC);
|
|
static VOID SendPacket(struct TNCINFO * TNC, struct STREAMINFO * STREAM, UCHAR * Msg, int MsgLen);
|
|
int KissEncode(UCHAR * inbuff, UCHAR * outbuff, int len);
|
|
VOID Send_AX_Datagram(PDIGIMESSAGE Block, DWORD Len, UCHAR Port);
|
|
int DoScanLine(struct TNCINFO * TNC, char * Buff, int Len);
|
|
VOID ProcessARQPacket(struct PORTCONTROL * PORT, MESSAGE * Buffer);
|
|
char * strlop(char * buf, char delim);
|
|
|
|
extern UCHAR BPQDirectory[];
|
|
extern char MYALIASLOPPED[10];
|
|
|
|
|
|
#define MAXMPSKPORTS 16
|
|
|
|
static BOOL GotMsg;
|
|
|
|
static HANDLE STDOUT=0;
|
|
|
|
extern int Blocksizes[10];
|
|
|
|
static char WindowTitle[] = "UIARQ";
|
|
static char ClassName[] = "UIARQSTATUS";
|
|
static int RigControlRow = 165;
|
|
|
|
|
|
|
|
static int ExtProc(int fn, int port,unsigned char * buff)
|
|
{
|
|
PMSGWITHLEN buffptr;
|
|
unsigned int txlen=0;
|
|
struct TNCINFO * TNC = TNCInfo[port];
|
|
int Stream = 0;
|
|
struct STREAMINFO * STREAM;
|
|
|
|
if (TNC == NULL)
|
|
return 0; // Port not defined
|
|
|
|
// Look for attach on any call
|
|
|
|
for (Stream = 0; Stream <= MAXARQ; Stream++)
|
|
{
|
|
STREAM = &TNC->Streams[Stream];
|
|
|
|
if (TNC->PortRecord->ATTACHEDSESSIONS[Stream] && TNC->Streams[Stream].Attached == 0)
|
|
{
|
|
// New Attach
|
|
|
|
int calllen;
|
|
STREAM->Attached = TRUE;
|
|
|
|
calllen = ConvFromAX25(TNC->PortRecord->ATTACHEDSESSIONS[Stream]->L4USER, STREAM->MyCall);
|
|
STREAM->MyCall[calllen] = 0;
|
|
STREAM->FramesOutstanding = 0;
|
|
|
|
SetWindowText(STREAM->xIDC_STATUS, "Attached");
|
|
SetWindowText(STREAM->xIDC_MYCALL, STREAM->MyCall);
|
|
|
|
}
|
|
}
|
|
|
|
switch (fn)
|
|
{
|
|
case 7:
|
|
|
|
// 100 mS Timer.
|
|
|
|
for (Stream = 0; Stream <= MAXARQ; Stream++)
|
|
{
|
|
STREAM = &TNC->Streams[Stream];
|
|
|
|
if (STREAM->NeedDisc)
|
|
{
|
|
STREAM->NeedDisc--;
|
|
|
|
if (STREAM->NeedDisc == 0)
|
|
{
|
|
// Send the DISCONNECT
|
|
|
|
TidyClose(TNC, Stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
ARQTimer(TNC);
|
|
|
|
return 0;
|
|
|
|
case 1: // poll
|
|
|
|
// See if any frames for this port
|
|
|
|
for (Stream = 0; Stream <= MAXARQ; Stream++)
|
|
{
|
|
STREAM = &TNC->Streams[Stream];
|
|
|
|
if (STREAM->Attached)
|
|
CheckForDetach(TNC, Stream, STREAM, TidyClose, ForcedClose, CloseComplete);
|
|
|
|
if (STREAM->ReportDISC)
|
|
{
|
|
STREAM->ReportDISC = FALSE;
|
|
buff[4] = Stream;
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (STREAM->PACTORtoBPQ_Q == 0)
|
|
{
|
|
if (STREAM->DiscWhenAllSent)
|
|
{
|
|
STREAM->DiscWhenAllSent--;
|
|
if (STREAM->DiscWhenAllSent == 0)
|
|
STREAM->ReportDISC = TRUE; // Dont want to leave session attached. Causes too much confusion
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int datalen;
|
|
|
|
buffptr=Q_REM(&STREAM->PACTORtoBPQ_Q);
|
|
|
|
datalen=buffptr->Len;
|
|
|
|
buff[4] = Stream;
|
|
buff[7] = 0xf0;
|
|
memcpy(&buff[8],buffptr->Data,datalen); // Data goes to +7, but we have an extra byte
|
|
datalen += (MSGHDDRLEN + 1);
|
|
|
|
PutLengthinBuffer((PDATAMESSAGE)buff, datalen);
|
|
|
|
ReleaseBuffer(buffptr);
|
|
|
|
return (1);
|
|
}
|
|
}
|
|
|
|
if (TNC->PortRecord->UI_Q)
|
|
{
|
|
struct _MESSAGE * buffptr;
|
|
int SendLen;
|
|
char Reply[256];
|
|
int UILen;
|
|
char * UIMsg;
|
|
|
|
buffptr = Q_REM(&TNC->PortRecord->UI_Q);
|
|
|
|
UILen = buffptr->LENGTH;
|
|
UILen -= 23;
|
|
UIMsg = buffptr->L2DATA;
|
|
|
|
UIMsg[UILen] = 0;
|
|
|
|
if (UILen < 129 && STREAM->Attached == FALSE) // Be sensible!
|
|
{
|
|
// >00uG8BPQ:72 TestA
|
|
SendLen = sprintf(Reply, "u%s:72 %s", TNC->NodeCall, UIMsg);
|
|
SendPacket(TNC, STREAM, Reply, SendLen);
|
|
}
|
|
ReleaseBuffer(buffptr);
|
|
}
|
|
|
|
return (0);
|
|
|
|
case 2: // send
|
|
|
|
|
|
Stream = buff[4];
|
|
|
|
STREAM = &TNC->Streams[Stream];
|
|
|
|
// txlen=(buff[6]<<8) + buff[5] - 8;
|
|
|
|
txlen = GetLengthfromBuffer((PDATAMESSAGE)buff) - (MSGHDDRLEN + 1); // 1 as no PID;
|
|
|
|
if (STREAM->Connected)
|
|
{
|
|
buffptr = GetBuff();
|
|
|
|
if (buffptr == 0) return (0); // No buffers, so ignore
|
|
|
|
buffptr->Len = txlen;
|
|
memcpy(buffptr->Data, &buff[8], txlen);
|
|
|
|
C_Q_ADD(&TNC->Streams[Stream].BPQtoPACTOR_Q, buffptr);
|
|
|
|
return (0);
|
|
}
|
|
else
|
|
{
|
|
buff[8 + txlen] = 0;
|
|
_strupr(&buff[8]);
|
|
|
|
if (_memicmp(&buff[8], "D\r", 2) == 0)
|
|
{
|
|
if (STREAM->Connected)
|
|
TidyClose(TNC, buff[4]);
|
|
|
|
STREAM->ReportDISC = TRUE; // Tell Node
|
|
return 0;
|
|
}
|
|
|
|
// See if a Connect Command.
|
|
|
|
if (toupper(buff[8]) == 'C' && buff[9] == ' ' && txlen > 2) // Connect
|
|
{
|
|
char * ptr;
|
|
char * context;
|
|
struct ARQINFO * ARQ = STREAM->ARQInfo;
|
|
int SendLen;
|
|
char Reply[80];
|
|
|
|
_strupr(&buff[8]);
|
|
buff[8 + txlen] = 0;
|
|
|
|
memset(ARQ, 0, sizeof(struct ARQINFO)); // Reset ARQ State
|
|
ARQ->TXSeq = ARQ->TXLastACK = 63; // Last Sent
|
|
ARQ->RXHighest = ARQ->RXNoGaps = 63; // Last Received
|
|
ARQ->OurStream = Stream + 64;
|
|
ARQ->FarStream = 64; // Not yet defined
|
|
|
|
memset(STREAM->RemoteCall, 0, 10);
|
|
|
|
ptr = strtok_s(&buff[10], " ,\r", &context);
|
|
strcpy(STREAM->RemoteCall, ptr);
|
|
|
|
//<SOH>00cG8BPQ:1025 G8BPQ:24 0 7 T60R5W10FA36<EOT>
|
|
|
|
SendLen = sprintf(Reply, "c%s:42 %s:24 %c 7 T60R5W10",
|
|
STREAM->MyCall, STREAM->RemoteCall, ARQ->OurStream);
|
|
|
|
ARQ->ARQState = ARQ_ACTIVE;
|
|
|
|
ARQ->ARQTimerState = ARQ_CONNECTING;
|
|
SaveAndSend(TNC, ARQ, TNC->TCPDataSock, Reply, SendLen);
|
|
|
|
SetWindowText(STREAM->xIDC_STATUS, "Connecting");
|
|
SetWindowText(STREAM->xIDC_MYCALL, STREAM->MyCall);
|
|
SetWindowText(STREAM->xIDC_DESTCALL, STREAM->RemoteCall);
|
|
SetWindowText(STREAM->xIDC_DIRN, "Out");
|
|
|
|
STREAM->Connecting = TRUE;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
return (0);
|
|
|
|
case 3:
|
|
|
|
Stream = (int)buff;
|
|
|
|
STREAM = &TNC->Streams[Stream];
|
|
{
|
|
// Busy if TX Window reached
|
|
|
|
struct ARQINFO * ARQ = STREAM->ARQInfo;
|
|
int Outstanding;
|
|
|
|
Outstanding = ARQ->TXSeq - ARQ->TXLastACK;
|
|
|
|
if (Outstanding < 0)
|
|
Outstanding += 64;
|
|
|
|
TNC->PortRecord->FramesQueued = Outstanding + C_Q_COUNT(&STREAM->BPQtoPACTOR_Q); // Save for Appl Level Queued Frames
|
|
|
|
if (Outstanding > ARQ->TXWindow)
|
|
return (1 | 1 << 8 | STREAM->Disconnecting << 15); // 3rd Nibble is frames unacked
|
|
else
|
|
return 1 << 8 | STREAM->Disconnecting << 15;
|
|
|
|
}
|
|
return 1 << 8 | STREAM->Disconnecting << 15; // OK, but lock attach if disconnecting
|
|
|
|
case 4: // reinit
|
|
|
|
return (0);
|
|
|
|
case 5: // Close
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
VOID UIHook(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL, UCHAR MSGFLAG)
|
|
{
|
|
PORT = GetPortTableEntryFromPortNum(PORT->HookPort->PORTNUMBER);
|
|
if (PORT)
|
|
ProcessARQPacket(PORT, Buffer);
|
|
}
|
|
static VOID UpdateStatsLine(struct TNCINFO * TNC, struct STREAMINFO * STREAM)
|
|
{
|
|
char Count[16];
|
|
|
|
sprintf(Count, "%d", STREAM->BytesRXed);
|
|
SetWindowText(STREAM->xIDC_RXED, Count);
|
|
|
|
sprintf(Count, "%d", STREAM->BytesTXed);
|
|
SetWindowText(STREAM->xIDC_SEND, Count);
|
|
|
|
sprintf(Count, "%d", STREAM->BytesResent);
|
|
SetWindowText(STREAM->xIDC_RESENT, Count);
|
|
|
|
sprintf(Count, "%d", STREAM->BytesAcked);
|
|
SetWindowText(STREAM->xIDC_ACKED, Count);
|
|
}
|
|
|
|
static int WebProc(struct TNCINFO * TNC, char * Buff, BOOL LOCAL)
|
|
{
|
|
int Len = sprintf(Buff, "<html><meta http-equiv=expires content=0><meta http-equiv=refresh content=15>"
|
|
"<script type=\"text/javascript\">\r\n"
|
|
"function ScrollOutput()\r\n"
|
|
"{var textarea = document.getElementById('textarea');"
|
|
"textarea.scrollTop = textarea.scrollHeight;}</script>"
|
|
"</head><title>FLDigi Status</title></head><body id=Text onload=\"ScrollOutput()\">"
|
|
"<h2>FLDIGI Status</h2>");
|
|
|
|
Len += sprintf(&Buff[Len], "<table style=\"text-align: left; width: 500px; font-family: monospace; align=center \" border=1 cellpadding=2 cellspacing=2>");
|
|
|
|
Len += sprintf(&Buff[Len], "<tr><td width=110px>Comms State</td><td>%s</td></tr>", TNC->WEB_COMMSSTATE);
|
|
Len += sprintf(&Buff[Len], "<tr><td>TNC State</td><td>%s</td></tr>", TNC->WEB_TNCSTATE);
|
|
Len += sprintf(&Buff[Len], "<tr><td>Mode</td><td>%s</td></tr>", TNC->WEB_MODE);
|
|
Len += sprintf(&Buff[Len], "<tr><td>Channel State</td><td>%s</td></tr>", TNC->WEB_CHANSTATE);
|
|
Len += sprintf(&Buff[Len], "<tr><td>Proto State</td><td>%s</td></tr>", TNC->WEB_PROTOSTATE);
|
|
Len += sprintf(&Buff[Len], "<tr><td>Traffic</td><td>%s</td></tr>", TNC->WEB_TRAFFIC);
|
|
// Len += sprintf(&Buff[Len], "<tr><td>TNC Restarts</td><td></td></tr>", TNC->WEB_RESTARTS);
|
|
Len += sprintf(&Buff[Len], "</table>");
|
|
|
|
Len += sprintf(&Buff[Len], "<textarea rows=10 style=\"width:500px; height:250px;\" id=textarea >%s</textarea>", TNC->WebBuffer);
|
|
Len = DoScanLine(TNC, Buff, Len);
|
|
|
|
return Len;
|
|
}
|
|
|
|
UINT UIARQExtInit(EXTPORTDATA * PortEntry)
|
|
{
|
|
int i, port;
|
|
char Msg[255];
|
|
struct TNCINFO * TNC;
|
|
char * ptr;
|
|
struct PORTCONTROL * PORT;
|
|
struct STREAMINFO * STREAM;
|
|
|
|
srand((unsigned int)time(NULL));
|
|
|
|
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 (int) ExtProc;
|
|
}
|
|
|
|
for (i = 0; i <MAXARQ; i++)
|
|
{
|
|
TNC->Streams[i].ARQInfo = zalloc(sizeof(struct ARQINFO));
|
|
}
|
|
|
|
TNC->Port = port;
|
|
|
|
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->PERMITGATEWAY = TRUE; // Can change ax.25 call on each stream
|
|
PortEntry->PORTCONTROL.UICAPABLE = 1; // Can send beacons
|
|
PortEntry->PORTCONTROL.PORTQUALITY = 0;
|
|
PortEntry->SCANCAPABILITIES = NONE; // Scan Control - None
|
|
|
|
if (PortEntry->PORTCONTROL.PORTPACLEN == 0 || PortEntry->PORTCONTROL.PORTPACLEN > 128)
|
|
PortEntry->PORTCONTROL.PORTPACLEN = 64;
|
|
|
|
ptr=strchr(TNC->NodeCall, ' ');
|
|
if (ptr) *(ptr) = 0; // Null Terminate
|
|
|
|
TNC->Hardware = H_UIARQ;
|
|
|
|
if (TNC->BusyWait == 0)
|
|
TNC->BusyWait = 10;
|
|
|
|
PortEntry->MAXHOSTMODESESSIONS = MAXARQ;
|
|
|
|
i = 0;
|
|
|
|
while (TNC->ARQPorts[i])
|
|
{
|
|
PORT = GetPortTableEntryFromPortNum(TNC->ARQPorts[i]);
|
|
PORT->UIHook = (FARPROCY)UIHook;
|
|
PORT->HookPort = (struct PORTCONTROL *)PortEntry;
|
|
i++;
|
|
}
|
|
|
|
TNC->WEB_MODE = zalloc(50);
|
|
TNC->WEB_TRAFFIC = zalloc(100);
|
|
|
|
PortEntry->PORTCONTROL.TNC = TNC;
|
|
|
|
TNC->WebWindowProc = WebProc;
|
|
TNC->WebWinX = 520;
|
|
TNC->WebWinY = 500;
|
|
TNC->WebBuffer = zalloc(5000);
|
|
|
|
TNC->WEB_COMMSSTATE = zalloc(100);
|
|
TNC->WEB_TNCSTATE = zalloc(100);
|
|
TNC->WEB_CHANSTATE = zalloc(100);
|
|
TNC->WEB_BUFFERS = zalloc(100);
|
|
TNC->WEB_PROTOSTATE = zalloc(100);
|
|
TNC->WEB_RESTARTTIME = zalloc(100);
|
|
TNC->WEB_RESTARTS = zalloc(100);
|
|
|
|
TNC->WEB_MODE = zalloc(50);
|
|
TNC->WEB_TRAFFIC = zalloc(100);
|
|
|
|
|
|
#ifndef LINBPQ
|
|
|
|
CreatePactorWindow(TNC, ClassName, WindowTitle, RigControlRow, PacWndProc, 560, 350, ForcedClose);
|
|
|
|
CreateWindowEx(WS_EX_STATICEDGE, "STATIC", " MyCall", WS_CHILD | WS_VISIBLE, 5,6,79,20, TNC->hDlg, NULL, hInstance, NULL);
|
|
CreateWindowEx(WS_EX_STATICEDGE, "STATIC", " DestCall", WS_CHILD | WS_VISIBLE, 85,6,79,20, TNC->hDlg, NULL, hInstance, NULL);
|
|
CreateWindowEx(WS_EX_STATICEDGE, "STATIC", " Status", WS_CHILD | WS_VISIBLE, 165,6,84,20, TNC->hDlg, NULL, hInstance, NULL);
|
|
CreateWindowEx(WS_EX_STATICEDGE, "STATIC", " Sent", WS_CHILD | WS_VISIBLE, 250,6,59,20, TNC->hDlg, NULL, hInstance, NULL);
|
|
CreateWindowEx(WS_EX_STATICEDGE, "STATIC", " Rxed", WS_CHILD | WS_VISIBLE, 310,6,59,20, TNC->hDlg, NULL, hInstance, NULL);
|
|
CreateWindowEx(WS_EX_STATICEDGE, "STATIC", " Resent", WS_CHILD | WS_VISIBLE, 370,6,59,20, TNC->hDlg, NULL, hInstance, NULL);
|
|
CreateWindowEx(WS_EX_STATICEDGE, "STATIC", " Acked", WS_CHILD | WS_VISIBLE, 430,6,59,20, TNC->hDlg, NULL, hInstance, NULL);
|
|
CreateWindowEx(WS_EX_STATICEDGE, "STATIC", " Dirn", WS_CHILD | WS_VISIBLE, 490,6,49,20, TNC->hDlg, NULL, hInstance, NULL);
|
|
|
|
for (i = 0; i <MAXARQ; i++)
|
|
{
|
|
STREAM = &TNC->Streams[i];
|
|
|
|
STREAM->xIDC_MYCALL = CreateWindowEx(WS_EX_CLIENTEDGE, "STATIC", "", WS_CHILD | WS_VISIBLE, 5, 26 + i*20, 79, 20, TNC->hDlg, NULL, hInstance, NULL);
|
|
STREAM->xIDC_DESTCALL = CreateWindowEx(WS_EX_CLIENTEDGE, "STATIC", "", WS_CHILD | WS_VISIBLE, 85, 26 + i*20, 79, 20, TNC->hDlg, NULL, hInstance, NULL);
|
|
STREAM->xIDC_STATUS = CreateWindowEx(WS_EX_CLIENTEDGE, "STATIC", "", WS_CHILD | WS_VISIBLE, 165, 26 + i*20, 84, 20, TNC->hDlg, NULL, hInstance, NULL);
|
|
STREAM->xIDC_SEND = CreateWindowEx(WS_EX_CLIENTEDGE, "STATIC", "", WS_CHILD | WS_VISIBLE, 250, 26 + i*20, 59, 20, TNC->hDlg, NULL, hInstance, NULL);
|
|
STREAM->xIDC_RXED = CreateWindowEx(WS_EX_CLIENTEDGE, "STATIC", "", WS_CHILD | WS_VISIBLE, 310, 26 + i*20, 59, 20, TNC->hDlg, NULL, hInstance, NULL);
|
|
STREAM->xIDC_RESENT = CreateWindowEx(WS_EX_CLIENTEDGE, "STATIC", "", WS_CHILD | WS_VISIBLE, 370, 26 + i*20, 59, 20, TNC->hDlg, NULL, hInstance, NULL);
|
|
STREAM->xIDC_ACKED = CreateWindowEx(WS_EX_CLIENTEDGE, "STATIC", "", WS_CHILD | WS_VISIBLE, 430, 26 + i*20, 59, 20, TNC->hDlg, NULL, hInstance, NULL);
|
|
STREAM->xIDC_DIRN = CreateWindowEx(WS_EX_CLIENTEDGE, "STATIC", "", WS_CHILD | WS_VISIBLE, 490, 26 + i*20, 49, 20, TNC->hDlg, NULL, hInstance, NULL);
|
|
}
|
|
|
|
TNC->ClientHeight = 360;
|
|
TNC->ClientWidth = 560;
|
|
|
|
MoveWindows(TNC);
|
|
|
|
#endif
|
|
|
|
i=sprintf(Msg,"UIARQ\n");
|
|
WritetoConsole(Msg);
|
|
|
|
return ((int) ExtProc);
|
|
}
|
|
|
|
|
|
static int ProcessLine(char * buf, int Port)
|
|
{
|
|
UCHAR * ptr;
|
|
char * p_ipad = 0;
|
|
int BPQport;
|
|
int len=510;
|
|
struct TNCINFO * TNC;
|
|
|
|
char errbuf[256];
|
|
|
|
strcpy(errbuf, buf);
|
|
|
|
BPQport = Port;
|
|
TNC = TNCInfo[BPQport] = zalloc(sizeof(struct TNCINFO));
|
|
|
|
TNC->Timeout = 50; // Default retry = 5 seconds
|
|
TNC->Retries = 6; // Default Retries
|
|
TNC->Window = 16;
|
|
|
|
TNC->InitScript = malloc(1000);
|
|
TNC->InitScript[0] = 0;
|
|
|
|
// Read Initialisation lines
|
|
|
|
while(TRUE)
|
|
{
|
|
strcpy(errbuf, buf);
|
|
|
|
if (memcmp(buf, "****", 4) == 0)
|
|
return TRUE;
|
|
|
|
ptr = strchr(buf, ';');
|
|
if (ptr)
|
|
{
|
|
*ptr++ = 13;
|
|
*ptr = 0;
|
|
}
|
|
|
|
if (_memicmp(buf, "TIMEOUT", 7) == 0)
|
|
TNC->Timeout = atoi(&buf[8]) * 10;
|
|
else if (_memicmp(buf, "RETRIES", 7) == 0)
|
|
TNC->Retries = atoi(&buf[8]);
|
|
else if (_memicmp(buf, "WINDOW", 6) == 0)
|
|
TNC->Window = atoi(&buf[7]);
|
|
else
|
|
{
|
|
char * ptr, * p_value, * p_port;
|
|
int i;
|
|
|
|
ptr = strtok(buf, "=\t\n\r");
|
|
p_value = strtok(NULL, " \t\n\r");
|
|
|
|
if (ptr == NULL) return (TRUE);
|
|
|
|
if (*ptr =='#') return (TRUE); // comment
|
|
|
|
if (*ptr ==';') return (TRUE); // comment
|
|
|
|
if (_stricmp(ptr,"Ports") == 0)
|
|
{
|
|
int n = 0;
|
|
|
|
p_port = strtok(p_value, " ,\t\n\r");
|
|
|
|
while (p_port != NULL)
|
|
{
|
|
i = atoi(p_port);
|
|
if (i == 0) return FALSE;
|
|
if (i > NUMBEROFPORTS) return FALSE;
|
|
|
|
TNC->ARQPorts[n++] = i;
|
|
p_port = strtok(NULL, " ,\t\n\r");
|
|
}
|
|
}
|
|
}
|
|
if (GetLine(buf) == 0)
|
|
return TRUE;
|
|
|
|
}
|
|
return FALSE;
|
|
}
|
|
static VOID SendPacket(struct TNCINFO * TNC, struct STREAMINFO * STREAM, UCHAR * Msg, int MsgLen)
|
|
{
|
|
DIGIMESSAGE Block = {0};
|
|
int Port = TNC->ARQPorts[0];
|
|
|
|
Block.CTL = 3;
|
|
Block.PID = 0xF0;
|
|
|
|
ConvToAX25(STREAM->RemoteCall, Block.DEST);
|
|
Block.DEST[6] |= 0x80; // set Command Bit
|
|
|
|
memcpy(Block.ORIGIN, MYCALL, 7);
|
|
|
|
Block.L2DATA[0] = STREAM->ARQInfo->FarStream;
|
|
memcpy(&Block.L2DATA[1], Msg, MsgLen);
|
|
MsgLen += 1;
|
|
|
|
Send_AX_Datagram(&Block, MsgLen + 2, Port); // Inulude CTL and PID
|
|
}
|
|
|
|
VOID ProcessFLDigiData(struct TNCINFO * TNC, UCHAR * Input, int Len, int Stream);
|
|
|
|
VOID ProcessARQPacket(struct PORTCONTROL * PORT, MESSAGE * Buffer)
|
|
{
|
|
// ARQ Packet from KISS-Like Hardware
|
|
|
|
struct TNCINFO * TNC = TNCInfo[PORT->PORTNUMBER];
|
|
UCHAR * Input;
|
|
int Len;
|
|
int Stream = Buffer->L2DATA[0] - 64;
|
|
|
|
if (Stream < 0 || Stream > MAXARQ)
|
|
return;
|
|
|
|
// First Bytes is Stream Number (as ASCII Letter)
|
|
|
|
Input = &Buffer->L2DATA[1];
|
|
Len = Buffer->LENGTH - 24;
|
|
|
|
ProcessFLDigiData(TNC, Input, Len, Stream);
|
|
}
|
|
|
|
/*
|
|
|
|
<SOH>00cG8BPQ:1025 G8BPQ:24 0 8 T60R6W108E06<EOT>
|
|
<SOH>00kG8BPQ:24 G8BPQ 4 85F9B<EOT>
|
|
|
|
<SOH>00cG8BPQ:1025 GM8BPQ:24 0 7 T60R5W1051D5<EOT> (128, 5)
|
|
|
|
,<SOH>00cG8BPQ:1025 G8BPQ:24 0 7 T60R5W10FA36<EOT>
|
|
<SOH>00kG8BPQ:24 G8BPQ 5 89FCA<EOT>
|
|
|
|
First no sees to be a connection counter. Next may be stream
|
|
|
|
|
|
<SOH>08s___ABFC<EOT>
|
|
<SOH>08tG8BPQ:73 xxx 33FA<EOT>
|
|
<SOH>00tG8BPQ:73 yyy 99A3<EOT>
|
|
<SOH>08dG8BPQ:90986C<EOT>
|
|
<SOH>00bG8BPQ:911207<EOT>
|
|
|
|
call:90 for dis 91 for dis ack 73<sp> for chat)
|
|
|
|
<SOH>08pG8BPQ<SUB>?__645E<EOT>
|
|
<SOH>00s_??4235<EOT>
|
|
|
|
<SOH>08pG8BPQ<SUB>?__645E<EOT>
|
|
<SOH>00s_??4235<EOT>
|
|
|
|
i Ident
|
|
c Connect
|
|
k Connect Ack
|
|
r Connect NAK
|
|
d Disconnect req
|
|
s Data Ack/ Retransmit Req )status)
|
|
p Poll
|
|
f Format Fail
|
|
b dis ack
|
|
t talk
|
|
|
|
a Abort
|
|
o Abort ACK
|
|
|
|
|
|
<SOH>00cG8BPQ:1025 G8BPQ:24 0 7 T60R5W10FA36<EOT>
|
|
<SOH>00kG8BPQ:24 G8BPQ 6 49A3A<EOT>
|
|
<SOH>08s___ABFC<EOT>
|
|
<SOH>08 ARQ:FILE::flarqmail-1.eml
|
|
ARQ:EMAIL::
|
|
ARQ:SIZE::90
|
|
ARQ::STX
|
|
//FLARQ COMPOSER
|
|
Date: 09/01/2014 23:24:42
|
|
To: gm8bpq
|
|
From:
|
|
SubjectA0E0<SOH>
|
|
<SOH>08!: Test
|
|
|
|
Test Message
|
|
|
|
ARQ::ETX
|
|
F0F2<SOH>
|
|
<SOH>08pG8BPQ<SUB>!__623E<EOT>
|
|
<SOH>08pG8BPQ<SUB>!__623E<EOT>
|
|
<SOH>08pG8BPQ<SUB>!__623E<EOT>
|
|
|
|
|
|
|
|
|
|
*/
|
|
static VOID ProcessFLDigiData(struct TNCINFO * TNC, UCHAR * Input, int Len, int Stream)
|
|
{
|
|
PMSGWITHLEN buffptr;
|
|
struct STREAMINFO * STREAM = &TNC->Streams[Stream];
|
|
char CTRL = Input[0];
|
|
struct ARQINFO * ARQ = STREAM->ARQInfo;
|
|
char Channel = Stream + 64;
|
|
int SendLen;
|
|
char Reply[80];
|
|
|
|
Input[Len] = 0;
|
|
|
|
// Process Message
|
|
|
|
// This processes eitrher message from the KISS or RAW interfaces.
|
|
// Headers and RAW checksum have been removed, so packet starts with Control Byte
|
|
|
|
// Only a connect request is allowed with no session, so check first
|
|
|
|
if (CTRL == 'c')
|
|
{
|
|
// Connect Request
|
|
|
|
char * call1;
|
|
char * call2;
|
|
char * port1;
|
|
char * port2;
|
|
char * ptr;
|
|
char * context;
|
|
char FarStream = 0;
|
|
int BlockSize = 6; // 64 default
|
|
int Window = TNC->Window;
|
|
APPLCALLS * APPL;
|
|
char * ApplPtr = APPLS;
|
|
int App;
|
|
char Appl[10];
|
|
struct WL2KInfo * WL2K = TNC->WL2K;
|
|
TRANSPORTENTRY * SESS;
|
|
|
|
if (Stream)
|
|
return; // Shouldn't have Stream on Connect Request
|
|
|
|
call1 = strtok_s(&Input[1], " ", &context);
|
|
call2 = strtok_s(NULL, " ", &context);
|
|
|
|
port1 = strlop(call1, ':');
|
|
port2 = strlop(call2, ':');
|
|
|
|
// See if for us
|
|
|
|
for (App = 0; App < 32; App++)
|
|
{
|
|
APPL=&APPLCALLTABLE[App];
|
|
memcpy(Appl, APPL->APPLCALL_TEXT, 10);
|
|
ptr=strchr(Appl, ' ');
|
|
|
|
if (ptr) *ptr = 0;
|
|
|
|
if (_stricmp(call2, Appl) == 0)
|
|
break;
|
|
|
|
memcpy(Appl, APPL->APPLALIAS_TEXT, 10);
|
|
ptr=strchr(Appl, ' ');
|
|
|
|
if (ptr) *ptr = 0;
|
|
|
|
if (_stricmp(call2, Appl) == 0)
|
|
break;
|
|
|
|
}
|
|
|
|
if (App > 31)
|
|
if (strcmp(TNC->NodeCall, call2) !=0)
|
|
if (strcmp(call2, MYALIASLOPPED) !=0)
|
|
return; // Not Appl or Port/Node Call
|
|
|
|
ptr = strtok_s(NULL, " ", &context);
|
|
FarStream = *ptr;
|
|
ptr = strtok_s(NULL, " ", &context);
|
|
BlockSize = atoi(ptr);
|
|
|
|
if (ARQ->ARQState > ARQ_CONNECTING)
|
|
{
|
|
// We have already received a connect request - just ACK it
|
|
|
|
goto AckConnectRequest;
|
|
}
|
|
|
|
// Get a Session
|
|
|
|
Stream = 1;
|
|
|
|
while(Stream <= MAXARQ)
|
|
{
|
|
if (TNC->PortRecord->ATTACHEDSESSIONS[Stream] == 0)
|
|
goto GotStream;
|
|
|
|
Stream++;
|
|
}
|
|
|
|
// No free streams - send Disconnect
|
|
|
|
return;
|
|
|
|
GotStream:
|
|
|
|
STREAM = &TNC->Streams[Stream];
|
|
|
|
ProcessIncommingConnect(TNC, call1, Stream, FALSE);
|
|
|
|
SESS = TNC->PortRecord->ATTACHEDSESSIONS[Stream];
|
|
|
|
strcpy(STREAM->MyCall, call2);
|
|
STREAM->ConnectTime = time(NULL);
|
|
STREAM->BytesRXed = STREAM->BytesTXed = STREAM->BytesAcked = STREAM->BytesResent = 0;
|
|
|
|
if (WL2K)
|
|
strcpy(SESS->RMSCall, WL2K->RMSCall);
|
|
|
|
ARQ = STREAM->ARQInfo;
|
|
|
|
memset(ARQ, 0, sizeof(struct ARQINFO)); // Reset ARQ State
|
|
ARQ->FarStream = FarStream;
|
|
ARQ->TXSeq = ARQ->TXLastACK = 63; // Last Sent
|
|
ARQ->RXHighest = ARQ->RXNoGaps = 63; // Last Received
|
|
ARQ->ARQState = ARQ_ACTIVE;
|
|
ARQ->OurStream = Stream + 64;
|
|
|
|
STREAM->NeedDisc = 0;
|
|
|
|
if (App < 32)
|
|
{
|
|
char AppName[13];
|
|
|
|
memcpy(AppName, &ApplPtr[App * sizeof(CMDX)], 12);
|
|
AppName[12] = 0;
|
|
|
|
// Make sure app is available
|
|
|
|
if (CheckAppl(TNC, AppName))
|
|
{
|
|
char Buffer[32];
|
|
int MsgLen = sprintf(Buffer, "%s\r", AppName);
|
|
|
|
buffptr = GetBuff();
|
|
|
|
if (buffptr == 0)
|
|
{
|
|
return; // No buffers, so ignore
|
|
}
|
|
|
|
buffptr->Len = MsgLen;
|
|
memcpy(buffptr->Data, Buffer, MsgLen);
|
|
|
|
C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr);
|
|
|
|
TNC->SwallowSignon = TRUE;
|
|
|
|
// Save Appl Call in case needed for
|
|
|
|
}
|
|
else
|
|
{
|
|
STREAM->NeedDisc = 50; // 1 sec
|
|
}
|
|
}
|
|
|
|
ARQ->TXWindow = Window;
|
|
|
|
if (BlockSize < 4) BlockSize = 4;
|
|
if (BlockSize < 9) BlockSize = 9;
|
|
|
|
ARQ->MaxBlock = Blocksizes[BlockSize];
|
|
|
|
|
|
ARQ->ARQTimer = 1; // To force CTEXT to be Queued
|
|
|
|
if (App == 32)
|
|
{
|
|
// Connect to Node - send CTEXT
|
|
|
|
if (HFCTEXTLEN > 1)
|
|
{
|
|
buffptr = GetBuff();
|
|
if (buffptr)
|
|
{
|
|
buffptr->Len = HFCTEXTLEN;
|
|
memcpy(buffptr->Data, HFCTEXT, HFCTEXTLEN);
|
|
SendARQData(TNC, buffptr, Stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (STREAM->NeedDisc)
|
|
{
|
|
// Send Not Avail
|
|
|
|
buffptr = GetBuff();
|
|
if (buffptr)
|
|
{
|
|
buffptr->Len = sprintf(buffptr->Data, "Application Not Available\r");
|
|
SendARQData(TNC, buffptr, Stream);
|
|
}
|
|
}
|
|
|
|
SetWindowText(STREAM->xIDC_MYCALL, STREAM->MyCall);
|
|
SetWindowText(STREAM->xIDC_DESTCALL, STREAM->RemoteCall);
|
|
SetWindowText(STREAM->xIDC_STATUS, "ConPending");
|
|
SetWindowText(STREAM->xIDC_DIRN, "In");
|
|
|
|
|
|
AckConnectRequest:
|
|
|
|
SendLen = sprintf(Reply, "k%s:24 %s %c 7", call2, call1, ARQ->OurStream);
|
|
|
|
SaveAndSend(TNC, ARQ, TNC->TCPDataSock, Reply, SendLen);
|
|
ARQ->ARQTimerState = ARQ_CONNECTACK;
|
|
|
|
return;
|
|
}
|
|
|
|
// All others need a session
|
|
|
|
// if (!STREAM->Connected && !STREAM->Connecting)
|
|
// return;
|
|
|
|
if (CTRL == 'k')
|
|
{
|
|
// Connect ACK
|
|
|
|
char * call1;
|
|
char * call2;
|
|
char * port1;
|
|
char * port2;
|
|
char * ptr;
|
|
char * context;
|
|
char FarStream = 0;
|
|
int BlockSize = 6; // 64 default
|
|
int Window = 16;
|
|
|
|
char Reply[80];
|
|
int ReplyLen;
|
|
|
|
call1 = strtok_s(&Input[1], " ", &context);
|
|
call2 = strtok_s(NULL, " ", &context);
|
|
|
|
port1 = strlop(call1, ':');
|
|
port2 = strlop(call2, ':');
|
|
|
|
if (strcmp(call1, STREAM->RemoteCall) != 0)
|
|
return;
|
|
|
|
if (Channel != ARQ->OurStream)
|
|
return; // Wrong Session
|
|
|
|
ptr = strtok_s(NULL, " ", &context);
|
|
if (ptr)
|
|
FarStream = *ptr;
|
|
ptr = strtok_s(NULL, " ", &context);
|
|
if (ptr)
|
|
BlockSize = atoi(ptr);
|
|
|
|
if (STREAM->Connected)
|
|
goto SendKReply; // Repeated ACK
|
|
|
|
STREAM->ConnectTime = time(NULL);
|
|
STREAM->BytesRXed = STREAM->BytesTXed = STREAM->BytesAcked = STREAM->BytesResent = 0;
|
|
STREAM->Connected = TRUE;
|
|
|
|
ARQ->ARQTimerState = 0;
|
|
ARQ->ARQTimer = 0;
|
|
|
|
sprintf(TNC->WEB_TNCSTATE, "%s Connected to %s Outbound", STREAM->MyCall, STREAM->RemoteCall);
|
|
|
|
SetWindowText(STREAM->xIDC_MYCALL, STREAM->MyCall);
|
|
SetWindowText(STREAM->xIDC_DESTCALL, STREAM->RemoteCall);
|
|
SetWindowText(STREAM->xIDC_DIRN, "Out");
|
|
|
|
UpdateMH(TNC, STREAM->RemoteCall, '+', 'Z');
|
|
|
|
ARQ->ARQTimerState = 0;
|
|
ARQ->FarStream = FarStream;
|
|
ARQ->TXWindow = TNC->Window;
|
|
ARQ->MaxBlock = Blocksizes[BlockSize];
|
|
|
|
ARQ->ARQState = ARQ_ACTIVE;
|
|
|
|
STREAM->NeedDisc = 0;
|
|
|
|
buffptr = GetBuff();
|
|
|
|
if (buffptr)
|
|
{
|
|
ReplyLen = sprintf(Reply, "*** Connected to %s\r", STREAM->RemoteCall);
|
|
|
|
buffptr->Len = ReplyLen;
|
|
memcpy(buffptr->Data, Reply, ReplyLen);
|
|
|
|
C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr);
|
|
}
|
|
|
|
strcpy(TNC->WEB_PROTOSTATE, "Connected");
|
|
SetWindowText(STREAM->xIDC_STATUS, "Connected");
|
|
|
|
SendKReply:
|
|
|
|
// Reply with status
|
|
|
|
SendLen = sprintf(Reply, "s%c%c%c", ARQ->TXSeq + 32, ARQ->RXNoGaps + 32, ARQ->RXHighest + 32);
|
|
|
|
if (ARQ->RXHighest != ARQ->RXNoGaps)
|
|
{
|
|
int n = ARQ->RXNoGaps + 1;
|
|
n &= 63;
|
|
|
|
while (n != ARQ->RXHighest)
|
|
{
|
|
if (ARQ->RXHOLDQ[n] == 0) // Dont have it
|
|
SendLen += sprintf(&Reply[SendLen], "%c", n + 32);
|
|
|
|
n++;
|
|
n &= 63;
|
|
}
|
|
}
|
|
|
|
QueueAndSend(TNC, ARQ, TNC->TCPDataSock, Reply, SendLen);
|
|
return;
|
|
}
|
|
|
|
// All others need a session
|
|
|
|
//if (!STREAM->Connected)
|
|
// return;
|
|
|
|
|
|
if (CTRL == 's')
|
|
{
|
|
// Status
|
|
|
|
if (Channel != ARQ->OurStream)
|
|
return; // Wrong Session
|
|
|
|
ARQ->ARQTimer = 0; // Stop retry timer
|
|
Input[Len] = 0;
|
|
ProcessARQStatus(TNC, Stream, ARQ, &Input[1]);
|
|
|
|
return;
|
|
}
|
|
|
|
if (CTRL == 'p')
|
|
{
|
|
// Poll
|
|
|
|
char * call1;
|
|
char * context;
|
|
|
|
call1 = strtok_s(&Input[1], " \x1A", &context);
|
|
|
|
if (strcmp(call1, STREAM->RemoteCall) != 0)
|
|
return;
|
|
|
|
if (Channel != ARQ->OurStream)
|
|
return; // Wrong Session
|
|
|
|
Debugprintf("Sending Poll Resp TX NOGaps High %d %d %d", ARQ->TXSeq, ARQ->RXNoGaps, ARQ->RXHighest);
|
|
|
|
SendLen = sprintf(Reply, "s%c%c%c", ARQ->TXSeq + 32, ARQ->RXNoGaps + 32, ARQ->RXHighest + 32);
|
|
|
|
if (ARQ->RXHighest != ARQ->RXNoGaps)
|
|
{
|
|
int n = ARQ->RXNoGaps + 1;
|
|
n &= 63;
|
|
|
|
while (n != ARQ->RXHighest)
|
|
{
|
|
if (ARQ->RXHOLDQ[n] == 0) // Dont have it
|
|
SendLen += sprintf(&Reply[SendLen], "%c", n + 32);
|
|
|
|
n++;
|
|
n &= 63;
|
|
}
|
|
}
|
|
else
|
|
ARQ->TurnroundTimer = 15; // Allow us to send it all acked
|
|
|
|
QueueAndSend(TNC, ARQ, TNC->TCPDataSock, Reply, SendLen);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
if (CTRL == 'a')
|
|
{
|
|
// Abort. Send Abort ACK - same as
|
|
|
|
char * call1;
|
|
char * context;
|
|
|
|
call1 = strtok_s(&Input[1], " :", &context);
|
|
|
|
if (strcmp(call1, STREAM->RemoteCall) != 0)
|
|
return;
|
|
|
|
if (Channel != ARQ->OurStream)
|
|
return; // Wrong Session
|
|
|
|
SendLen = sprintf(Reply, "o%c%c%c", ARQ->TXSeq + 32, ARQ->RXNoGaps + 32, ARQ->RXHighest + 32);
|
|
|
|
if (ARQ->RXHighest != ARQ->RXNoGaps)
|
|
{
|
|
int n = ARQ->RXNoGaps + 1;
|
|
n &= 63;
|
|
|
|
while (n != ARQ->RXHighest)
|
|
{
|
|
if (ARQ->RXHOLDQ[n] == 0) // Dont have it
|
|
SendLen += sprintf(&Reply[SendLen], "%c", n + 32);
|
|
|
|
n++;
|
|
n &= 63;
|
|
}
|
|
}
|
|
|
|
QueueAndSend(TNC, ARQ, TNC->TCPDataSock, Reply, SendLen);
|
|
return;
|
|
}
|
|
|
|
if (CTRL == 'i')
|
|
{
|
|
// Ident
|
|
|
|
return;
|
|
}
|
|
|
|
if (CTRL == 't')
|
|
{
|
|
// Talk - not sure what to do with these
|
|
|
|
return;
|
|
}
|
|
|
|
if (CTRL == 'd')
|
|
{
|
|
// Disconnect Request
|
|
|
|
char * call1;
|
|
char * context;
|
|
|
|
call1 = strtok_s(&Input[1], " ", &context);
|
|
strlop(call1, ':');
|
|
|
|
if (strcmp(STREAM->RemoteCall, call1))
|
|
return;
|
|
|
|
if (Channel != ARQ->OurStream)
|
|
return; // Wrong Session
|
|
|
|
|
|
// As the Disc ACK isn't repeated, we have to clear session now
|
|
|
|
STREAM->Connected = FALSE;
|
|
STREAM->Connecting = FALSE;
|
|
STREAM->ReportDISC = TRUE;
|
|
|
|
strcpy(TNC->WEB_PROTOSTATE, "Disconncted");
|
|
|
|
SetWindowText(STREAM->xIDC_MYCALL, "");
|
|
SetWindowText(STREAM->xIDC_DESTCALL, "");
|
|
SetWindowText(STREAM->xIDC_DIRN, "");
|
|
SetWindowText(STREAM->xIDC_STATUS, "");
|
|
|
|
ARQ->ARQState = 0;
|
|
|
|
SendLen = sprintf(Reply, "b%s:91", STREAM->MyCall);
|
|
|
|
ARQ->ARQTimerState = ARQ_WAITACK;
|
|
SaveAndSend(TNC, ARQ, TNC->TCPDataSock, Reply, SendLen);
|
|
ARQ->Retries = 2;
|
|
return;
|
|
}
|
|
|
|
if (CTRL == 'b')
|
|
{
|
|
// Disconnect ACK
|
|
|
|
char * call1;
|
|
char * context;
|
|
|
|
call1 = strtok_s(&Input[1], " ", &context);
|
|
strlop(call1, ':');
|
|
|
|
if (strcmp(STREAM->RemoteCall, call1))
|
|
return;
|
|
|
|
if (Channel != ARQ->OurStream)
|
|
return; // Wrong Session
|
|
|
|
ARQ->ARQTimer = 0;
|
|
ARQ->ARQTimerState = 0;
|
|
ARQ->ARQState = 0;
|
|
|
|
if (STREAM->Connected)
|
|
{
|
|
// Create a traffic record
|
|
|
|
char logmsg[120];
|
|
time_t Duration;
|
|
|
|
Duration = time(NULL) - STREAM->ConnectTime;
|
|
|
|
if (Duration == 0)
|
|
Duration = 1;
|
|
|
|
sprintf(logmsg,"Port %2d %9s Bytes Sent %d BPS %d Bytes Received %d BPS %d Time %d Seconds",
|
|
TNC->Port, STREAM->RemoteCall,
|
|
STREAM->BytesTXed, (int)(STREAM->BytesTXed/Duration),
|
|
STREAM->BytesRXed, (int)(STREAM->BytesRXed/Duration), (int)Duration);
|
|
|
|
Debugprintf(logmsg);
|
|
}
|
|
|
|
STREAM->Connecting = FALSE;
|
|
STREAM->Connected = FALSE; // Back to Command Mode
|
|
STREAM->ReportDISC = TRUE; // Tell Node
|
|
|
|
STREAM->Disconnecting = FALSE;
|
|
|
|
strcpy(TNC->WEB_PROTOSTATE, "Disconncted");
|
|
SetWindowText(STREAM->xIDC_MYCALL, "");
|
|
SetWindowText(STREAM->xIDC_DESTCALL, "");
|
|
SetWindowText(STREAM->xIDC_DIRN, "");
|
|
SetWindowText(STREAM->xIDC_STATUS, "");
|
|
|
|
return;
|
|
}
|
|
|
|
if (CTRL == 'u')
|
|
{
|
|
// Beacon
|
|
|
|
//>00uGM8BPQ:72 GM8BPQ TestingAD67
|
|
|
|
char * Call = &Input[1];
|
|
strlop(Call, ':');
|
|
|
|
UpdateMH(TNC, Call, '!', 0);
|
|
return;
|
|
}
|
|
|
|
if (STREAM->Connected)
|
|
{
|
|
if (Channel != ARQ->OurStream)
|
|
return; // Wrong Session
|
|
|
|
if (CTRL >= ' ' && CTRL < 96)
|
|
{
|
|
// ARQ Data
|
|
|
|
int Seq = CTRL - 32;
|
|
int Work;
|
|
|
|
// if (rand() % 5 == 2)
|
|
// {
|
|
// Debugprintf("Dropping %d", Seq);
|
|
// return;
|
|
// }
|
|
|
|
buffptr = GetBuff();
|
|
|
|
if (buffptr == NULL)
|
|
return; // Sould never run out, but cant do much else
|
|
|
|
// Remove any DLE transparency
|
|
|
|
Len -= 1;
|
|
|
|
buffptr->Len = Len;
|
|
memcpy(buffptr->Data, &Input[1], Len);
|
|
STREAM->BytesRXed += Len;
|
|
|
|
UpdateStatsLine(TNC, STREAM);
|
|
|
|
// Safest always to save, then see what we can process
|
|
|
|
if (ARQ->RXHOLDQ[Seq])
|
|
{
|
|
// Wot! Shouldn't happen
|
|
|
|
ReleaseBuffer(ARQ->RXHOLDQ[Seq]);
|
|
// Debugprintf("ARQ Seq %d Duplicate");
|
|
}
|
|
|
|
ARQ->RXHOLDQ[Seq] = buffptr;
|
|
// Debugprintf("ARQ saving %d", Seq);
|
|
|
|
// If this is higher that highest received, save. But beware of wrap'
|
|
|
|
// Hi = 2, Seq = 60 dont save s=h = 58
|
|
// Hi = 10 Seq = 12 save s-h = 2
|
|
// Hi = 14 Seq = 10 dont save s-h = -4
|
|
// Hi = 60 Seq = 2 save s-h = -58
|
|
|
|
Work = Seq - ARQ->RXHighest;
|
|
|
|
if ((Work > 0 && Work < 32) || Work < -32)
|
|
ARQ->RXHighest = Seq;
|
|
|
|
// We may now be able to process some
|
|
|
|
Work = (ARQ->RXNoGaps + 1) & 63; // The next one we need
|
|
|
|
while (ARQ->RXHOLDQ[Work])
|
|
{
|
|
// We have it
|
|
|
|
C_Q_ADD(&STREAM->PACTORtoBPQ_Q, ARQ->RXHOLDQ[Work]);
|
|
// ReleaseBuffer(ARQ->RXHOLDQ[Work]);
|
|
|
|
ARQ->RXHOLDQ[Work] = NULL;
|
|
// Debugprintf("Processing %d from Q", Work);
|
|
|
|
ARQ->RXNoGaps = Work;
|
|
Work = (Work + 1) & 63; // The next one we need
|
|
}
|
|
|
|
ARQ->TurnroundTimer = 200; // Delay before allowing reply. Will normally be reset by the poll following data
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static VOID SendARQData(struct TNCINFO * TNC, PMSGWITHLEN Buffer, int Stream)
|
|
{
|
|
// Send Data, saving a copy until acked.
|
|
|
|
struct STREAMINFO * STREAM = &TNC->Streams[Stream];
|
|
struct ARQINFO * ARQ = STREAM->ARQInfo;
|
|
|
|
|
|
UCHAR TXBuffer[300];
|
|
SOCKET sock = TNC->TCPDataSock;
|
|
int SendLen;
|
|
UCHAR * ptr;
|
|
int Origlen = Buffer->Len;
|
|
|
|
ARQ->TXSeq++;
|
|
ARQ->TXSeq &= 63;
|
|
|
|
SendLen = sprintf(TXBuffer, "%c", ARQ->TXSeq + 32);
|
|
|
|
ptr = Buffer->Data; // Start of data;
|
|
|
|
ptr[Buffer->Len] = 0;
|
|
|
|
memcpy(&TXBuffer[SendLen], Buffer->Data, Origlen);
|
|
SendLen += Origlen;
|
|
|
|
TXBuffer[SendLen] = 0;
|
|
|
|
// if (rand() % 5 == 2)
|
|
// Debugprintf("Dropping %d", ARQ->TXSeq);
|
|
// else
|
|
|
|
ARQ->TXHOLDQ[ARQ->TXSeq] = Buffer;
|
|
|
|
STREAM->BytesTXed += Origlen;
|
|
|
|
UpdateStatsLine(TNC, STREAM);
|
|
|
|
|
|
// if waiting for ack, don't send, just queue. Will be sent when ack received
|
|
|
|
if (ARQ->ARQTimer == 0 || ARQ->ARQTimerState == ARQ_WAITDATA)
|
|
{
|
|
SendPacket(TNC, STREAM, TXBuffer, SendLen);
|
|
ARQ->ARQTimer = 15; // wait up to 1.5 sec for more data before polling
|
|
ARQ->Retries = 1;
|
|
ARQ->ARQTimerState = ARQ_WAITDATA;
|
|
}
|
|
else
|
|
STREAM->BytesResent -= Origlen; // So wont be included in resent bytes
|
|
}
|
|
|
|
VOID TidyClose(struct TNCINFO * TNC, int Stream)
|
|
{
|
|
char Reply[80];
|
|
int SendLen;
|
|
|
|
struct STREAMINFO * STREAM = &TNC->Streams[Stream];
|
|
struct ARQINFO * ARQ = STREAM->ARQInfo;
|
|
|
|
SendLen = sprintf(Reply, "d%s:90", STREAM->MyCall);
|
|
|
|
SaveAndSend(TNC, ARQ, TNC->TCPDataSock, Reply, SendLen);
|
|
ARQ->ARQTimerState = ARQ_DISC;
|
|
}
|
|
|
|
VOID ForcedClose(struct TNCINFO * TNC, int Stream)
|
|
{
|
|
TidyClose(TNC, Stream); // I don't think Hostmode has a DD
|
|
}
|
|
|
|
VOID CloseComplete(struct TNCINFO * TNC, int Stream)
|
|
{
|
|
}
|
|
|
|
static VOID SaveAndSend(struct TNCINFO * TNC, struct ARQINFO * ARQ, SOCKET sock, char * Msg, int MsgLen)
|
|
{
|
|
// Used for Messages that need a reply. Save, send and set timeout
|
|
|
|
memcpy(ARQ->LastMsg, Msg, MsgLen + 1); // Include Null
|
|
ARQ->LastLen = MsgLen;
|
|
|
|
// Delay the send for a shot while
|
|
|
|
// SendPacket(sock, Msg, MsgLen, 0);
|
|
|
|
ARQ->ARQTimer = 1;
|
|
ARQ->Retries = TNC->Retries + 1; // First timout is the real send
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
static VOID ARQTimer(struct TNCINFO * TNC)
|
|
{
|
|
PMSGWITHLEN buffptr;
|
|
struct STREAMINFO * STREAM;
|
|
struct ARQINFO * ARQ;
|
|
int SendLen;
|
|
char Reply[80];
|
|
int Stream;
|
|
|
|
//Send frames, unless held by TurnroundTimer or Window
|
|
|
|
int Outstanding;
|
|
|
|
for (Stream = 0; Stream <MAXARQ; Stream++)
|
|
{
|
|
STREAM = &TNC->Streams[Stream];
|
|
ARQ = STREAM->ARQInfo;
|
|
|
|
// TXDelay is used as a turn round delay for frames that don't have to be retried. It doesn't
|
|
// need to check for busy (or anything else (I think!)
|
|
|
|
if (ARQ->TXDelay)
|
|
{
|
|
ARQ->TXDelay--;
|
|
|
|
if (ARQ->TXDelay)
|
|
continue;
|
|
|
|
SendPacket(TNC, STREAM, ARQ->TXMsg, ARQ->TXLen);
|
|
}
|
|
|
|
// if We are alredy sending (State = ARQ_WAITDATA) we should allow it to send more (and the Poll at end)
|
|
|
|
if (ARQ->ARQTimerState == ARQ_WAITDATA)
|
|
{
|
|
while (STREAM->BPQtoPACTOR_Q)
|
|
{
|
|
Outstanding = ARQ->TXSeq - ARQ->TXLastACK;
|
|
|
|
if (Outstanding < 0)
|
|
Outstanding += 64;
|
|
|
|
TNC->PortRecord->FramesQueued = Outstanding + C_Q_COUNT(STREAM->BPQtoPACTOR_Q); // Save for Appl Level Queued Frames
|
|
|
|
if (Outstanding >= ARQ->TXWindow)
|
|
break;
|
|
|
|
buffptr = Q_REM(&STREAM->BPQtoPACTOR_Q);
|
|
SendARQData(TNC, buffptr, Stream);
|
|
}
|
|
|
|
ARQ->ARQTimer--;
|
|
|
|
if (ARQ->ARQTimer > 0)
|
|
continue; // Timer Still Running
|
|
|
|
// No more data available - send poll
|
|
|
|
SendLen = sprintf(Reply, "p%s", STREAM->MyCall);
|
|
|
|
ARQ->ARQTimerState = ARQ_WAITACK;
|
|
|
|
// This is one message that should not be queued so it is sent straiget after data
|
|
|
|
Debugprintf("Sending Poll After Data");
|
|
|
|
memcpy(ARQ->LastMsg, Reply, SendLen + 1);
|
|
ARQ->LastLen = SendLen;
|
|
|
|
SendPacket(TNC, STREAM, Reply, SendLen);
|
|
|
|
ARQ->ARQTimer = TNC->Timeout;
|
|
ARQ->Retries = TNC->Retries;
|
|
|
|
strcpy(TNC->WEB_PROTOSTATE, "Wait ACK");
|
|
SetWindowText(STREAM->xIDC_STATUS, "Wait ACK");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// TrunroundTimer is used to allow time for far end to revert to RX
|
|
|
|
if (ARQ->TurnroundTimer)
|
|
ARQ->TurnroundTimer--;
|
|
|
|
if (ARQ->TurnroundTimer == 0)
|
|
{
|
|
while (STREAM->BPQtoPACTOR_Q)
|
|
{
|
|
Outstanding = ARQ->TXSeq - ARQ->TXLastACK;
|
|
|
|
if (Outstanding < 0)
|
|
Outstanding += 64;
|
|
|
|
TNC->PortRecord->FramesQueued = Outstanding + C_Q_COUNT(&STREAM->BPQtoPACTOR_Q) + 1; // Make sure busy is reported to BBS
|
|
|
|
if (Outstanding >= ARQ->TXWindow)
|
|
break;
|
|
|
|
buffptr = Q_REM(&STREAM->BPQtoPACTOR_Q);
|
|
SendARQData(TNC, buffptr, Stream);
|
|
}
|
|
}
|
|
|
|
if (ARQ->ARQTimer)
|
|
{
|
|
// Only decrement if running send poll timer
|
|
|
|
// if (ARQ->ARQTimerState != ARQ_WAITDATA)
|
|
// return;
|
|
|
|
ARQ->ARQTimer--;
|
|
{
|
|
if (ARQ->ARQTimer)
|
|
continue; // Timer Still Running
|
|
}
|
|
|
|
ARQ->Retries--;
|
|
|
|
if (ARQ->Retries)
|
|
{
|
|
// Retry Current Message
|
|
|
|
SendPacket(TNC, STREAM, ARQ->LastMsg, ARQ->LastLen);
|
|
ARQ->ARQTimer = TNC->Timeout + (rand() % 30);
|
|
|
|
continue;
|
|
}
|
|
|
|
// Retried out.
|
|
|
|
switch (ARQ->ARQTimerState)
|
|
{
|
|
case ARQ_WAITDATA:
|
|
|
|
// No more data available - send poll
|
|
|
|
SendLen = sprintf(Reply, "p%s", STREAM->MyCall);
|
|
|
|
Debugprintf("Sending Poll After Timeout??");
|
|
|
|
ARQ->ARQTimerState = ARQ_WAITACK;
|
|
|
|
// This is one message that should not be queued so it is sent straiget after data
|
|
|
|
memcpy(ARQ->LastMsg, Reply, SendLen + 1);
|
|
ARQ->LastLen = SendLen;
|
|
|
|
SendPacket(TNC, STREAM, Reply, SendLen);
|
|
|
|
ARQ->ARQTimer = TNC->Timeout;
|
|
ARQ->Retries = TNC->Retries;
|
|
|
|
strcpy(TNC->WEB_PROTOSTATE, "Wait ACK");
|
|
SetWindowText(STREAM->xIDC_STATUS, "Wait ACK");
|
|
|
|
continue;
|
|
|
|
case ARQ_CONNECTING:
|
|
|
|
// Report Connect Failed, and drop back to command mode
|
|
|
|
buffptr = GetBuff();
|
|
|
|
if (buffptr)
|
|
{
|
|
buffptr->Len = sprintf((UCHAR *)&buffptr[2], "UIARQ} Failure with %s\r", STREAM->RemoteCall);
|
|
C_Q_ADD(&STREAM->PACTORtoBPQ_Q, buffptr);
|
|
}
|
|
|
|
// Send Disc to TNC in case it got the Connects, but we missed the ACKs
|
|
|
|
TidyClose(TNC, Stream);
|
|
ARQ->Retries = 2; // First timout is the real send, only send once
|
|
STREAM->Connecting = FALSE; // Back to Command Mode
|
|
ARQ->ARQState = FALSE;
|
|
|
|
break;
|
|
|
|
case ARQ_WAITACK:
|
|
case ARQ_CONNECTACK:
|
|
case ARQ_DISC:
|
|
|
|
STREAM->Connected = FALSE; // Back to Command Mode
|
|
STREAM->ReportDISC = TRUE;
|
|
ARQ->ARQState = FALSE;
|
|
|
|
while (STREAM->PACTORtoBPQ_Q)
|
|
ReleaseBuffer(Q_REM(&STREAM->PACTORtoBPQ_Q));
|
|
|
|
while (STREAM->BPQtoPACTOR_Q)
|
|
ReleaseBuffer(Q_REM(&STREAM->BPQtoPACTOR_Q));
|
|
|
|
strcpy(TNC->WEB_TNCSTATE, "Free");
|
|
|
|
strcpy(TNC->WEB_PROTOSTATE, "Disconncted");
|
|
|
|
SetWindowText(STREAM->xIDC_MYCALL, "");
|
|
SetWindowText(STREAM->xIDC_DESTCALL, "");
|
|
SetWindowText(STREAM->xIDC_DIRN, "");
|
|
SetWindowText(STREAM->xIDC_STATUS, "");
|
|
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static VOID ProcessARQStatus(struct TNCINFO * TNC, int Stream, struct ARQINFO * ARQ, char * Input)
|
|
{
|
|
// Release any acked frames and resend any outstanding
|
|
|
|
struct STREAMINFO * STREAM = &TNC->Streams[Stream];
|
|
int LastInSeq = Input[1] - 32;
|
|
int LastRXed = Input[2] - 32;
|
|
int FirstUnAcked = ARQ->TXLastACK;
|
|
int n = strlen(Input) - 3;
|
|
char * ptr;
|
|
int NexttoResend;
|
|
int First, Last, Outstanding;
|
|
PMSGWITHLEN Buffer;
|
|
int Acked = 0;
|
|
|
|
// First status is an ack of Connect ACK
|
|
|
|
if (ARQ->ARQTimerState == ARQ_CONNECTACK)
|
|
{
|
|
ARQ->Retries = 0;
|
|
ARQ->ARQTimer = 0;
|
|
ARQ->ARQTimerState = 0;
|
|
|
|
strcpy(TNC->WEB_PROTOSTATE, "Connected");
|
|
SetWindowText(STREAM->xIDC_STATUS, "Connected");
|
|
}
|
|
|
|
Debugprintf("Lsast In Seq, LastRXed %d %d", LastInSeq, LastRXed);
|
|
|
|
// Release all up to LastInSeq
|
|
|
|
while (FirstUnAcked != LastInSeq)
|
|
{
|
|
FirstUnAcked++;
|
|
FirstUnAcked &= 63;
|
|
|
|
Buffer = ARQ->TXHOLDQ[FirstUnAcked];
|
|
|
|
if (Buffer)
|
|
{
|
|
Debugprintf("Acked %d", FirstUnAcked);
|
|
STREAM->BytesAcked += Buffer->Len;
|
|
ReleaseBuffer(Buffer);
|
|
ARQ->TXHOLDQ[FirstUnAcked] = NULL;
|
|
Acked++;
|
|
}
|
|
}
|
|
|
|
ARQ->TXLastACK = FirstUnAcked;
|
|
|
|
Outstanding = ARQ->TXSeq - ARQ->TXLastACK;
|
|
|
|
if (Outstanding < 0)
|
|
Outstanding += 64;
|
|
|
|
TNC->PortRecord->FramesQueued = Outstanding + C_Q_COUNT(&STREAM->BPQtoPACTOR_Q); // Save for Appl Level Queued Frames
|
|
|
|
if (FirstUnAcked == ARQ->TXSeq)
|
|
{
|
|
UpdateStatsLine(TNC, STREAM);
|
|
ARQ->NoAckRetries = 0;
|
|
|
|
strcpy(TNC->WEB_PROTOSTATE, "Connected");
|
|
SetWindowText(STREAM->xIDC_STATUS, "Connected");
|
|
|
|
return; // All Acked
|
|
}
|
|
|
|
// Release any not in retry list up to LastRXed.
|
|
|
|
ptr = &Input[3];
|
|
|
|
while (n)
|
|
{
|
|
NexttoResend = *(ptr++) - 32;
|
|
|
|
FirstUnAcked++;
|
|
FirstUnAcked &= 63;
|
|
|
|
while (FirstUnAcked != NexttoResend)
|
|
{
|
|
Buffer = ARQ->TXHOLDQ[FirstUnAcked];
|
|
|
|
if (Buffer)
|
|
{
|
|
Debugprintf("Acked %d", FirstUnAcked);
|
|
STREAM->BytesAcked += Buffer->Len;
|
|
ReleaseBuffer(Buffer);
|
|
ARQ->TXHOLDQ[FirstUnAcked] = NULL;
|
|
Acked++;
|
|
}
|
|
|
|
FirstUnAcked++;
|
|
FirstUnAcked &= 63;
|
|
}
|
|
|
|
// We don't ACK this one. Process any more resend values, then release up to LastRXed.
|
|
|
|
n--;
|
|
}
|
|
|
|
// Release rest up to LastRXed
|
|
|
|
while (FirstUnAcked != LastRXed)
|
|
{
|
|
FirstUnAcked++;
|
|
FirstUnAcked &= 63;
|
|
|
|
Buffer = ARQ->TXHOLDQ[FirstUnAcked];
|
|
|
|
if (Buffer)
|
|
{
|
|
Debugprintf("Acked %d", FirstUnAcked);
|
|
STREAM->BytesAcked += Buffer->Len;
|
|
ReleaseBuffer(Buffer);
|
|
ARQ->TXHOLDQ[FirstUnAcked] = NULL;
|
|
Acked++;
|
|
}
|
|
}
|
|
|
|
// Resend anything in TX Buffer (From LastACK to TXSeq
|
|
|
|
Last = ARQ->TXSeq + 1;
|
|
Last &= 63;
|
|
|
|
First = LastInSeq;
|
|
|
|
while (First != Last)
|
|
{
|
|
First++;
|
|
First &= 63;
|
|
|
|
if(ARQ->TXHOLDQ[First])
|
|
{
|
|
PMSGWITHLEN Buffer = ARQ->TXHOLDQ[First];
|
|
UCHAR TXBuffer[300];
|
|
SOCKET sock = TNC->TCPDataSock;
|
|
int SendLen;
|
|
|
|
Debugprintf("Resend %d", First);
|
|
|
|
STREAM->BytesResent += Buffer->Len;
|
|
|
|
SendLen = sprintf(TXBuffer, "%c", First + 32);
|
|
|
|
memcpy(&TXBuffer[SendLen], Buffer->Data, Buffer->Len);
|
|
SendLen += Buffer->Len;
|
|
|
|
TXBuffer[SendLen] = 0;
|
|
|
|
SendPacket(TNC, STREAM, TXBuffer, SendLen);
|
|
|
|
ARQ->ARQTimer = 10; // wait up to 1 sec for more data before polling
|
|
ARQ->Retries = 1;
|
|
ARQ->ARQTimerState = ARQ_WAITDATA;
|
|
|
|
if (Acked == 0)
|
|
{
|
|
// Nothing acked by this statis message
|
|
|
|
Acked = 1; // Dont count more thna once
|
|
ARQ->NoAckRetries++;
|
|
if (ARQ->NoAckRetries > TNC->Retries)
|
|
{
|
|
// Too many retries - just disconnect
|
|
|
|
TidyClose(TNC, Stream);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
UpdateStatsLine(TNC, STREAM);
|
|
}
|
|
|