From ba4a34b3a537764fef47abbfbde74a36bd02c811 Mon Sep 17 00:00:00 2001 From: g8bpq Date: Thu, 28 Nov 2024 19:11:21 +0000 Subject: [PATCH] 6.0.24.51 --- AGWAPI.c | 125 +- APRSCode.c | 3 +- BBSUtilities.c | 80 +- BPQMail.c | 18 +- BPQMail.vcproj.LAPTOP-Q6S4RP5Q.johnw.user | 65 + BPQMail.vcproj.SKIGACER.johnw.user | 65 + BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user | 65 + Bpq32.c | 18 +- CBPQ32.vcproj.LAPTOP-Q6S4RP5Q.johnw.user | 65 + CBPQ32.vcproj.SKIGACER.johnw.user | 65 + CHeaders.h | 1 + Cmd-skigdebian.c | 5989 +++++++++++++++++ CommonCode.c | 33 +- FormatHTML.vcproj.LAPTOP-Q6S4RP5Q.johnw.user | 65 + L2Code-skigdebian.c | 4143 ++++++++++++ L4Code-skigdebian.c | 2416 +++++++ LinBPQ.c | 12 + MailDataDefs.c | 1 + MailNode.vcproj.LAPTOP-Q6S4RP5Q.johnw.user | 65 + MailNode.vcproj.SKIGACER.johnw.user | 65 + MailTCP.c | 3 +- Versions.h | 6 +- ...rControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user | 65 + adif.c | 1 - bpqmail.h | 3 + cMain.c | 17 +- config.c | 17 +- datadefs.c | 4 + lzhuf32.c | 3 + mailapi.c | 389 +- xpaho-mqtt3a.dll | Bin 101376 -> 0 bytes 31 files changed, 13778 insertions(+), 89 deletions(-) create mode 100644 BPQMail.vcproj.LAPTOP-Q6S4RP5Q.johnw.user create mode 100644 BPQMail.vcproj.SKIGACER.johnw.user create mode 100644 BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user create mode 100644 CBPQ32.vcproj.LAPTOP-Q6S4RP5Q.johnw.user create mode 100644 CBPQ32.vcproj.SKIGACER.johnw.user create mode 100644 Cmd-skigdebian.c create mode 100644 FormatHTML.vcproj.LAPTOP-Q6S4RP5Q.johnw.user create mode 100644 L2Code-skigdebian.c create mode 100644 L4Code-skigdebian.c create mode 100644 MailNode.vcproj.LAPTOP-Q6S4RP5Q.johnw.user create mode 100644 MailNode.vcproj.SKIGACER.johnw.user create mode 100644 WinmorControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user delete mode 100644 xpaho-mqtt3a.dll diff --git a/AGWAPI.c b/AGWAPI.c index 0f2d86c..80c1ce4 100644 --- a/AGWAPI.c +++ b/AGWAPI.c @@ -1017,6 +1017,7 @@ int AGWDataSocket_Read(struct AGWSocketConnectionInfo * sockptr, SOCKET sock) { int i; int DataLength; + struct AGWHeader * AGW = &sockptr->AGWRXHeader; ioctlsocket(sock,FIONREAD,&DataLength); @@ -1028,84 +1029,98 @@ int AGWDataSocket_Read(struct AGWSocketConnectionInfo * sockptr, SOCKET sock) return 0; } - - if (sockptr->GotHeader) + if (DataLength < 36) // A header { - // Received a header, without sufficient data bytes - - if (DataLength < sockptr->MsgDataLength) - { - // Fiddle - seem to be problems somtimes with un-Neagled hosts - - Sleep(500); + // If we don't get a header within a few ms assume a rogue connection and close it + + int n = 50; + while (n--) + { + Sleep(10); ioctlsocket(sock,FIONREAD,&DataLength); + + if (DataLength >= 36) + break; } - - if (DataLength >= sockptr->MsgDataLength) - { - // Read Data and Process Command - sockptr->MsgData = malloc(sockptr->MsgDataLength); - - i = recv(sock, sockptr->MsgData, sockptr->MsgDataLength, 0); - - ProcessAGWCommand (sockptr); - free(sockptr->MsgData); - - sockptr->GotHeader = FALSE; + if (n < 1) + { + Debugprintf("Corrupt AGW Packet Received"); + AGWDataSocket_Disconnect(sockptr); + return 0; } + } - // Not Enough Data - wait + // Have a header - } - else // Not got header + i=recv(sock,(char *)&sockptr->AGWRXHeader, 36, 0); + + if (i == SOCKET_ERROR) { - if (DataLength > 35)// A header - { - struct AGWHeader * AGW = &sockptr->AGWRXHeader; + i=WSAGetLastError(); + AGWDataSocket_Disconnect(sockptr); + } + + sockptr->MsgDataLength = sockptr->AGWRXHeader.DataLength; - i=recv(sock,(char *)&sockptr->AGWRXHeader, 36, 0); - - if (i == SOCKET_ERROR) - { - i=WSAGetLastError(); + // Validate packet to protect against accidental (or malicious!) connects from a non-agw application - AGWDataSocket_Disconnect(sockptr); - } + if (AGW->Port > 64 || AGW->filler2 != 0 || AGW->filler3 != 0 || AGW->DataLength > 400) + { + Debugprintf("Corrupt AGW Packet Received"); + AGWDataSocket_Disconnect(sockptr); + return 0; + } + + if (sockptr->MsgDataLength == 0) + ProcessAGWCommand (sockptr); + else + sockptr->GotHeader = TRUE; // Wait for data + ioctlsocket(sock,FIONREAD,&DataLength); // See if more data - sockptr->MsgDataLength = sockptr->AGWRXHeader.DataLength; - - // Validate packet to protect against accidental (or malicious!) connects from a non-agw application + if (sockptr->GotHeader) + { + // Received a header, without sufficient data bytes + + if (DataLength < sockptr->MsgDataLength) + { + // Fiddle - seem to be problems somtimes with un-Neagled hosts so wait a few ms + // if we don't get a full packet assume a rogue connection and close it + int n = 50; - if (AGW->Port > 64 || AGW->filler2 != 0 || AGW->filler3 != 0 || AGW->DataLength > 400) + while (n--) { - Debugprintf("Corrupt AGW Packet Received"); - AGWDataSocket_Disconnect(sockptr); - return 0; + Sleep(10); + ioctlsocket(sock,FIONREAD,&DataLength); + + if (DataLength >= sockptr->MsgDataLength) + break; } - if (sockptr->MsgDataLength > 500) - OutputDebugString("Corrupt AGW message"); - - - if (sockptr->MsgDataLength == 0) - { - ProcessAGWCommand (sockptr); - } - else + if (n < 1) { - sockptr->GotHeader = TRUE; // Wait for data + Debugprintf("Corrupt AGW Packet Received"); + AGWDataSocket_Disconnect(sockptr); + return 0; } - - } + } - // not got 36 bytes + if (DataLength >= sockptr->MsgDataLength) + { + // Read Data and Process Command + sockptr->MsgData = malloc(sockptr->MsgDataLength); + + i = recv(sock, sockptr->MsgData, sockptr->MsgDataLength, 0); + + ProcessAGWCommand (sockptr); + free(sockptr->MsgData); + sockptr->GotHeader = FALSE; + } } - return 0; } diff --git a/APRSCode.c b/APRSCode.c index ff1e322..b8f02f0 100644 --- a/APRSCode.c +++ b/APRSCode.c @@ -88,7 +88,7 @@ double myDistance(double laa, double loa, BOOL KM); struct STATIONRECORD * FindStation(char * Call, BOOL AddIfNotFound); int DecodeAPRSPayload(char * Payload, struct STATIONRECORD * Station); BOOL KillOldTNC(char * Path); -int FromLOC(char * Locator, double * pLat, double * pLon); + BOOL ToLOC(double Lat, double Lon , char * Locator); BOOL InternalSendAPRSMessage(char * Text, char * Call); void UndoTransparency(char * input); @@ -104,6 +104,7 @@ void ClearSavedMessages(); void GetSavedAPRSMessages(); static VOID GPSDConnect(void * unused); int CanPortDigi(int Port); +int FromLOC(char * Locator, double * pLat, double * pLon); extern int SemHeldByAPI; extern int APRSMONDECODE(); diff --git a/BBSUtilities.c b/BBSUtilities.c index 82344bd..56e08a0 100644 --- a/BBSUtilities.c +++ b/BBSUtilities.c @@ -5566,14 +5566,19 @@ BOOL CreateMessage(CIRCUIT * conn, char * From, char * ToCall, char * ATBBS, cha { if (_memicmp(ToCall, "rms:", 4) == 0) { - if (!FindRMS()) + // Could be ampr.org message + + if (!isAMPRMsg(ToCall)) { - nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); - return FALSE; + if (!FindRMS()) + { + nodeprintf(conn, "*** Error - Forwarding via RMS is not configured on this BBS\r"); + return FALSE; + } } - via=strlop(ToCall, ':'); _strupr(ToCall); + } else if (_memicmp(ToCall, "rms/", 4) == 0) { @@ -6877,7 +6882,7 @@ int CountMessagestoForward (struct UserInfo * user) if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) { n++; - continue; // So we dont count twice in Flag set and NTS MPS + continue; // So we dont count twice if Flag set and NTS MPS } // if an NTS MPS, also check for any matches @@ -6918,6 +6923,66 @@ int CountMessagestoForward (struct UserInfo * user) return n; } +int CountBytestoForward (struct UserInfo * user) +{ + // See if any messages are queued for this BBS. If so return total bytes queued + + int m, n=0; + struct MsgInfo * Msg; + int BBSNumber = user->BBSNumber; + int FirstMessage = FirstMessageIndextoForward; + + if ((user->flags & F_NTSMPS)) + FirstMessage = 1; + + for (m = FirstMessage; m <= NumberofMessages; m++) + { + Msg=MsgHddrPtr[m]; + + if ((Msg->status != 'H') && (Msg->status != 'D') && Msg->type && check_fwd_bit(Msg->fbbs, BBSNumber)) + { + n += Msg->length; + continue; // So we dont count twice if Flag set and NTS MPS + } + + // if an NTS MPS, also check for any matches + + if (Msg->type == 'T' && (user->flags & F_NTSMPS)) + { + struct BBSForwardingInfo * ForwardingInfo = user->ForwardingInfo; + int depth; + + if (Msg->status == 'N' && ForwardingInfo) + { + depth = CheckBBSToForNTS(Msg, ForwardingInfo); + + if (depth > -1 && Msg->Locked == 0) + { + n += Msg->length; + continue; + } + depth = CheckBBSAtList(Msg, ForwardingInfo, Msg->via); + + if (depth && Msg->Locked == 0) + { + n += Msg->length; + continue; + } + + depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, Msg->via); + + if (depth > -1 && Msg->Locked == 0) + { + n += Msg->length; + continue; + } + } + } + } + + return n; +} + int ListMessagestoForward(CIRCUIT * conn, struct UserInfo * user) { // See if any messages are queued for this BBS @@ -15823,6 +15888,11 @@ void SendMessageReadEvent(char * call, struct MsgInfo * Msg) } } +void SendMessageForwardedToM0LTE(char * call, struct MsgInfo * Msg) +{ +} + + void SendNewMessageEvent(char * call, struct MsgInfo * Msg) { if (reportMailEvents) diff --git a/BPQMail.c b/BPQMail.c index 08d5818..5d26cd5 100644 --- a/BPQMail.c +++ b/BPQMail.c @@ -1143,6 +1143,8 @@ // Semaphore calls to SaveConfig // Include SERVIC as valid from call (for Winlink Service messages) (49) // Attempt to detect line draw characters in Webmail (50) +// Fix sending ampr.org mail when RMS is not enabled (51) +// Send forwarding info tp packetnodes.spots.radio database (51) #include "bpqmail.h" #include "winstdint.h" @@ -1161,6 +1163,8 @@ FARPROCZ pGetLOC; FARPROCX pRefreshWebMailIndex; FARPROCX pRunEventProgram; FARPROCX pGetPortFrequency; +FARPROCX pSendWebRequest; +FARPROCX pGetLatLon; BOOL WINE = FALSE; @@ -1385,6 +1389,7 @@ char * CheckToAddress(CIRCUIT * conn, char * Addr); BOOL CheckifPacket(char * Via); int GetHTMLForms(); VOID GetPGConfig(); +void SendBBSDataToPktMap(); struct _EXCEPTION_POINTERS exinfox; @@ -1936,6 +1941,8 @@ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) pRefreshWebMailIndex = GetProcAddress(ExtDriver,"_RefreshWebMailIndex@0"); pRunEventProgram = GetProcAddress(ExtDriver,"_RunEventProgram@8"); pGetPortFrequency = GetProcAddress(ExtDriver,"_GetPortFrequency@8"); + pSendWebRequest = GetProcAddress(ExtDriver,"_SendWebRequest@16"); + pGetLatLon = GetProcAddress(ExtDriver,"_GetLatLon@8"); if (pGetLOC) @@ -2183,6 +2190,13 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) Debugprintf("|Enter HouseKeeping"); DoHouseKeeping(FALSE); } + + if (APIClock < NOW) + { + SendBBSDataToPktMap(); + APIClock = NOW + 7200; // Every 2 hours + } + tm = gmtime(&NOW); if (tm->tm_wday == 0) // Sunday @@ -3057,7 +3071,6 @@ static PSOCKADDR_IN psin; SOCKET sock; - BOOL Initialise() { int i, len; @@ -3383,6 +3396,9 @@ BOOL Initialise() CreatePipeThread(); GetPGConfig(); + + APIClock = 0; + return TRUE; } diff --git a/BPQMail.vcproj.LAPTOP-Q6S4RP5Q.johnw.user b/BPQMail.vcproj.LAPTOP-Q6S4RP5Q.johnw.user new file mode 100644 index 0000000..bed4096 --- /dev/null +++ b/BPQMail.vcproj.LAPTOP-Q6S4RP5Q.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/BPQMail.vcproj.SKIGACER.johnw.user b/BPQMail.vcproj.SKIGACER.johnw.user new file mode 100644 index 0000000..bbece07 --- /dev/null +++ b/BPQMail.vcproj.SKIGACER.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user b/BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user new file mode 100644 index 0000000..bed4096 --- /dev/null +++ b/BPQWinAPP.vcproj.LAPTOP-Q6S4RP5Q.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/Bpq32.c b/Bpq32.c index f7e1f59..1125b31 100644 --- a/Bpq32.c +++ b/Bpq32.c @@ -1086,7 +1086,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses // Add ? and * wildcards to NODES command (74) // Add Port RADIO config parameter (74) -// Version 6.0.24.1 August 2024 +// Version 6.0.24.1 August 2023 // Apply NODES command wildcard to alias as well a call (2) // Add STOPPORT/STARTPORT to VARA Driver (2) @@ -1234,6 +1234,7 @@ along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses // Add optional ATTACH time limit for VARA (48) // API format fixes (48) // AGWAPI Add protection against accidental connects from a non-agw application (50) +// Save MH and NODES every hour (51) #define CKernel @@ -1374,6 +1375,9 @@ extern struct _LINKTABLE * LINKS; extern int LINK_TABLE_LEN; extern int MAXLINKS; +extern double LatFromLOC; +extern double LonFromLOC; + extern int BPQHOSTAPI(); extern int INITIALISEPORTS(); @@ -3068,7 +3072,7 @@ SkipInit: if (AttachedProcesses < 2) { - if (AUTOSAVE == 1) + if (AUTOSAVE) SaveNodes(); if (AUTOSAVEMH) SaveMH(); @@ -6621,11 +6625,19 @@ int GetListeningPortsPID(int Port) return 0; // Not found } -DllExport char * APIENTRY GetLOC() +DllExport char * APIENTRY GetLOC() { return LOC; } +DllExport void APIENTRY GetLatLon(double * lat, double * lon) +{ + *lat = LatFromLOC; + *lon = LonFromLOC; + return; +} + + // UZ7HO Dll PTT interface // 1 ext_PTT_info diff --git a/CBPQ32.vcproj.LAPTOP-Q6S4RP5Q.johnw.user b/CBPQ32.vcproj.LAPTOP-Q6S4RP5Q.johnw.user new file mode 100644 index 0000000..fa74d31 --- /dev/null +++ b/CBPQ32.vcproj.LAPTOP-Q6S4RP5Q.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/CBPQ32.vcproj.SKIGACER.johnw.user b/CBPQ32.vcproj.SKIGACER.johnw.user new file mode 100644 index 0000000..6aa33d1 --- /dev/null +++ b/CBPQ32.vcproj.SKIGACER.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/CHeaders.h b/CHeaders.h index 13cb99d..aeef69c 100644 --- a/CHeaders.h +++ b/CHeaders.h @@ -398,6 +398,7 @@ extern int REALTIMETICKS; extern time_t CurrentSecs; extern time_t lastSlowSecs; +extern time_t lastSaveSecs; // SNMP Variables diff --git a/Cmd-skigdebian.c b/Cmd-skigdebian.c new file mode 100644 index 0000000..88189b1 --- /dev/null +++ b/Cmd-skigdebian.c @@ -0,0 +1,5989 @@ +/* +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. S"paclenee 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 +*/ + +// +// C replacement for cmd.asm +// +#define Kernel + +#define _CRT_SECURE_NO_DEPRECATE +#pragma data_seg("_BPQDATA") + +//#include "windows.h" +//#include "winerror.h" + + +#include "time.h" +#include "stdio.h" +#include +//#include "vmm.h" +//#include "SHELLAPI.H" + +#include "CHeaders.h" +#include "bpqaprs.h" +#include "kiss.h" + +#pragma pack() + +#include "tncinfo.h" +#include "telnetserver.h" + +//#include "GetVersion.h" + +//#define DllImport __declspec( dllimport ) +//#define DllExport __declspec( dllexport ) + +BOOL DecodeCallString(char * Calls, BOOL * Stay, BOOL * Spy, UCHAR *AXCalls); +VOID Send_AX_Datagram(PDIGIMESSAGE Block, DWORD Len, UCHAR Port); +int APIENTRY ClearNodes(); +VOID GetJSONValue(char * _REPLYBUFFER, char * Name, char * Value); +VOID SendHTTPRequest(SOCKET sock, char * Host, int Port, char * Request, char * Params, int Len, char * Return); +SOCKET OpenWL2KHTTPSock(); +VOID FormatTime3(char * Time, time_t cTime); +VOID Format_Addr(unsigned char * Addr, char * Output, BOOL IPV6); +VOID Tel_Format_Addr(struct ConnectionInfo * sockptr, char * dst); +VOID FindLostBuffers(); +BOOL CheckCMS(struct TNCINFO * TNC); +VOID L2SENDXID(struct _LINKTABLE * LINK); +int CountBits(unsigned long in); +VOID SaveMH(); +BOOL RestartTNC(struct TNCINFO * TNC); +void GetPortCTEXT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID WriteMiniDump(); +int CheckKissInterlock(struct PORTCONTROL * PORT, int Exclusive); +int seeifInterlockneeded(struct PORTCONTROL * PORT); + +extern VOID KISSTX(); + +char COMMANDBUFFER[81] = ""; // Command Hander input buffer +char OrigCmdBuffer[81] = ""; // Command Hander input buffer before toupper + +struct DATAMESSAGE * REPLYBUFFER = NULL; +UINT APPLMASK = 0; +UCHAR SAVEDAPPLFLAGS = 0; + +UCHAR ALIASINVOKED = 0; + + +VOID * CMDPTR = 0; + +short CMDPACLEN = 0; + +char OKMSG[] = "Ok\r"; + +char CMDERRMSG[] = "Invalid command - Enter ? for command list\r"; +#define CMDERRLEN sizeof(CMDERRMSG) - 1 + +char PASSWORDMSG[] = "Command requires SYSOP status - enter password\r"; +#define LPASSMSG sizeof(PASSWORDMSG) - 1 + +char CMDLIST[] = "CONNECT BYE INFO NODES PORTS ROUTES USERS MHEARD"; + +#define CMDLISTLEN sizeof(CMDLIST) - 1 + +char BADMSG[] = "Bad Parameter\r"; +char BADPORT[] = "Invalid Port Number\r"; +char NOTEXTPORT[] = "Only valid on EXT ports\r"; +char NOVALCALLS[] = "No Valid Calls defined on this port\r"; + +char BADVALUEMSG[] = "Invalid parameter\r"; + +char BADCONFIGMSG[] = "Configuration File check falled - will continue with old config\r"; +#ifdef LINBPQ +char REBOOTOK[] = "Rebooting\r"; +#else +char REBOOTOK[] = "Rebooting in 20 secs\r"; +#endif +char REBOOTFAILED[] = "Shutdown failed\r"; + +char RESTARTOK[] = "Restarting\r"; +char RESTARTFAILED[] = "Restart failed\r"; + +UCHAR ARDOP[7] = {'A'+'A','R'+'R','D'+'D','O'+'O','P'+'P',' '+' '}; // ARDOP IN AX25 +UCHAR VARA[7] = {'V'+'V','A'+'A','R'+'R','A'+'A',' '+' ',' '+' '}; // VARA IN AX25 + +int STATSTIME = 0; +int MAXBUFFS = 0; +int QCOUNT = 0; +int MINBUFFCOUNT = 65535; +int NOBUFFCOUNT = 0; +int BUFFERWAITS = 0; +int MAXDESTS = 0; +int NUMBEROFNODES = 0; +int L4CONNECTSOUT = 0; +int L4CONNECTSIN = 0; +int L4FRAMESTX = 0; +int L4FRAMESRX = 0; +int L4FRAMESRETRIED = 0; +int OLDFRAMES = 0; +int L3FRAMES = 0; + +VOID SENDSABM(); +VOID RESET2(); + +int APPL1 = 0; +int PASSCMD = 0; + +#pragma pack(1) + +struct _EXTPORTDATA DP; // Only way I can think of to get offets to port data into cmd table + +char CMDALIAS[ALIASLEN][NumberofAppls] = {0}; +char * ALIASPTR = &CMDALIAS[0][0]; + +extern int RigReconfigFlag; + +CMDX COMMANDS[]; + +int CMDXLEN = sizeof (CMDX); + +VOID SENDNODESMSG(); +VOID KISSCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID STOPCMS(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID STARTCMS(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID STOPPORT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID STARTPORT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID FINDBUFFS(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID WL2KSYSOP(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID AXRESOLVER(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID AXMHEARD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID SHOWTELNET(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID SHOWAGW(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID SHOWARP(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID SHOWNAT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID PING(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID SHOWIPROUTE(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID FLMSG(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * UserCMD); +void ListExcludedCalls(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID APRSCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID RECONFIGTELNET (TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID HELPCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); +VOID UZ7HOCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * UserCMD); +VOID QTSMCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * UserCMD); +void hookL2SessionAttempt(int Port, char * fromCall, char * toCall, struct _LINKTABLE * LINK); + + + +char * __cdecl Cmdprintf(TRANSPORTENTRY * Session, char * Bufferptr, const char * format, ...) +{ + // Send Command response checking PACLEN + + char Mess[4096]; + va_list(arglist); + int OldLen; + int MsgLen; + struct DATAMESSAGE * Buffer; + char * Messptr = Mess; + int Paclen = Session->SESSPACLEN; + + if (Paclen == 0) + Paclen = 255; + + va_start(arglist, format); + + MsgLen = vsprintf(Mess, format, arglist); + + OldLen = (int)(Bufferptr - (char *)REPLYBUFFER->L2DATA); + + while ((OldLen + MsgLen) > Paclen) + { + // Have to send Paclen then get a new buffer + + int ThisBit = Paclen - OldLen; // What we can send this time + + if (ThisBit < 0) + ThisBit = 0; // How can this happen?? + + memcpy(Bufferptr, Messptr, ThisBit); + Messptr += ThisBit; + MsgLen -= ThisBit; + + // QUEUE IT AND GET ANOTHER BUFFER + + Buffer = (struct DATAMESSAGE *)GetBuff(); + + if (Buffer == NULL) + + // No buffers, so just reuse the old one (better than crashing !!) + + Buffer = REPLYBUFFER; + else + SendCommandReply(Session, REPLYBUFFER, Paclen + (4 + sizeof(void *))); + + + REPLYBUFFER = Buffer; + Buffer->PID = 0xf0; + + Bufferptr = &Buffer->L2DATA[0]; + OldLen = 0; + } + + // Add last bit to buffer + + memcpy(Bufferptr, Messptr, MsgLen); + + return Bufferptr + MsgLen; +} + + +VOID SENDNODES(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + SENDNODESMSG(); + + strcpy(Bufferptr, OKMSG); + Bufferptr += (int)strlen(OKMSG); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID SAVEMHCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + SaveMH(); + + strcpy(Bufferptr, OKMSG); + Bufferptr += (int)strlen(OKMSG); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID SAVENODES(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + SaveNodes(); + + strcpy(Bufferptr, OKMSG); + Bufferptr += (int)strlen(OKMSG); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID DUMPCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + WriteMiniDump(); + + strcpy(Bufferptr, OKMSG); + Bufferptr += (int)strlen(OKMSG); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID RIGRECONFIG(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + if (!ProcessConfig()) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Configuration File check falled - will continue with old config"); + } + else + { + RigReconfigFlag = TRUE; + Bufferptr = Cmdprintf(Session, Bufferptr, "Rigcontrol Reconfig requested"); + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID REBOOT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + if (Reboot()) + { + strcpy(Bufferptr, REBOOTOK); + Bufferptr += (int)strlen(REBOOTOK); + } + else + { + strcpy(Bufferptr, REBOOTFAILED); + Bufferptr += (int)strlen(REBOOTFAILED); + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID RESTART(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + if (Restart()) + { + strcpy(Bufferptr, RESTARTOK); + Bufferptr += (int)strlen(RESTARTOK); + } + else + { + strcpy(Bufferptr, RESTARTFAILED); + Bufferptr += (int)strlen(RESTARTFAILED); + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID RESTARTTNC(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char * ptr, *Context; + int portno; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + { + portno = atoi (ptr); + + if (portno && portno < 33) + { + struct TNCINFO * TNC = TNCInfo[portno]; + + if (TNC == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + } + else + { + if (TNC->ProgramPath) + { + if (RestartTNC(TNC)) + Bufferptr = Cmdprintf(Session, Bufferptr, "Restart %s Ok\r", TNC->ProgramPath); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Restart %s Failed\r", TNC->ProgramPath); + } + else + { + Bufferptr = Cmdprintf(Session, Bufferptr, "PATH not defined so can't restart TNC\r"); + } + } + } + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +UCHAR VALNODESFLAG = 0, EXTONLY = 0; + +VOID PORTVAL (TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD); + +VOID VALNODES(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + VALNODESFLAG = 1; + PORTVAL(Session, Bufferptr, CmdTail, CMD); +} + +VOID EXTPORTVAL(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + EXTONLY = 1; + PORTVAL(Session, Bufferptr, CmdTail, CMD); +} +VOID PORTVAL(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // PROCESS PORT VALUE COMMANDS + + char * ptr, *Context, * ptr1; + int portno; + UCHAR oldvalue, newvalue; + struct PORTCONTROL * PORT = PORTTABLE; + int n = NUMBEROFPORTS; + UCHAR * valueptr; + + // Get port number + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + { + portno = atoi (ptr); + + if (portno) + { + while (n--) + { + if (PORT->PORTNUMBER == portno) + { + if (VALNODESFLAG) + { + char * VNPtr = PORT->PERMITTEDCALLS; + char Normcall[10]; + + VALNODESFLAG = 0; + + if (VNPtr) + { + while (VNPtr[0]) + { + Normcall[ConvFromAX25(VNPtr, Normcall)] = 0; + Bufferptr = Cmdprintf(Session, Bufferptr, "%s ", Normcall); + VNPtr += 7; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + } + else + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", NOVALCALLS); + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + + return; + + } + + if (EXTONLY) + { + // Make sure an Extenal Port + + EXTONLY = 0; + + if (PORT->PORTTYPE != 0x10) + { + strcpy(Bufferptr, NOTEXTPORT); + Bufferptr += (int)strlen(NOTEXTPORT); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + } + + valueptr = (UCHAR *)PORT + CMD->CMDFLAG; + oldvalue = *valueptr; + + // Display Param Namee + + ptr1 = &CMD->String[0]; + n = 12; + + while (*(ptr1) != ' ' && n--) + *(Bufferptr++) = *(ptr1++); + + // See if another param - if not, just display current value + + ptr = strtok_s(NULL, " ", &Context); + + if (ptr && ptr[0]) + { + // Get new value + + newvalue = atoi(ptr); + *valueptr = newvalue; + + Bufferptr = Cmdprintf(Session, Bufferptr, " was %d now %d\r", oldvalue, newvalue); + } + + else + Bufferptr = Cmdprintf(Session, Bufferptr, " %d\r", oldvalue); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + + } + PORT = PORT->PORTPOINTER; + } + } + } + + // Bad port + + strcpy(Bufferptr, BADPORT); + Bufferptr += (int)strlen(BADPORT); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + +} + +VOID SWITCHVAL (TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // Update switch 8 bit value + + char * ptr, *Context, * ptr1; + UCHAR oldvalue, newvalue; + int n; + UCHAR * valueptr; + + valueptr = (UCHAR *)CMD->CMDFLAG; + + oldvalue = *valueptr; + + // Display Param Name + + ptr1 = &CMD->String[0]; + n = 12; + + while (*(ptr1) != ' ' && n--) + *(Bufferptr++) = *(ptr1++); + + // See if a param - if not, just display current value + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr && ptr[0]) + { + // Get new value + + newvalue = atoi(ptr); + *valueptr = newvalue; + + Bufferptr = Cmdprintf(Session, Bufferptr, " was %d now %d\r", oldvalue, newvalue); + + if (memcmp(CMD->String, "NODESINT ", 8) == 0) + L3TIMER = L3INTERVAL; + } + else + Bufferptr = Cmdprintf(Session, Bufferptr, " %d\r", oldvalue); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + +} + +VOID SWITCHVALW (TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // Update switch 16 bit value + + char * ptr, *Context, * ptr1; + USHORT oldvalue, newvalue; + int n; + USHORT * valueptr; + + valueptr = (USHORT *)CMD->CMDFLAG; + + oldvalue = (USHORT)*valueptr; + + // Display Param Name + + ptr1 = &CMD->String[0]; + n = 12; + + while (*(ptr1) != ' ' && n--) + *(Bufferptr++) = *(ptr1++); + + // See if a param - if not, just display current value + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr && ptr[0]) + { + // Get new value + + newvalue = atoi(ptr); + *valueptr = newvalue; + + Bufferptr = Cmdprintf(Session, Bufferptr, " was %d now %d\r", oldvalue, newvalue); + } + else + Bufferptr = Cmdprintf(Session, Bufferptr, " %d\r", oldvalue); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + +} + +TRANSPORTENTRY * SetupSessionFromSession(TRANSPORTENTRY * Session, PBPQVECSTRUC HOSTSESS, UINT APPLMASK) +{ + // Create a Transport (L4) session linked to an incoming Session + + TRANSPORTENTRY * NewSess = L4TABLE; + int Index = 0; + + while (Index < MAXCIRCUITS) + { + if (NewSess->L4USER[0] == 0) + { + // Got One + + UCHAR * ourcall = &MYCALL[0]; + + Session->L4CROSSLINK = NewSess; + NewSess->L4CROSSLINK = Session; + + if (APPLMASK) + { + // Circuit for APPL - look for an APPLCALL + + APPLCALLS * APPL = APPLCALLTABLE; + + while ((APPLMASK & 1) == 0) + { + APPLMASK >>= 1; + APPL++; + } + if (APPL->APPLCALL[0] > 0x40) // We have an applcall + ourcall = &APPL->APPLCALL[0]; + } + + memcpy(NewSess->L4USER, ourcall, 7); + memcpy(NewSess->L4MYCALL, Session->L4MYCALL, 7); + + NewSess->CIRCUITINDEX = Index; //OUR INDEX + NewSess->CIRCUITID = NEXTID; + + NEXTID++; + if (NEXTID == 0) + NEXTID++; // kEEP nON-ZERO + + NewSess->SESSIONT1 = Session->SESSIONT1; + NewSess->L4WINDOW = (UCHAR)L4DEFAULTWINDOW; + NewSess->SESSPACLEN = PACLEN; // Default; + + NewSess->L4TARGET.HOST = HOSTSESS; + NewSess->L4STATE = 5; + return NewSess; + } + Index++; + NewSess++; + } + return NULL; +} + +extern int GETCONNECTIONINFO(); + + +BOOL cATTACHTOBBS(TRANSPORTENTRY * Session, UINT Mask, int Paclen, int * AnySessions) +{ + PBPQVECSTRUC HOSTSESS = BPQHOSTVECTOR; + TRANSPORTENTRY * NewSess; + int ApplNum; + int n = BPQHOSTSTREAMS; + int ConfigedPorts = 0; + + // LOOK FOR A FREE HOST SESSION + + while (n--) + { + if (HOSTSESS->HOSTAPPLMASK & Mask) + { + // Right appl + + ConfigedPorts++; + + if (HOSTSESS->HOSTSESSION == NULL && (HOSTSESS->HOSTFLAGS & 3) == 0) // Not attached and no report outstanding + { + // WEVE GOT A FREE BPQ HOST PORT - USE IT + + NewSess = SetupSessionFromSession(Session, HOSTSESS, Mask); + + if (NewSess == NULL) + return FALSE; // Appl not available + + HOSTSESS->HOSTSESSION = NewSess; + + // Convert APPLMASK to APPLNUM + + ApplNum = 1; + + while (APPLMASK && (APPLMASK & 1) == 0) + { + ApplNum++; + APPLMASK >>= 1; + } + + HOSTSESS->HOSTAPPLNUM = ApplNum; + + HOSTSESS->HOSTFLAGS |= 2; // Indicate State Change + + NewSess->L4CIRCUITTYPE = BPQHOST | DOWNLINK; + + PostStateChange(NewSess); + + NewSess->SESS_APPLFLAGS = HOSTSESS->HOSTAPPLFLAGS; + + NewSess->SESSPACLEN = Paclen; + + return TRUE; + } + } + HOSTSESS++; + } + + *AnySessions = ConfigedPorts; // to distinguish between none and all in use + return FALSE; +} + +VOID APPLCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + BOOL CONFAILED = 0; + UINT CONERROR ; + char APPName[13]; + char * ptr1, *ptr2; + int n = 12; + BOOL Stay = FALSE; + + // Copy Appl and Null Terminate + + ptr1 = &CMD->String[0]; + ptr2 = APPName; + + while (*(ptr1) != ' ' && n--) + *(ptr2++) = *(ptr1++); + + *(ptr2) = 0; + + if (Session->LISTEN) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Can't use %s while listening\r", APPName); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + + if (CmdTail[0] == 'S') + Stay = TRUE; + + Session->STAYFLAG = Stay; + + memcpy(Session->APPL, CMD->String, 12); + + // SEE IF THERE IS AN ALIAS DEFINDED FOR THIS COMMAND + + if (ALIASPTR[0] > ' ') + { + // COPY ALIAS TO COMMAND BUFFER, THEN REENTER COMMAND HANDLER + + int SaveSecure = Session->Secure_Session; + + memcpy(COMMANDBUFFER, ALIASPTR, ALIASLEN); + _strupr(COMMANDBUFFER); + memcpy(OrigCmdBuffer, ALIASPTR, ALIASLEN); // In case original case version needed + + ALIASINVOKED = 1; // To prevent Alias Loops + + // Set secure session for application alias in case telnet outward connect + + Session->Secure_Session = 1; + DoTheCommand(Session); + Session->Secure_Session = SaveSecure; + + return; + } + + if (cATTACHTOBBS(Session, APPLMASK, CMDPACLEN, &CONERROR) == 0) + { + // No Streams + + if (CONERROR) + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, All %s Ports are in use - Please try later\r", APPName); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, Application %s is not running - Please try later\r", APPName); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // IF CMD_TO_APPL SET IN APPLFLAGS, SEND INPUT MSG TO APPL + + if (Session->L4CROSSLINK->SESS_APPLFLAGS & CMD_TO_APPL) + { + struct DATAMESSAGE * Msg = (struct DATAMESSAGE *)GetBuff(); + TRANSPORTENTRY * XSession = Session->L4CROSSLINK; + + if (Msg) + { + COMMANDBUFFER[72] = 13; + memcpy(Msg->L2DATA, COMMANDBUFFER, 73); + Msg->LENGTH = 73 + 4 + sizeof(void *); + Msg->PID = 0xf0; + + C_Q_ADD(&XSession->L4TX_Q, (UINT *)Msg); + PostDataAvailable(XSession); + } + } + + if (Stay) + Session->L4CROSSLINK->L4TARGET.HOST->HOSTFLAGS |= 0x20; + + // IF MSG_TO_USER SET, SEND 'CONNECTED' MESSAGE TO USER + + Session->SESS_APPLFLAGS = Session->L4CROSSLINK->SESS_APPLFLAGS; + + if (Session->L4CROSSLINK->SESS_APPLFLAGS & MSG_TO_USER) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Connected to %s\r", APPName); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + + // DONT NEED BUFFER ANY MORE + + ReleaseBuffer((UINT *)REPLYBUFFER); + return; +} + + +VOID CMDI00(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", INFOMSG); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID CMDV00(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + if (sizeof(void *) == 4) + Bufferptr = Cmdprintf(Session, Bufferptr, "Version %s\r", VersionString); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Version %s (64 bit)\r", VersionString); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID BYECMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + CLOSECURRENTSESSION(Session); // Kills any crosslink, plus local link + ReleaseBuffer((UINT *)REPLYBUFFER); + return; +} + +VOID CMDPAC(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // SET PACLEN FOR THIS SESSION + + char * ptr, *Context; + int newvalue; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr && ptr[0]) + { + // Get new value + + newvalue = atoi(ptr); + if (newvalue > 29 && newvalue < 256) + Session->SESSPACLEN = newvalue & 0xff; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "PACLEN - %d\r", Session->SESSPACLEN); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID CMDIDLE(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // SET IDLETIME FOR THIS SESSION + + char * ptr, *Context; + int newvalue; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr && ptr[0]) + { + // Get new value + + newvalue = atoi(ptr); + if (newvalue > 59 && newvalue < 901) + Session->L4LIMIT = newvalue; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "IDLETIME - %d\r", Session->L4LIMIT); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + +} +VOID CMDT00(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // SET L4 TIMEOUT FOR CONNECTS ON THIS SESSION + + char * ptr, *Context; + int newvalue; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr && ptr[0]) + { + // Get new value + + newvalue = atoi(ptr); + if (newvalue > 20) + Session->SESSIONT1 = newvalue; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "L4TIMEOUT - %d\r", Session->SESSIONT1); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +UCHAR PWLen; +char PWTEXT[80]; + +VOID PWDCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char * ptr, *Context; + USHORT pwsum = 0; + int n = 5, p1, p2, p3, p4, p5; + + if (Session->Secure_Session) // HOST - SET AUTHORISED REGARDLESS + { + Session->PASSWORD = 0xFFFF; // SET AUTHORISED + Session->Secure_Session = 1; + strcpy(Bufferptr, OKMSG); + Bufferptr += (int)strlen(OKMSG); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr && ptr[0]) + { + // Check Password + + n = 5; + + while (n--) + pwsum += *(ptr++); + + if (Session->PASSWORD == pwsum) + { + Session->PASSWORD = 0xFFFF; // SET AUTHORISED + Session->Secure_Session = 1; + strcpy(Bufferptr, OKMSG); + Bufferptr += (int)strlen(OKMSG); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + ReleaseBuffer((UINT *)REPLYBUFFER); + return; + } + + // SEND PASSWORD PROMPT + + if (PWLen == 0) + PWLen = 1; + + p1 = rand() % PWLen; + pwsum += PWTEXT[p1++]; + + p2 = rand() % PWLen; + pwsum += PWTEXT[p2++]; + + p3 = rand() % PWLen; + pwsum += PWTEXT[p3++]; + + p4 = rand() % PWLen; + pwsum += PWTEXT[p4++]; + + p5 = rand() % PWLen; + pwsum += PWTEXT[p5++]; + + Session->PASSWORD = pwsum; + + Bufferptr = Cmdprintf(Session, Bufferptr, "%d %d %d %d %d\r", p1, p2, p3, p4, p5); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + +VOID CMDSTATS(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char * ptr, *Context; + int Port = 0, cols = NUMBEROFPORTS, i; + char * uptime; + struct PORTCONTROL * PORT = PORTTABLE; + struct PORTCONTROL * STARTPORT; + + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + // SEE IF ANY PARAM + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr && ptr[0]) + Port = atoi(ptr); + + // IF ASKING FOR PORT STATS, DONT DO SYSTEM ONES + + if (Port == 0) + { + uptime = FormatUptime(STATSTIME); + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", uptime); + + Bufferptr = Cmdprintf(Session, Bufferptr, "Semaphore Get-Rel/Clashes %9d%9d\r", + Semaphore.Gets - Semaphore.Rels, Semaphore.Clashes); + + Bufferptr = Cmdprintf(Session, Bufferptr, "Buffers:Max/Cur/Min/Out/Wait%9d%9d%9d%9d%9d\r", + MAXBUFFS, QCOUNT, MINBUFFCOUNT, NOBUFFCOUNT, BUFFERWAITS); + + Bufferptr = Cmdprintf(Session, Bufferptr, "Known Nodes/Max Nodes %9d%9d\r", + NUMBEROFNODES, MAXDESTS); + + Bufferptr = Cmdprintf(Session, Bufferptr, "L4 Connects Sent/Rxed %9d%9d\r", + L4CONNECTSOUT, L4CONNECTSIN); + + Bufferptr = Cmdprintf(Session, Bufferptr, "L4 Frames TX/RX/Resent/Reseq%9d%9d%9d%9d\r", + L4FRAMESTX, L4FRAMESRX, L4FRAMESRETRIED, OLDFRAMES); + + Bufferptr = Cmdprintf(Session, Bufferptr, "L3 Frames Relayed %9d\r", L3FRAMES); + + if (ptr && ptr[0] == 'S') + { + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + } + + // POSITION TO REQUESTED PORT + + if (Port) + { + while (PORT && PORT->PORTNUMBER != Port) + { + PORT = PORT->PORTPOINTER; + cols--; + } + } + + if (PORT == NULL) // REQUESTED PORT NOT FOUND + { + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + STARTPORT = PORT; + + if (cols > 7) + cols = 7; + + Bufferptr = Cmdprintf(Session, Bufferptr, " "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Port %02d ", PORT->PORTNUMBER); + PORT = PORT->PORTPOINTER; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "L2 Frames Digied"); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2DIGIED); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "L2 Frames Heard "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2FRAMES); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "L2 Frames Rxed "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2FRAMESFORUS); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "L2 Frames Sent "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2FRAMESSENT); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "L2 Timeouts "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2TIMEOUTS); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "REJ Frames Rxed "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2REJCOUNT); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "RX out of Seq "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2OUTOFSEQ); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "L2 Resequenced "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2RESEQ); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "Undrun/Poll T/o "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2URUNC); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "RX Overruns "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2ORUNC); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "RX CRC Errors "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->RXERRORS); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "FRMRs Sent "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2FRMRTX); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "FRMRs Received "); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L2FRMRRX); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; + Bufferptr = Cmdprintf(Session, Bufferptr, "Frames abandoned"); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%9d", PORT->L1DISCARD); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + PORT = STARTPORT; +// Bufferptr = Cmdprintf(Session, Bufferptr, "Link Active %% "); + Bufferptr = Cmdprintf(Session, Bufferptr, "Active(TX/Busy) %%"); + + for (i = 0; i < cols; i++) + { + Bufferptr = Cmdprintf(Session, Bufferptr, " %2d %3d ", PORT->AVSENDING, PORT->AVACTIVE); + PORT = PORT->PORTPOINTER; + } + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID CMDL00(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // PROCESS 'LINKS' MESSAGE + + struct _LINKTABLE * LINK = LINKS; + int n = MAXLINKS; + int len; + char Normcall[11] = ""; + + Bufferptr = Cmdprintf(Session, Bufferptr, "Links\r"); + + while (n--) + { + if (LINK->LINKCALL[0]) + { + len = ConvFromAX25(LINK->LINKCALL, Normcall); + + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", Normcall); + + len = ConvFromAX25(LINK->OURCALL, Normcall); + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", Normcall); + + if (LINK->Ver2point2) + Bufferptr = Cmdprintf(Session, Bufferptr, " S=%d P=%d T=%d V=2.2\r", + LINK->L2STATE, LINK->LINKPORT->PORTNUMBER, LINK->LINKTYPE); + else + Bufferptr = Cmdprintf(Session, Bufferptr, " S=%d P=%d T=%d V=%d\r", + LINK->L2STATE, LINK->LINKPORT->PORTNUMBER, LINK->LINKTYPE, 2 - LINK->VER1FLAG); + } + LINK++; + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + + +VOID CMDS00(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // PROCESS 'USERS' + + int n = MAXCIRCUITS; + TRANSPORTENTRY * L4 = L4TABLE; + TRANSPORTENTRY * Partner; + int MaxLinks = MAXLINKS; + char State[12] = "", Type[12] = "Uplink"; + char LHS[50] = "", MID[10] = "", RHS[50] = ""; + char Line[100]; + + Bufferptr = Cmdprintf(Session, Bufferptr, "%s%d)\r", SESSIONHDDR, QCOUNT); + + while (n--) + { + if (L4->L4USER[0]) + { + RHS[0] = MID[0] = 0; + + if ((L4->L4CIRCUITTYPE & UPLINK) == 0) //SHORT CMDS10A ; YES + { + // IF DOWNLINK, ONLY DISPLAY IF NO CROSSLINK + + if (L4->L4CROSSLINK == 0) //jne CMDS60 ; WILL PROCESS FROM OTHER END + { + // ITS A DOWNLINK WITH NO PARTNER - MUST BE A CLOSING SESSION + // DISPLAY TO THE RIGHT FOR NOW + + strcpy(LHS, "(Closing) "); + DISPLAYCIRCUIT(L4, RHS); + goto CMDS50; + } + else + goto CMDS60; // WILL PROCESS FROM OTHER END + } + + if (L4->L4CROSSLINK == 0) + { + // Single Entry + + DISPLAYCIRCUIT(L4, LHS); + } + else + { + DISPLAYCIRCUIT(L4, LHS); + + Partner = L4->L4CROSSLINK; + + if (Partner->L4STATE == 5) + strcpy(MID, "<-->"); + else + strcpy(MID, "<~~>"); + + DISPLAYCIRCUIT(Partner, RHS); + } +CMDS50: + memset(Line, 32, 100); + memcpy(Line, LHS, (int)strlen(LHS)); + memcpy(&Line[35], MID, (int)strlen(MID)); + strcpy(&Line[40], RHS); + strcat(&Line[40], "\r"); + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", Line); + } +CMDS60: + L4++; + } + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +extern int MasterPort[MAXBPQPORTS+1]; // Pointer to first BPQ port for a specific MPSK or UZ7HO host + +VOID CMDP00(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // Process PORTS Message + + // If extended show state of TNC (Open, Active, etc) + + struct PORTCONTROL * PORT = PORTTABLE; + char Extended = CmdTail[0]; + struct PORTCONTROL * SAVEPORT; + + Bufferptr = Cmdprintf(Session, Bufferptr, "Ports\r"); + + while (PORT) + { + char Status[32] = "???????"; + int Portno = PORT->PORTNUMBER; + + if (PORT->Hide) + { + PORT = PORT->PORTPOINTER; + continue; + } + + if (Extended != 'E') + { + Bufferptr = Cmdprintf(Session, Bufferptr, " %2d %s\r", PORT->PORTNUMBER, PORT->PORTDESCRIPTION); + + PORT = PORT->PORTPOINTER; + continue; + } + + // Try to get port status - may not be possible with some + + if (PORT->PortStopped) + { + strcpy(Status, "Stopped"); + Bufferptr = Cmdprintf(Session, Bufferptr, " %2d %-7s %s\r", PORT->PORTNUMBER, Status, PORT->PORTDESCRIPTION); + + PORT = PORT->PORTPOINTER; + continue; + } + + if (PORT->PORTTYPE == 0) + { + struct KISSINFO * KISS = (struct KISSINFO *)PORT; + NPASYINFO Port; + + SAVEPORT = PORT; + + if (KISS->FIRSTPORT && KISS->FIRSTPORT != KISS) + { + // Not first port on device + + PORT = (struct PORTCONTROL *)KISS->FIRSTPORT; + Port = KISSInfo[Portno]; + } + + Port = KISSInfo[PORT->PORTNUMBER]; + + if (Port) + { + // KISS like - see if connected + + if (PORT->PORTIPADDR.s_addr || PORT->KISSSLAVE) + { + // KISS over UDP or TCP + + if (PORT->KISSTCP) + { + if (Port->Connected) + strcpy(Status, "Open "); + else + if (PORT->KISSSLAVE) + strcpy(Status, "Listen"); + else + strcpy(Status, "Closed"); + } + else + strcpy(Status, "UDP"); + } + else + if (Port->idComDev) // Serial port Open + strcpy(Status, "Open "); + else + strcpy(Status, "Closed"); + + PORT = SAVEPORT; + } + } + else if (PORT->PORTTYPE == 14) // Loopback + strcpy(Status, "Open "); + + else if (PORT->PORTTYPE == 16) // External + { + if (PORT->PROTOCOL == 10) // 'HF' Port + { + struct TNCINFO * TNC = TNCInfo[Portno]; + + if (TNC == NULL) + { + PORT = PORT->PORTPOINTER; + continue; + } + + switch (TNC->Hardware) // Hardware Type + { + case H_SCS: + case H_KAM: + case H_AEA: + case H_HAL: + case H_TRK: + case H_SERIAL: + + // Serial + + if (TNC->hDevice) + strcpy(Status, "Open "); + else + strcpy(Status, "Closed"); + + break; + + case H_UZ7HO: + + if (TNCInfo[MasterPort[Portno]]->CONNECTED) + strcpy(Status, "Open "); + else + strcpy(Status, "Closed"); + + break; + + case H_WINMOR: + case H_V4: + + case H_MPSK: + case H_FLDIGI: + case H_UIARQ: + case H_ARDOP: + case H_VARA: + case H_KISSHF: + case H_WINRPR: + case H_FREEDATA: + + // TCP + + if (TNC->CONNECTED) + { + if (TNC->Streams[0].Attached) + strcpy(Status, "In Use"); + else + strcpy(Status, "Open "); + } + else + strcpy(Status, "Closed"); + + break; + + case H_TELNET: + + strcpy(Status, "Open "); + } + } + else + { + // External but not HF - AXIP, BPQETHER VKISS, ?? + + struct _EXTPORTDATA * EXTPORT = (struct _EXTPORTDATA *)PORT; + + strcpy(Status, "Open "); + } + } + + Bufferptr = Cmdprintf(Session, Bufferptr, " %2d %-7s %s\r", PORT->PORTNUMBER, Status, PORT->PORTDESCRIPTION); + + PORT = PORT->PORTPOINTER; + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +char * DisplayRoute(TRANSPORTENTRY * Session, char * Bufferptr, struct ROUTE * Routes, char Verbose) +{ + char Normcall[10]; + char locked[] = " ! "; + int NodeCount; + int Percent = 0; + char PercentString[20]; + int Iframes, Retries; + char Active[10]; + int Queued; + + int Port = 0; + + int len = ConvFromAX25(Routes->NEIGHBOUR_CALL, Normcall); + + Normcall[9]=0; + + if ((Routes->NEIGHBOUR_FLAG & 1) == 1) + strcpy(locked, "!"); + else + strcpy(locked, " "); + + NodeCount = COUNTNODES(Routes); + + if (Routes->NEIGHBOUR_LINK && Routes->NEIGHBOUR_LINK->L2STATE >= 5) + strcpy(Active, ">"); + else + strcpy(Active, " "); + + if (Verbose) + { + if (Routes->NEIGHBOUR_LINK) + Queued = COUNT_AT_L2(Routes->NEIGHBOUR_LINK); // SEE HOW MANY QUEUED + else + Queued = 0; + + Iframes = Routes->NBOUR_IFRAMES; + Retries = Routes->NBOUR_RETRIES; + + if (Iframes) + { + Percent = (Retries * 100) / Iframes; + sprintf(PercentString, "%3d%%", Percent); + } + else + strcpy(PercentString, " "); + + + Bufferptr = Cmdprintf(Session, Bufferptr, "%s%2d %s %3d %3d%s%4d %4d %s %d %d %02d:%02d %d %d", + Active, Routes->NEIGHBOUR_PORT, Normcall, + Routes->NEIGHBOUR_QUAL, NodeCount, locked, Iframes, Retries, PercentString, Routes->NBOUR_MAXFRAME, Routes->NBOUR_FRACK, + Routes->NEIGHBOUR_TIME >> 8, (Routes->NEIGHBOUR_TIME) & 0xff, Queued, Routes->OtherendsRouteQual); + + // IF INP3 DISPLAY SRTT + + if (Routes->INP3Node) // INP3 Enabled? + { + double srtt = Routes->SRTT/1000.0; + double nsrtt = Routes->NeighbourSRTT/1000.0; + + Bufferptr = Cmdprintf(Session, Bufferptr, " %4.2fs %4.2fs", srtt, nsrtt); + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + } + else + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%s %d %s %d %d%s\r", + Active, Routes->NEIGHBOUR_PORT, Normcall, Routes->NEIGHBOUR_QUAL, NodeCount, locked); + } + + return Bufferptr; +} + + +VOID CMDR00(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + struct ROUTE * Routes = NEIGHBOURS; + int MaxRoutes = MAXNEIGHBOURS; + char locked[] = " ! "; + int Percent = 0; + char * ptr, * Context; + char Verbose = 0; + int Port = 0; + char AXCALL[7]; + BOOL Found; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr && (int)strlen(ptr) > 1) + { + // Route Update + + goto ROUTEUPDATE; + } + + if (ptr) + { + Verbose = ptr[0]; + ptr = strtok_s(NULL, " ", &Context); + if (ptr) + Port = atoi(ptr); + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "Routes\r"); + + while (MaxRoutes--) + { + if (Routes->NEIGHBOUR_CALL[0] != 0) + if (Port == 0 || Port == Routes->NEIGHBOUR_PORT) + Bufferptr = DisplayRoute(Session, Bufferptr, Routes, Verbose); + + Routes++; + } + goto SendReply; + +ROUTEUPDATE: + + if (Session->PASSWORD != 0xFFFF) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", PASSWORDMSG); + goto SendReply; + } + + // Line is + + // ROUTES G8BPQ-2 2 100 - Set quality to 100 + // ROUTES G8BPQ-2 2 ! - Toggle 'Locked Route' flag + // ROUTES G8BPQ-2 2 100 ! - Set quality and toggle 'locked' flag + + + ConvToAX25(ptr, AXCALL); + + ptr = strtok_s(NULL, " ", &Context); + + if (ptr) + Port = atoi(ptr); + + if (Port == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Port Number Missing \r"); + goto SendReply; + } + + Found = FindNeighbour(AXCALL, Port, &Routes); + + if (Context && Context[0] > 32) + { + // More Params + + ptr = strtok_s(NULL, " ", &Context); + + if (ptr) + { + // Adding + + memcpy(Routes->NEIGHBOUR_CALL, AXCALL, 7); // In case Add + Routes->NEIGHBOUR_PORT = Port; + Found = TRUE; + } + + if (strcmp(ptr, "!") == 0) + { + // Toggle Lock + + Routes->NEIGHBOUR_FLAG ^= 1; // FLIP LOCKED BIT + goto Displayit; + } + + if (strcmp(ptr, "Z") == 0) + { + // Clear Counts + + Routes->NBOUR_IFRAMES = 0; + Routes->NBOUR_RETRIES = 0; + goto Displayit; + } + + Routes->NEIGHBOUR_QUAL = atoi(ptr); + + if (Context && Context[0] == '!') + { + // Toggle Lock + + Routes->NEIGHBOUR_FLAG ^= 1; // FLIP LOCKED BIT + goto Displayit; + } + } + +Displayit: + + // Just display + + if (Found) + Bufferptr = DisplayRoute(Session, Bufferptr, Routes, 1); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Not Found\r"); + + + +/* MOV ROUTEDISP,1 + + CMP BYTE PTR [ESI],20H + JE SHORT JUSTDISPLAY + + MOV ZAPFLAG,0 + + CMP BYTE PTR [ESI],'Z' + JNE SHORT NOTZAP + + MOV ZAPFLAG,1 + JMP SHORT JUSTDISPLAY + + PUBLIC NOTZAP +NOTZAP: + + MOV ROUTEDISP,2 ; LOCK UPDATE + + CMP BYTE PTR [ESI],'!' + JE SHORT JUSTDISPLAY +; +; LOOK FOR V FOR ADDING A DIGI +; + CMP WORD PTR [ESI],' V' ; V [SPACE] + JE ADDDIGI + + CALL GETVALUE ; GET NUMBER, UP TO SPACE , CR OR OFFH + JC SHORT BADROUTECMD ; INVALID DIGITS + + MOV NEWROUTEVAL,AL + + MOV ROUTEDISP,0 + + CALL SCAN ; SEE IF ! + MOV AH,[ESI] + + + PUBLIC JUSTDISPLAY +JUSTDISPLAY: + + + MOV ESI,OFFSET32 AX25CALL + CALL _FINDNEIGHBOUR + JZ SHORT FOUNDROUTE ; IN LIST - OK + + CMP EBX,0 + JE SHORT BADROUTECMD ; TABLE FULL?? + + MOV ECX,7 + MOV EDI,EBX + REP MOVSB ; PUT IN CALL + + MOV AL,SAVEPORT + MOV NEIGHBOUR_PORT[EBX],AL + + JMP SHORT FOUNDROUTE + + + PUBLIC BADROUTECMD +BADROUTECMD: + + POP EDI + + JMP PBADVALUE + + PUBLIC FOUNDROUTE +FOUNDROUTE: + + CMP ZAPFLAG,1 + JNE SHORT NOTCLEARCOUNTS + + XOR AX,AX + MOV ES:WORD PTR NBOUR_IFRAMES[EDI],AX + MOV ES:WORD PTR NBOUR_IFRAMES+2[EDI],AX + MOV ES:WORD PTR NBOUR_RETRIES[EDI],AX + MOV ES:WORD PTR NBOUR_RETRIES+2[EDI],AX + + JMP SHORT NOUPDATE + + PUBLIC NOTCLEARCOUNTS +NOTCLEARCOUNTS: + + CMP ROUTEDISP,1 + JE SHORT NOUPDATE + + CMP ROUTEDISP,2 + JE SHORT LOCKUPDATE + + MOV AL,NEWROUTEVAL + MOV NEIGHBOUR_QUAL[EBX],AL + + CMP AH,'!' + JNE SHORT NOUPDATE + + PUBLIC LOCKUPDATE +LOCKUPDATE: + + XOR NEIGHBOUR_FLAG[EBX],1 ; FLIP LOCKED BIT + + PUBLIC NOUPDATE +NOUPDATE: + + MOV ESI,EBX + POP EDI + + POP EBX + CALL DISPLAYROUTE + + JMP SENDCOMMANDREPLY + + PUBLIC ADDDIGI +ADDDIGI: + + ADD ESI,2 + PUSH ESI ; SAVE INPUT BUFFER + + MOV ESI,OFFSET32 AX25CALL + CALL _FINDNEIGHBOUR + + POP ESI + + JZ SHORT ADD_FOUND ; IN LIST - OK + + JMP BADROUTECMD + + PUBLIC ADD_FOUND +ADD_FOUND: + + CALL CONVTOAX25 ; GET DIGI CALLSIGN + + PUSH ESI + + MOV ESI,OFFSET32 AX25CALL + LEA EDI,NEIGHBOUR_DIGI[EBX] + MOV ECX,7 + REP MOVSB + + POP ESI ; MSG BUFFER +; +; SEE IF ANOTHER DIGI +; + CMP BYTE PTR [ESI],20H + JE SHORT NOMORE + + CALL CONVTOAX25 ; GET DIGI CALLSIGN + MOV ESI,OFFSET32 AX25CALL + LEA EDI,NEIGHBOUR_DIGI+7[EBX] + MOV ECX,7 + REP MOVSB + + PUBLIC NOMORE +NOMORE: + + JMP NOUPDATE + + + +*/ + +SendReply: + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + + +VOID LISTENCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // PROCESS LISTEN COMMAND + + // for monitoring a remote ax.25 port + + int Port = 0, index =0; + uint64_t ListenMask = 0; + char * ptr, *Context; + struct PORTCONTROL * PORT = NULL; + char ListenPortList[128] = ""; + + ptr = strtok_s(CmdTail, " ,", &Context); + + // Now accepts a list of ports + + if (ptr == 0 || memcmp(ptr, "OFF", 3) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Listening disabled\r"); + Session->LISTEN = 0; + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + while (ptr) + { + Port = atoi(ptr); + + if (Port == 0 && NUMBEROFPORTS == 1) + Port = 1; + + ptr = strtok_s(NULL, ", ", &Context); // Get port String + + if (Port) + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port %d\r", Port); + continue; + } + + if (PORT->PROTOCOL == 10 && PORT->UICAPABLE == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, port %d is not an ax.25 port\r", Port); + continue; + } + + if (PORT->PORTL3FLAG) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, port %d is for internode traffic only\r", Port); + continue; + } + + if (Session->L4CIRCUITTYPE == L2LINK + UPLINK) + { + if (Session->L4TARGET.LINK->LINKPORT->PORTNUMBER == Port) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "You can't Listen to the port you are connected on\r"); + continue; + } + } + + sprintf(ListenPortList, "%s %d", ListenPortList, Port); + + ListenMask |= ((uint64_t)1 << (Port - 1)); + } + + Session->LISTEN = ListenMask; + + if (ListenMask) + { + if (CountBits64(ListenMask) == 1) + Bufferptr = Cmdprintf(Session, Bufferptr, "Listening on port%s. Use CQ to send a beacon, LIS to disable\r", ListenPortList); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Listening on ports%s. Use LIS to disable\r", ListenPortList); + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + + +VOID UNPROTOCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // PROCESS UNPROTO COMMAND + + int Port = 0, index =0; + char * ptr, *Context; + struct PORTCONTROL * PORT = NULL; + UCHAR axcalls[64]; + BOOL Stay, Spy; + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + Port = atoi(ptr); + + if (Port == 0 && NUMBEROFPORTS == 1) + Port = 1; + else + ptr = strtok_s(NULL, " ", &Context); // Get Unproto String + + if (Port) + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (ptr == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Destination missing\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + ptr[strlen(ptr)] = ' '; // Put param back together + + if (DecodeCallString(ptr, &Stay, &Spy, &axcalls[0]) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Call\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (PORT->PROTOCOL == 10 && PORT->UICAPABLE == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, port is not an ax.25 port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (PORT->PORTL3FLAG) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, port is for internode traffic only\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // Copy Address Info to Session Record + + Session->UNPROTO = Port; + Session->UAddrLen = (int)strlen(axcalls); + memcpy(Session->UADDRESS, axcalls, 63); + + Bufferptr = Cmdprintf(Session, Bufferptr, "Unproto Mode - enter ctrl/z or /ex to exit\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + +VOID CALCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // PROCESS CAL COMMAND + + int Port = 0, index = 0, Count = 0; + char * ptr, *Context; + struct PORTCONTROL * PORT = NULL; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + Port = atoi(ptr); + + if (Port == 0 && NUMBEROFPORTS == 1) + Port = 1; + else + ptr = strtok_s(NULL, " ", &Context); // Get Unproto String + + if (Port) + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (PORT->PROTOCOL == 10 && PORT->UICAPABLE == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, port is not an ax.25 port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (ptr == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Count Missing\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Count = atoi(ptr); + + ptr = strtok_s(NULL, " ", &Context); // Get Unproto String + + Bufferptr = Cmdprintf(Session, Bufferptr, "Ok\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + + + +VOID CQCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // Send a CQ Beacon on a radio port. Must be in LISTEN state + + DIGIMESSAGE Msg; + int Port = 0; + int OneBits = 0; + uint64_t MaskCopy = Session->LISTEN; + int Len; + UCHAR CQCALL[7]; + char Empty[] = ""; + char * ptr1 = &OrigCmdBuffer[3]; + UCHAR * axptr = &Msg.DIGIS[0][0]; + char * ptr2, *Context; + + while (MaskCopy) + { + if (MaskCopy & 1) + OneBits++; + + Port++; + MaskCopy = MaskCopy >> 1; + } + + if (OneBits == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "You must enter LISTEN before calling CQ\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (OneBits > 1) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "You can't call CQ if LISTENing on more than one port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + + Len = (int)strlen(OrigCmdBuffer) - 3; + + if (Len < 0) + Len = 0; + + memset(&Msg, 0, sizeof(Msg)); + + Msg.PORT = Port; + Msg.CTL = 3; // UI + + // see if a Via specified + + if (_memicmp(ptr1, "via ", 4) == 0) + { + ptr2 = strtok_s(ptr1 + 4, ",", &Context); + + while (ptr2) + { + if (ConvToAX25(ptr2, axptr) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid via string\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + axptr += 7; + + if (axptr == &Msg.DIGIS[7][0]) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Too many digis\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + ptr1 = ptr2; + ptr2 = strtok_s(NULL, ",", &Context); + } + + // ptr1 is start of last digi call. We need to position to data + + ptr1 = strchr(ptr1, ' '); + + if (ptr1 == NULL) + ptr1 = Empty; + else + ptr1++ ; // to message + + Len = (int)strlen(ptr1); + + } + + ConvToAX25("CQ", CQCALL); + memcpy(Msg.DEST, CQCALL, 7); + Msg.DEST[6] |= 0x80; // set Command Bit + memcpy(Msg.ORIGIN, Session->L4USER, 7); + Msg.ORIGIN[6] ^= 0x1e; // Flip SSID + Msg.PID = 0xf0; // Data PID + memcpy(&Msg.L2DATA, ptr1, Len); + + Send_AX_Datagram(&Msg, Len + 2, Port); // Len is Payload ie CTL, PID and Data + + Bufferptr = Cmdprintf(Session, Bufferptr, "CQ sent\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + +} + + +TRANSPORTENTRY * SetupNewSession(TRANSPORTENTRY * Session, char * Bufferptr) +{ + TRANSPORTENTRY * NewSess = L4TABLE; + int Index = 0; + + while (Index < MAXCIRCUITS) + { + if (NewSess->L4USER[0] == 0) + { + // Got One + + Session->L4CROSSLINK = NewSess; + NewSess->L4CROSSLINK = Session; + + memcpy(NewSess->L4USER, Session->L4USER, 7); + memcpy(NewSess->L4MYCALL, Session->L4MYCALL, 7); + + + NewSess->CIRCUITINDEX = Index; //OUR INDEX + NewSess->CIRCUITID = NEXTID; + + NEXTID++; + if (NEXTID == 0) + NEXTID++; // kEEP nON-ZERO + + NewSess->SESSIONT1 = Session->SESSIONT1; + NewSess->L4WINDOW = (UCHAR)L4DEFAULTWINDOW; + + return NewSess; + } + Index++; + NewSess++; + } + + if (Bufferptr) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry - System Tables Full\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + } + + return NULL; +} + + +VOID DoNetromConnect(TRANSPORTENTRY * Session, char * Bufferptr, struct DEST_LIST * Dest, BOOL Spy) +{ + TRANSPORTENTRY * NewSess; + + NewSess = SetupNewSession(Session, Bufferptr); + + if (NewSess == NULL) + return; // Tables Full + + NewSess->L4CIRCUITTYPE = SESSION + DOWNLINK; + + NewSess->L4TARGET.DEST = Dest; + NewSess->L4STATE = 2; // CONNECTING + + NewSess->SPYFLAG = Spy; + + ReleaseBuffer((UINT *)REPLYBUFFER); + + SENDL4CONNECT(NewSess); + + L4CONNECTSOUT++; + + return; +} + +BOOL FindLink(UCHAR * LinkCall, UCHAR * OurCall, int Port, struct _LINKTABLE ** REQLINK) +{ + struct _LINKTABLE * LINK = LINKS; + struct _LINKTABLE * FIRSTSPARE = NULL; + int n = MAXLINKS; + + while (n--) + { + if (LINK->LINKCALL[0] == 0) // Spare + { + if (FIRSTSPARE == NULL) + FIRSTSPARE = LINK; + + LINK++; + continue; + } + + if ((LINK->LINKPORT->PORTNUMBER == Port) && CompareCalls(LINK->LINKCALL, LinkCall) && CompareCalls(LINK->OURCALL, OurCall)) + { + *REQLINK = LINK; + return TRUE; + } + + LINK++; + } + // ENTRY NOT FOUND - FIRSTSPARE HAS FIRST FREE ENTRY, OR ZERO IF TABLE FULL + + *REQLINK = FIRSTSPARE; + return FALSE; +} + +VOID ATTACHCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * UserCMD); + +VOID CMDC00(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // PROCESS CONNECT COMMAND + + TRANSPORTENTRY * NewSess; + + int CONNECTPORT, Port; + BOOL CallEvenIfInNodes = FALSE; + char * ptr, *Context; + UCHAR axcalls[64]; + UCHAR ourcall[7]; // Call we are using (may have SSID bits inverted + int ret; + struct PORTCONTROL * PORT = PORTTABLE; + struct _LINKTABLE * LINK; + int CQFLAG = 0; // NOT CQ CALL + BOOL Stay, Spy; + int n; + char TextCall[10]; + int TextCallLen; + char PortString[10]; + char cmdCopy[256]; + struct _EXTPORTDATA * EXTPORT = (struct _EXTPORTDATA *)PORT; + char toCall[12], fromCall[12]; + +#ifdef EXCLUDEBITS + + if (CheckExcludeList(Session->L4USER) == FALSE) + { + // CONNECTS FROM THIS STATION ARE NOT ALLOWED + + ReleaseBuffer((UINT *)REPLYBUFFER); + return; + } + +#endif + + if (Session->LISTEN) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Can't connect while listening\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + CONNECTPORT = 0; // NO PORT SPECIFIED + + ptr = strtok_s(CmdTail, " ", &Context); + + strcpy(cmdCopy, Context); // Save in case Telnet Connect + + if (ptr == 0) + { + // No param + + if (CFLAG) // C Command Disabled ? + { + // Convert to HOST (appl 32) command + + //MOV _CMDPTR,OFFSET32 _HOSTCMD + //MOV _ALIASPTR,OFFSET32 _HOSTCMD + 32 * 31 + + //MOV _APPLMASK, 80000000H ; Internal Term + + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Call\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Port = atoi(ptr); + + if (Port) + { + // IF THERE IS NOTHING FOLLOWING THE NUMBER, ASSUME IT IS A + // NUMERIC ALIAS INSTEAD OF A PORT + + sprintf(PortString, "%d", Port); + + if (strlen(PortString) < (int)strlen(ptr)) + goto NoPort; + + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + EXTPORT = (struct _EXTPORTDATA *)PORT; + + ptr = strtok_s(NULL, " ", &Context); + + if (ptr == 0) + { + // No param + + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Call\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + CONNECTPORT = Port; + + if (strcmp(ptr, "CMS") == 0 || strcmp(ptr, "HOST") == 0) // In case someeone has CMS or HOST as an alias + goto Downlink; + + } + +NoPort: + + ptr[strlen(ptr)] = ' '; // Put param back together + + if (ptr[0] == '!') + { + CallEvenIfInNodes = TRUE; + ptr++; + } + + if (memcmp(ptr, "RELAY ", 5) == 0 || memcmp(ptr, "SYNC ", 5) == 0) + { + // c p relay with extra parms + + goto Downlink; + } + + // Skip call validation if using a ptc to allow 1:call, 2:call format + + if (Port && PORT->PROTOCOL == 10 && memcmp(EXTPORT->PORT_DLL_NAME, "SCSPACTOR", 9) == 0) + { + char * p; + + if (p = strstr(cmdCopy, " S ")) + { + Stay = TRUE; + p++; + *p = ' '; + } + + if (p = strstr(cmdCopy, " Z ")) + { + Spy = TRUE; + p++; + *p = ' '; + } + + goto Downlink; + } + else + { + if (DecodeCallString(ptr, &Stay, &Spy, &axcalls[0]) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Call\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + } + + Session->STAYFLAG = Stay; + + TextCallLen = ConvFromAX25(axcalls, TextCall); + + if (CallEvenIfInNodes) + goto Downlink; + + // SEE IF CALL TO ANY OF OUR HOST SESSIONS - UNLESS DIGIS SPECIFIED + + if (axcalls[7] == 0) + { + // If this connect is as a result of a command alias, don't check appls or we will loop + + if (ALIASINVOKED == 0) + { + APPLCALLS * APPL = APPLCALLTABLE; + int n = NumberofAppls; + APPLMASK = 1; + + while (n--) + { + if (memcmp(axcalls, APPL->APPLALIAS, 6) == 0 || CompareCalls(axcalls, APPL->APPLCALL)) + { + // Call to an appl + + // Convert to an APPL command, so any alias is actioned + + // SEE IF THERE IS AN ALIAS DEFINDED FOR THIS COMMAND + + if (APPL->APPLHASALIAS && APPL->APPLALIASVAL[0] != 0x20) + { + // COPY ALIAS TO COMMAND _BUFFER, THEN REENTER COMMAND HANDLER + + memcpy(COMMANDBUFFER, APPL->APPLALIASVAL, ALIASLEN); + COMMANDBUFFER[80] = 0; + _strupr(COMMANDBUFFER); + memcpy(OrigCmdBuffer, APPL->APPLALIASVAL, ALIASLEN); // In case original case version needed + + ALIASINVOKED = TRUE; // To prevent Alias Loops + } + else + { + + // Copy Appl Command to Command Buffer. Ensure doesn't contain old command + + memset(COMMANDBUFFER, ' ', 72); + memcpy(COMMANDBUFFER, APPL->APPLCMD, 12); + } + DoTheCommand(Session); + return; + } + APPL++; + APPLMASK <<= 1; + } + } + } + + if (axcalls[7] == 0) + { + // SEE IF CALL TO ANOTHER NODE + + struct DEST_LIST * Dest = DESTS; + int n = MAXDESTS; + + if (axcalls[6] == 0x60) // if SSID, dont check aliases + { + while (n--) + { + if (memcmp(Dest->DEST_ALIAS, TextCall, 6) == 0) + { + DoNetromConnect(Session, Bufferptr, Dest, Spy); + return; + } + Dest++; + } + } + + Dest = DESTS; + n = MAXDESTS; + + while (n--) + { + if (CompareCalls(Dest->DEST_CALL, axcalls)) + { + DoNetromConnect(Session, Bufferptr, Dest, Spy); + return; + } + Dest++; + } + } + + // Must be Downlink Connect + +Downlink: + + if (CONNECTPORT == 0 && NUMBEROFPORTS > 1) + { + // L2 NEEDS PORT NUMBER + + Bufferptr = Cmdprintf(Session, Bufferptr, "Downlink connect needs port number - C P CALLSIGN\r"); + + // Send Port List + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // ENSURE PORT IS AVAILABLE FOR L2 USE + + if (PORT->PROTOCOL >= 10) // Pactor=-style port? + { + int count; + + // if Via PACTOR ARDOP WINMOR or VARA, convert to attach and call = Digi's are in AX25STRING (+7) + + if (memcmp(&axcalls[7], &WINMOR[0], 6) == 0 || + memcmp(&axcalls[7], &ARDOP[0], 6) == 0 || + memcmp(&axcalls[7], &VARA[0], 6) == 0 || + memcmp(&axcalls[7], &PACTORCALL[0], 6) == 0) + { + char newcmd[80]; + + TextCall[TextCallLen] = 0; + sprintf(newcmd, "%s %s", CmdTail, TextCall); + + ATTACHCMD(Session, Bufferptr, newcmd, NULL); + return; + } + + // If on a KAM or SCS with ax.25 on port 2, do an Attach command, then pass on connect + + if (EXTPORT->MAXHOSTMODESESSIONS <= 1) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, port is not an ax.25 port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // Only Allow Attach VHF from Secure Applications or if PERMITGATEWAY is set + + if (EXTPORT->PERMITGATEWAY == 0 && Session->Secure_Session == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, you are not allowed to use this port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + count = EXTPORT->MAXHOSTMODESESSIONS; + count--; // First is Pactor Stream, count is now last ax.25 session + + while (count) + { + if (EXTPORT->ATTACHEDSESSIONS[count] == 0) + { + int Paclen, PortPaclen; + struct DATAMESSAGE * Buffer; + struct DATAMESSAGE Message = {0}; + char Callstring[80]; + int len; + + // Found a free one - use it + + // See if TNC is OK + + Message.PORT = count; + + ret = PORT->PORTTXCHECKCODE(PORT, Message.PORT); + + if ((ret & 0xff00) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - TNC Not Ready\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // GET CIRCUIT TABLE ENTRY FOR OTHER END OF LINK + + NewSess = SetupNewSession(Session, Bufferptr); + if (NewSess == NULL) + return; + + // if a UZ7HO port, and the uplink is L2 or Uz7HO invert SSID bits + + // We only get here if multisession + + if (memcmp(EXTPORT->PORT_DLL_NAME, "UZ7HO", 5) != 0) + goto noFlip; + + if ((Session->L4CIRCUITTYPE & BPQHOST))// host + goto noFlip; + + if ((Session->L4CIRCUITTYPE & PACTOR)) + { + // incoming is Pactorlike - see if UZ7HO + + if (memcmp(Session->L4TARGET.EXTPORT->PORT_DLL_NAME, "UZ7HO", 5) != 0) + goto noFlip; + else + NewSess->L4USER[6] ^= 0x1e; // UZ7HO Uplink - flip + } + else + + // Must be L2 uplink - flip + + NewSess->L4USER[6] ^= 0x1e; // Flip SSID +noFlip: + EXTPORT->ATTACHEDSESSIONS[count] = NewSess; + + NewSess->KAMSESSION = count; + + // Set paclen to lower of incoming and outgoing + + Paclen = Session->SESSPACLEN; // Incoming PACLEN + + if (Paclen == 0) + Paclen = 256; // 0 = 256 + + PortPaclen = PORT->PORTPACLEN; + + if (PortPaclen == 0) + PortPaclen = 256; // 0 = 256 + + if (PortPaclen < Paclen) + Paclen = PortPaclen; + + NewSess->SESSPACLEN = Paclen; + Session->SESSPACLEN = Paclen; + + NewSess->L4STATE = 5; + NewSess->L4CIRCUITTYPE = DOWNLINK + PACTOR; + NewSess->L4TARGET.PORT = PORT; + + // Send the connect command to the TNC + + Buffer = REPLYBUFFER; + + Buffer->PORT = count; + Buffer->PID = 0xf0; + + // if on Telnet Port convert use original cmd tail + + // Why just on telnet - what not all ports?? + + if (memcmp(EXTPORT->PORT_DLL_NAME, "TELNET", 6) == 0 || memcmp(EXTPORT->PORT_DLL_NAME, "SCSPACTOR", 9) == 0) + { + NewSess->Secure_Session = Session->Secure_Session; + len = sprintf(Callstring,"C %s", cmdCopy); + } + else + { + TextCall[TextCallLen] = 0; + + len = sprintf(Callstring,"C %s", TextCall); + + if (axcalls[7]) + { + int digi = 7; + + // we have digis + + len += sprintf(&Callstring[len], " via"); + + while (axcalls[digi]) + { + TextCall[ConvFromAX25(&axcalls[digi], TextCall)] = 0; + len += sprintf(&Callstring[len], " %s", TextCall); + digi += 7; + } + } + } + Callstring[len++] = 13; + Callstring[len] = 0; + + Buffer->LENGTH = len + MSGHDDRLEN + 1; + memcpy(Buffer->L2DATA, Callstring, len); + C_Q_ADD(&PORT->PORTTX_Q, (UINT *)Buffer); + + return; + } + count--; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - No free streams on this port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if ((Session->L4CIRCUITTYPE & BPQHOST) == 0 && PORT->PORTL3FLAG) + { + //Port only for L3 + + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, port is for internode traffic only\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (PORT->PortUIONLY) + { + //Port only for UI + + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, port is for UI traffic only\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + ret = CheckKissInterlock(PORT, TRUE); + + if (ret) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, Interlocked port %d is in use\r", ret); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + + if (Session->L4USER[6] == 0x42 || Session->L4USER[6] == 0x44) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry - Can't make ax.25 calls with SSID of T or R\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // Get Session Entry for Downlink + + NewSess = SetupNewSession(Session, Bufferptr); + if (NewSess == NULL) + return; + + NewSess->L4CIRCUITTYPE = L2LINK + DOWNLINK; + + // FORMAT LINK TABLE ENTRY FOR THIS CONNECTION + + memcpy(ourcall, NewSess->L4USER, 7); + + // SSID SWAP TEST - LEAVE ALONE FOR HOST or Pactor like (unless UZ7HO) + + if ((Session->L4CIRCUITTYPE & BPQHOST))// host + goto noFlip3; + + if ((Session->L4CIRCUITTYPE & PACTOR)) + { + // incoming is Pactorlike - see if UZ7HO + + if (memcmp(Session->L4TARGET.EXTPORT->PORT_DLL_NAME, "UZ7HO", 5) != 0) + goto noFlip3; + + if (Session->L4TARGET.EXTPORT->MAXHOSTMODESESSIONS < 2) // Not multisession + goto noFlip3; + + ourcall[6] ^= 0x1e; // UZ7HO Uplink - flip + } + else + + // Must be L2 uplink - flip + + ourcall[6] ^= 0x1e; // Flip SSID + +noFlip3: + + // SET UP NEW SESSION (OR RESET EXISTING ONE) + + FindLink(axcalls, ourcall, Port, &LINK); + + if (LINK == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry - System Tables Full\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + + // Should release NewSess + + return; + } + + memcpy(LINK->LINKCALL, axcalls, 7); + memcpy(LINK->OURCALL, ourcall, 7); + + LINK->LINKPORT = PORT; + + LINK->L2TIME = PORT->PORTT1; + + // Copy Digis + + n = 7; + ptr = &LINK->DIGIS[0]; + + while (axcalls[n]) + { + memcpy(ptr, &axcalls[n], 7); + n += 7; + ptr += 7; + + LINK->L2TIME += 2 * PORT->PORTT1; // ADJUST TIMER VALUE FOR 1 DIGI + } + + LINK->LINKTYPE = 2; // DOWNLINK + LINK->LINKWINDOW = PORT->PORTWINDOW; + + RESET2(LINK); // RESET ALL FLAGS + + toCall[ConvFromAX25(LINK->LINKCALL, toCall)] = 0; + fromCall[ConvFromAX25(LINK->OURCALL, fromCall)] = 0; + + hookL2SessionAttempt(CONNECTPORT, fromCall, toCall, LINK); + + + if (CMD->String[0] == 'N' && SUPPORT2point2) + LINK->L2STATE = 1; // New (2.2) send XID + else + LINK->L2STATE = 2; // Send SABM + + LINK->CIRCUITPOINTER = NewSess; + + NewSess->L4TARGET.LINK = LINK; + + if (PORT->PORTPACLEN) + NewSess->SESSPACLEN = Session->SESSPACLEN = PORT->PORTPACLEN; + + if (CQFLAG == 0) // if a CQ CALL DONT SEND SABM + { + seeifInterlockneeded(PORT); + + if (LINK->L2STATE == 1) + L2SENDXID(LINK); + else + SENDSABM(LINK); + } + ReleaseBuffer((UINT *)REPLYBUFFER); + return; +} + +BOOL DecodeCallString(char * Calls, BOOL * Stay, BOOL * Spy, UCHAR * AXCalls) +{ + // CONVERT CALL + OPTIONAL DIGI STRING TO AX25, RETURN + // CONVERTED STRING IN AXCALLS. Return FALSE if invalied + + char * axptr = AXCalls; + char * ptr, *Context; + int CQFLAG = 0; // NOT CQ CALL + int n = 8; // Max digis + + *Stay = 0; + *Spy = 0; + + memset(AXCalls, 0, 64); + + ptr = strtok_s(Calls, " ,", &Context); + + if (ptr == NULL) + return FALSE; + + // First field is Call + + if (ConvToAX25(ptr, axptr) == 0) + return FALSE; + + axptr += 7; + + ptr = strtok_s(NULL, " ,", &Context); + + while (ptr && n--) + { + // NEXT FIELD = COULD BE CALLSIGN, VIA, OR S (FOR STAY) + + if (strcmp(ptr, "S") == 0) + *Stay = TRUE; + else if (strcmp(ptr, "Z") == 0) + *Spy = TRUE; + else if (memcmp(ptr, "VIA", (int)strlen(ptr)) == 0) + { + } //skip via + else + { + // Convert next digi + + if (ConvToAX25(ptr, axptr) == 0) + return FALSE; + + axptr += 7; + } + + ptr = strtok_s(NULL, " ,", &Context); + } + + return TRUE; +} + + +VOID LINKCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // PROCESS *** LINKED to CALLSIGN + + char * ptr, *Context; + UCHAR axcall[7]; + int ret; + + if (LINKEDFLAG == 'Y' || // UNCONDITIONAL? + (LINKEDFLAG == 'A' && + ((Session->L4CIRCUITTYPE & BPQHOST) || Session->Secure_Session || Session->PASSWORD == 0xffff))) + { + ptr = strtok_s(CmdTail, " ", &Context); + if (ptr) + ptr = strtok_s(NULL, " ", &Context); + + if (ptr) + { + ret = ConvToAX25Ex(ptr, axcall); + + if (ret) + { + memcpy(Session->L4USER, axcall, 7); + strcpy(Bufferptr, OKMSG); + Bufferptr += (int)strlen(OKMSG); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + } + + strcpy(Bufferptr, BADMSG); + Bufferptr += (int)strlen(BADMSG); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + memcpy(Bufferptr, PASSWORDMSG, LPASSMSG); + Bufferptr += LPASSMSG; + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +int CompareNode(const void *a, const void *b); +int CompareAlias(const void *a, const void *b); + +char * DoOneNode(TRANSPORTENTRY * Session, char * Bufferptr, struct DEST_LIST * Dest) +{ + char Normcall[10]; + char Alias[10]; + struct NR_DEST_ROUTE_ENTRY * NRRoute; + struct DEST_ROUTE_ENTRY * Route; + struct ROUTE * Neighbour; + int i, Active, len; + + Alias[6] = 0; + + memcpy(Alias, Dest->DEST_ALIAS, 6); + strlop(Alias, ' '); + + Normcall[ConvFromAX25(Dest->DEST_CALL, Normcall)] = 0; + + Bufferptr = Cmdprintf(Session, Bufferptr, "Routes to: %s:%s", Alias, Normcall); + + if (Dest->DEST_COUNT) + Bufferptr = Cmdprintf(Session, Bufferptr, " RTT=%4.2f FR=%d %c %.1d\r", + Dest->DEST_RTT /1000.0, Dest->DEST_COUNT, + (Dest->DEST_STATE & 0x40)? 'B':' ', (Dest->DEST_STATE & 63)); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + NRRoute = &Dest->NRROUTE[0]; + + Active = Dest->DEST_ROUTE; + + for (i = 1; i < 4; i++) + { + Neighbour = NRRoute->ROUT_NEIGHBOUR; + + if (Neighbour) + { + len = ConvFromAX25(Neighbour->NEIGHBOUR_CALL, Normcall); + Normcall[len] = 0; + + Bufferptr = Cmdprintf(Session, Bufferptr, "%c %d %d %d %s\r", + (Active == i)?'>':' ',NRRoute->ROUT_QUALITY, NRRoute->ROUT_OBSCOUNT, Neighbour->NEIGHBOUR_PORT, Normcall); + } + NRRoute++; + } + + // DISPLAY INP3 ROUTES + + Route = &Dest->ROUTE[0]; + + Active = Dest->DEST_ROUTE; + + for (i = 1; i < 4; i++) + { + Neighbour = Route->ROUT_NEIGHBOUR; + + if (Neighbour) + { + double srtt = Route->SRTT/1000.0; + + len = ConvFromAX25(Neighbour->NEIGHBOUR_CALL, Normcall); + Normcall[len] = 0; + + Bufferptr = Cmdprintf(Session, Bufferptr, "%c %d %4.2fs %d %s\r", + (Active == i + 3)?'>':' ',Route->Hops, srtt, Neighbour->NEIGHBOUR_PORT, Normcall); + } + Route++; + } + + return Bufferptr; +} + + +int DoViaEntry(struct DEST_LIST * Dest, int n, char * line, int cursor) +{ + char Portcall[10]; + int len; + + if (Dest->NRROUTE[n].ROUT_NEIGHBOUR != 0 && Dest->NRROUTE[n].ROUT_NEIGHBOUR->INP3Node == 0) + { + len=ConvFromAX25(Dest->NRROUTE[n].ROUT_NEIGHBOUR->NEIGHBOUR_CALL, Portcall); + Portcall[len]=0; + + len=sprintf(&line[cursor],"%s %d %d ", + Portcall, + Dest->NRROUTE[n].ROUT_NEIGHBOUR->NEIGHBOUR_PORT, + Dest->NRROUTE[n].ROUT_QUALITY); + + cursor+=len; + + if (Dest->NRROUTE[n].ROUT_OBSCOUNT > 127) + { + len=sprintf(&line[cursor],"! "); + cursor+=len; + } + } + return cursor; +} + +int DoINP3ViaEntry(struct DEST_LIST * Dest, int n, char * line, int cursor) +{ + char Portcall[10]; + int len; + double srtt; + + if (Dest->ROUTE[n].ROUT_NEIGHBOUR != 0) + { + srtt = Dest->ROUTE[n].SRTT/1000.0; + + len=ConvFromAX25(Dest->ROUTE[n].ROUT_NEIGHBOUR->NEIGHBOUR_CALL, Portcall); + Portcall[len]=0; + + len=sprintf(&line[cursor],"%s %d %d %4.2fs ", + Portcall, + Dest->ROUTE[n].ROUT_NEIGHBOUR->NEIGHBOUR_PORT, + Dest->ROUTE[n].Hops, srtt); + + cursor+=len; + + if (Dest->NRROUTE[n].ROUT_OBSCOUNT > 127) + { + len=sprintf(&line[cursor],"! "); + cursor+=len; + } + } + return cursor; +} + +int WildCmp(char * pattern, char * string) +{ + // Check if string is at end or not. + + if (*pattern == '\0') + return *string == '\0'; + + // Check for single character missing or match + + if (*pattern == '?' || *pattern == *string) + return *string != '\0' && WildCmp(pattern + 1, string + 1); + + if (*pattern == '*') + { + // Check for multiple character missing + + return WildCmp(pattern + 1, string) || (*string != '\0' && WildCmp(pattern, string + 1)); + } + + return 0; +} + +VOID CMDN00(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + struct DEST_LIST * Dest = DESTS; + int count = MAXDESTS, i; + char Normcall[10]; + char Alias[10]; + int Width = 4; + int x = 0, n = 0; + struct DEST_LIST * List[1000]; + char Param = 0; + char * ptr, * param2,* Context; + char Nodeline[21]; + char AXCALL[7]; + char * Call; + char * Qualptr; + int Qual; + char line[160]; + int cursor, len; + UCHAR axcall[7]; + int SavedOBSINIT = OBSINIT; + struct ROUTE * ROUTE = NULL; + char Pattern[80] = ""; + char * firststar; + int minqual = 0; + + ptr = strtok_s(CmdTail, " ", &Context); + param2 = strtok_s(NULL, " ", &Context); + + if (ptr) + { + if (strcmp(ptr, "ADD") == 0) + goto NODE_ADD; + + if (strcmp(ptr, "DEL") == 0) + goto NODE_DEL; + + if (strcmp(ptr, "VIA") == 0) + goto NODE_VIA; + } + + if (ptr) + { + // Could be C or a pattern. Accept C pattern or pattern C + + if ((int)strlen(ptr) > 1) + { + strcpy(Pattern, ptr); + if (param2 && param2[0] == 'C') + Param = 'C'; + } + else + { + Param = ptr[0]; + if (param2) + strcpy(Pattern, param2); + } + } + + // Pattern >nnn selects nodes with at least that quality + + if (Pattern[0] == '>') + { + minqual = atoi(&Pattern[1]); + Pattern[0] = 0; + } + + // We need to pick out CALL or CALL* from other patterns (as call use detail display) + + firststar = strchr(Pattern, '*'); + + if ((firststar && *(firststar + 1) != 0)|| strchr(Pattern, '?')) //(* not on end) + + // definitely pattern + + goto DoNodePattern; + + // If it works as CALL*, process, else drop through + + if (Pattern[0]) + { + UCHAR AXCall[8]; + int count; + int paramlen = (int)strlen(ptr); + char parampadded[20]; + int n = 0; + + Alias[8] = 0; + strcpy(parampadded, Pattern); + strcat(parampadded, " "); + + ConvToAX25(Pattern, AXCall); + + // if * on end, list all ssids + + if (firststar) + { + AXCall[6] = 0; + + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + while (AXCall[6] < 32) + { + Dest = DESTS; + + for (count = 0; count < MAXDESTS; count++) + { + if (memcmp(Dest->DEST_ALIAS, parampadded, 6) == 0 || CompareCalls(Dest->DEST_CALL, AXCall)) + { + break; + } + Dest++; + } + + if (count < MAXDESTS) + { + Bufferptr = DoOneNode(Session, Bufferptr, Dest); + n++; + } + + AXCall[6] += 2; + } + + if (n) // Found Some + { + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Dest = DESTS; // Reset + + // Drop through to try as pattern + } + else + { + // process as just call + + for (count = 0; count < MAXDESTS; count++) + { + if (memcmp(Dest->DEST_ALIAS, parampadded, 6) == 0 || CompareCalls(Dest->DEST_CALL, AXCall)) + { + break; + } + Dest++; + } + + if (count == MAXDESTS) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not found\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Bufferptr = DoOneNode(Session, Bufferptr, Dest); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + } + +DoNodePattern: + + Bufferptr = Cmdprintf(Session, Bufferptr, "Nodes\r"); + + while (count--) + { + if (Dest->DEST_CALL[0] != 0) + { + if (Dest->NRROUTE->ROUT_QUALITY >= minqual) + if (Param != 'T' || Dest->DEST_COUNT) + List[n++] = Dest; + + if (n > 999) + break; + } + Dest++; + } + + if (Param == 'C') + qsort(List, n, sizeof(void *), CompareNode); + else + qsort(List, n, sizeof(void *), CompareAlias); + + + for (i = 0; i < n; i++) + { + int len = ConvFromAX25(List[i]->DEST_CALL, Normcall); + Normcall[len]=0; + + memcpy(Alias, List[i]->DEST_ALIAS, 6); + Alias[6] = 0; + strlop(Alias, ' '); + + if (strlen(Alias)) + strcat(Alias, ":"); + + if (Alias[0] == '#' && HIDENODES == 1 && Param != '*') // Hidden Node and not N * command + continue; + + if (Pattern[0]) + if (!WildCmp(Pattern, Normcall) && !WildCmp(Pattern, Alias)) + continue; + + if (Param == 'T') + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%s%s RTT=%4.2f Frames = %d %c %.1d\r", + Alias, Normcall, List[i]->DEST_RTT /1000.0, List[i]->DEST_COUNT, + (List[i]->DEST_STATE & 0x40)? 'B':' ', (List[i]->DEST_STATE & 63)); + } + else + { + len = sprintf(Nodeline, "%s%s", Alias, Normcall); + memset(&Nodeline[len], ' ', 20 - len); + Nodeline[20] = 0; + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", Nodeline); + + if (++x == Width) + { + x = 0; + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + } + } + } + + if (x) + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + goto SendReply; + + +NODE_VIA: + + // List Nodes reachable via a neighbour + + ptr = param2; + + if (ptr == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Missing Call\r"); + goto SendReply; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + ConvToAX25(ptr, AXCALL); + + Dest = DESTS; + + Dest-=1; + + for (count=0; countNRROUTE[0].ROUT_NEIGHBOUR == 0 && Dest->ROUTE[0].ROUT_NEIGHBOUR == 0) + continue; + + + if ((Dest->NRROUTE[0].ROUT_NEIGHBOUR && CompareCalls(Dest->NRROUTE[0].ROUT_NEIGHBOUR->NEIGHBOUR_CALL, AXCALL)) + || (Dest->NRROUTE[1].ROUT_NEIGHBOUR && CompareCalls(Dest->NRROUTE[1].ROUT_NEIGHBOUR->NEIGHBOUR_CALL, AXCALL)) + || (Dest->NRROUTE[2].ROUT_NEIGHBOUR && CompareCalls(Dest->NRROUTE[2].ROUT_NEIGHBOUR->NEIGHBOUR_CALL, AXCALL)) + + || (Dest->ROUTE[0].ROUT_NEIGHBOUR && CompareCalls(Dest->ROUTE[0].ROUT_NEIGHBOUR->NEIGHBOUR_CALL, AXCALL)) + || (Dest->ROUTE[1].ROUT_NEIGHBOUR && CompareCalls(Dest->ROUTE[1].ROUT_NEIGHBOUR->NEIGHBOUR_CALL, AXCALL)) + || (Dest->ROUTE[2].ROUT_NEIGHBOUR && CompareCalls(Dest->ROUTE[2].ROUT_NEIGHBOUR->NEIGHBOUR_CALL, AXCALL))) + { + len=ConvFromAX25(Dest->DEST_CALL,Normcall); + + Normcall[len]=0; + + memcpy(Alias,Dest->DEST_ALIAS,6); + + Alias[6]=0; + + for (i=0;i<6;i++) + { + if (Alias[i] == ' ') + Alias[i] = 0; + } + + cursor=sprintf(line,"%s:%s ", Alias,Normcall); + + cursor = DoViaEntry(Dest, 0, line, cursor); + cursor = DoViaEntry(Dest, 1, line, cursor); + cursor = DoViaEntry(Dest, 2, line, cursor); + cursor = DoINP3ViaEntry(Dest, 0, line, cursor); + cursor = DoINP3ViaEntry(Dest, 1, line, cursor); + cursor = DoINP3ViaEntry(Dest, 2, line, cursor); + + line[cursor++]='\r'; + line[cursor++]=0; + + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", line); + } + } + + + goto SendReply; + +NODE_ADD: + + // FORMAT IS NODE ADD ALIAS:CALL QUAL ROUTE PORT + + + if (Session->PASSWORD != 0xFFFF) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", PASSWORDMSG); + goto SendReply; + } + + ptr = param2; + + if (ptr == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Missing Alias:Call\r"); + goto SendReply; + } + + Call = strlop(ptr, ':'); + + if (Call == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Missing Alias:Call\r"); + goto SendReply; + } + + + ConvToAX25(Call, AXCALL); + + Qualptr = strtok_s(NULL, " ", &Context); + + if (Qualptr == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Quality missing\r"); + goto SendReply; + } + + Qual = atoi(Qualptr); + + if (Qual < MINQUAL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Quality is below MINQUAL\r"); + goto SendReply; + } + + if (FindDestination(AXCALL, &Dest)) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Node already in Table\r"); + goto SendReply; + } + + if (Dest == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Node Table Full\r"); + goto SendReply; + } + + memcpy(Dest->DEST_CALL, AXCALL, 7); + memcpy(Dest->DEST_ALIAS, ptr, 6); + + NUMBEROFNODES++; + + ptr = strtok_s(NULL, " ", &Context); + + if (ptr == NULL || ptr[0] == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Neighbour missing\r"); + goto SendReply; + } + + if (ConvToAX25(ptr, axcall) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Neighbour\r"); + goto SendReply; + } + else + { + int Port; + + ptr = strtok_s(NULL, " ", &Context); + if (ptr == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Port missing\r"); + goto SendReply; + } + + Port = atoi(ptr); + + if (Context[0] == '!') + { + OBSINIT = 255; //; SPECIAL FOR LOCKED + } + + if (FindNeighbour(axcall, Port, &ROUTE)) + { + PROCROUTES(Dest, ROUTE, Qual); + } + + OBSINIT = SavedOBSINIT; + + Bufferptr = Cmdprintf(Session, Bufferptr, "Node Added\r"); + goto SendReply; + } + + + + +/* +PNODE48: + + +; GET NEIGHBOURS FOR THIS DESTINATION +; + CALL CONVTOAX25 + JNZ SHORT BADROUTE +; + CALL GETVALUE + MOV SAVEPORT,AL ; SET PORT FOR _FINDNEIGHBOUR + + CALL GETVALUE + MOV ROUTEQUAL,AL +; + MOV ESI,OFFSET32 AX25CALL + + PUSH EBX ; SAVE DEST + CALL _FINDNEIGHBOUR + MOV EAX,EBX ; ROUTE TO AX + POP EBX + + JZ SHORT NOTBADROUTE + + JMP SHORT BADROUTE + +NOTBADROUTE: +; +; UPDATE ROUTE LIST FOR THIS DEST +; + MOV ROUT1_NEIGHBOUR[EBX],EAX + MOV AL,ROUTEQUAL + MOV ROUT1_QUALITY[EBX],AL + MOV ROUT1_OBSCOUNT[EBX],255 ; LOCKED +; + POP EDI + POP EBX + + INC _NUMBEROFNODES + + JMP SENDOK + +BADROUTE: +; +; KILL IT +; + MOV ECX,TYPE DEST_LIST + MOV EDI,EBX + MOV AL,0 + REP STOSB + + JMP BADROUTECMD + +*/ + + goto SendReply; + + +NODE_DEL: + + if (Session->PASSWORD != 0xFFFF) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", PASSWORDMSG); + goto SendReply; + } + + ptr = param2; + + if (ptr == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Missing Call\r"); + goto SendReply; + } + + if (strcmp(ptr, "ALL") == 0) + { + struct DEST_LIST * DEST = DESTS; + int n = MAXDESTS; + + while (n--) + { + if (DEST->DEST_CALL[0] && ((DEST->DEST_STATE & 0x80) == 0)) // Don't delete appl node + REMOVENODE(DEST); + + DEST++; + } + + ClearNodes(); + + Bufferptr = Cmdprintf(Session, Bufferptr, "All Nodes Deleted\r"); + goto SendReply; + } + + ConvToAX25(ptr, AXCALL); + + if (FindDestination(AXCALL, &Dest) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not Found\r"); + goto SendReply; + } + + if (Dest->DEST_STATE & 0x80) + Bufferptr = Cmdprintf(Session, Bufferptr, "APPL Node - Can't delete\r"); + else + { + REMOVENODE(Dest); + Bufferptr = Cmdprintf(Session, Bufferptr, "Node Deleted\r"); + } + Bufferptr = Cmdprintf(Session, Bufferptr, "Node Deleted\r"); + +SendReply: + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID CMDQUERY(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * UserCMD) +{ + // DISPLAY AVAILABLE COMMANDS + + int n; + char * ptr; + char ApplList[2048]; + char * out = ApplList; + + CMDX * CMD = &COMMANDS[APPL1]; + + for (n = 0; n < NumberofAppls; n++) + { + ptr = &CMD->String[0]; + if (*(ptr) != '*') + { + while (*ptr != ' ') + { + *(out++) = *(ptr++); + } + *(out++) = ' '; + } + CMD++; + } + + *(out) = 0; + + n = CMDLISTLEN; + + if (NEEDMH == 0) + n -= 7; // Dont show MH + + Bufferptr = Cmdprintf(Session, Bufferptr, "%s%s\r", ApplList, CMDLIST); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +char * FormatMH(MHSTRUC * MH, char Format); + +VOID MHCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // DISPLAY HEARD LIST + + int Port = 0, sess = 0; + char * ptr, *Context, *pattern; + struct PORTCONTROL * PORT = NULL; + MHSTRUC * MH; + int count = MHENTRIES; + int n; + char Normcall[20]; + char From[10]; + char DigiList[100]; + char * Output; + int len; + char Digi = 0; + + + // Note that the MHDIGIS field may contain rubbish. You have to check End of Address bit to find + // how many digis there are + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr == NULL || ptr[0] == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Port Number needed eg MH 1\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (ptr) + Port = atoi(ptr); + + if (Port) + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + pattern = strtok_s(NULL, " ", &Context); + + if (pattern) + _strupr(pattern); // Optional filter + + MH = PORT->PORTMHEARD; + + if (MH == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "MHEARD not enabled on that port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (pattern && strstr(pattern, "CLEAR")) + { + if (Session->Secure_Session) + { + memset(MH, 0, MHENTRIES * sizeof(MHSTRUC)); + SaveMH(); + Bufferptr = Cmdprintf(Session, Bufferptr, "Heard List for Port %d Cleared\r", Port); + } + else + { + Bufferptr = Cmdprintf(Session, Bufferptr, "MH Clear needs SYSOP status\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + } + else + { + if (CMD->String[2] == 'V') // MHV + { + Bufferptr = Cmdprintf(Session, Bufferptr, "MHeard List %s for Port %d\r", MYNODECALL, Port); + Bufferptr = Cmdprintf(Session, Bufferptr, "Callsign Last heard Pkts RX via Digi ;) \r"); + Bufferptr = Cmdprintf(Session, Bufferptr, "--------- ----------- ------- ------------------------------------------\r"); + } + else + if (pattern) + Bufferptr = Cmdprintf(Session, Bufferptr, "Heard List for Port %d filtered by %s\r", Port, pattern); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Heard List for Port %d\r", Port); + } + while (count--) + { + if (MH->MHCALL[0] == 0) + break; + + Digi = 0; + + len = ConvFromAX25(MH->MHCALL, Normcall); + + Normcall[len++] = MH->MHDIGI; + Normcall[len++] = 0; + + if (pattern && strstr(Normcall, pattern) == 0) + { + MH++; + continue; + } + + n = 8; // Max number of digi-peaters + + ptr = &MH->MHCALL[6]; // End of Address bit + + Output = &DigiList[0]; + + if ((*ptr & 1) == 0) + { + // at least one digi + + strcpy(Output, "via "); + Output += 4; + + while ((*ptr & 1) == 0) + { + // MORE TO COME + + From[ConvFromAX25(ptr + 1, From)] = 0; + Output += sprintf((char *)Output, "%s", From); + + ptr += 7; + n--; + + if (n == 0) + break; + + // See if digi actioned - put a * on last actioned + + if (*ptr & 0x80) + { + if (*ptr & 1) // if last address, must need * + { + *(Output++) = '*'; + Digi = '*'; + } + + else + if ((ptr[7] & 0x80) == 0) // Repeased by next? + { + *(Output++) = '*'; // No, so need * + Digi = '*'; + } + +} + *(Output++) = ','; + } + *(--Output) = 0; // remove last comma + } + else + *(Output) = 0; + + // if we used a digi set * on call and display via string + + + if (Digi) + Normcall[len++] = Digi; + else + DigiList[0] = 0; // Dont show list if not used + + Normcall[len++] = 0; + + + ptr = FormatMH(MH, CMD->String[2]); + + if (CMD->String[2] == 'V') // MHV + Bufferptr = Cmdprintf(Session, Bufferptr, "%-10s %-10s %-10d %-30s\r", + Normcall, ptr, MH->MHCOUNT, DigiList); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "%-10s %s %s\r", Normcall, ptr, DigiList); + + MH++; + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +int Rig_Command(TRANSPORTENTRY * Session, char * Command); + +VOID RADIOCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * UserCMD) +{ + char * ptr; + + if (Rig_Command(Session, CmdTail)) + { + ReleaseBuffer((UINT *)REPLYBUFFER); + return; + } + + // Error Message is in buffer + + ptr = strchr(CmdTail, 13); + + if (ptr) + { + int len = (int)(++ptr - CmdTail); + + memcpy(Bufferptr, CmdTail, len); + Bufferptr += len; + } + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + + +VOID SendNRRecordRoute(struct DEST_LIST * DEST, TRANSPORTENTRY * Session); + + +VOID NRRCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * UserCMD) +{ + // PROCESS 'NRR - Netrom Record Route' COMMAND + + char * ptr, *Context; + struct DEST_LIST * Dest = DESTS; + int count = MAXDESTS; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + { + UCHAR AXCall[8]; + int count; + + ConvToAX25(ptr, AXCall); + strcat(ptr, " "); + + for (count = 0; count < MAXDESTS; count++) + { + if (memcmp(Dest->DEST_ALIAS, ptr, 6) == 0 || CompareCalls(Dest->DEST_CALL, AXCall)) + { + SendNRRecordRoute(Dest, Session); + memcpy(Bufferptr, OKMSG, 3); + Bufferptr += 3; + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + + return; + } + Dest++; + } + } + Bufferptr = Cmdprintf(Session, Bufferptr, "Not found\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + +int CHECKINTERLOCK(struct PORTCONTROL * OURPORT) +{ + // See if any Interlocked ports are Busy + + struct PORTCONTROL * PORT = PORTTABLE; + struct _EXTPORTDATA * EXTPORT; + + int n = NUMBEROFPORTS; + int ourgroup = OURPORT->PORTINTERLOCK; + + while (PORT) + { + if (PORT != OURPORT) + { + if (PORT->PORTINTERLOCK == ourgroup) + { + // Same Group - is it busy + + int i = 0; + + EXTPORT = (struct _EXTPORTDATA *)PORT; + + while (i < 27) + if (EXTPORT->ATTACHEDSESSIONS[i++]) + return PORT->PORTNUMBER; + } + } + PORT = PORT->PORTPOINTER; + } + + return 0; +} + +VOID ATTACHCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * UserCMD) +{ + // ATTACH to a PACTOR or similar port + + TRANSPORTENTRY * NewSess; + struct _EXTPORTDATA * EXTPORT; + struct TNCINFO * TNC = 0; + + int Port = 0, sess = 0; + char * ptr, *Context; + int ret; + struct PORTCONTROL * PORT = NULL; + struct DATAMESSAGE Message = {0}; + int Paclen, PortPaclen; + struct DATAMESSAGE * Buffer; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + Port = atoi(ptr); + + if (Port) + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL || PORT->PROTOCOL < 10) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // If attach on telnet port, find a free stream + + EXTPORT = (struct _EXTPORTDATA *)PORT; + + if (strstr(EXTPORT->PORT_DLL_NAME, "TELNET")) + { + int count = EXTPORT->MAXHOSTMODESESSIONS; + count--; // First is Pactor Stream, count is now last ax.25 session + + while (count) + { + if (EXTPORT->ATTACHEDSESSIONS[count] == 0) + { + int Paclen, PortPaclen; + struct DATAMESSAGE Message = {0}; + + // Found a free one - use it + + // See if TNC is OK + + Message.PORT = count; + + ret = PORT->PORTTXCHECKCODE(PORT, Message.PORT); + + if ((ret & 0xff00) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - TNC Not Ready\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // GET CIRCUIT TABLE ENTRY FOR OTHER END OF LINK + + NewSess = SetupNewSession(Session, Bufferptr); + + if (NewSess == NULL) + return; + + EXTPORT->ATTACHEDSESSIONS[count] = NewSess; + + NewSess->Secure_Session = Session->Secure_Session; + + NewSess->KAMSESSION = count; + + // Set paclen to lower of incoming and outgoing + + Paclen = Session->SESSPACLEN; // Incoming PACLEN + + if (Paclen == 0) + Paclen = 256; // 0 = 256 + + PortPaclen = PORT->PORTPACLEN; + + if (PortPaclen == 0) + PortPaclen = 256; // 0 = 256 + + if (PortPaclen < Paclen) + Paclen = PortPaclen; + + NewSess->SESSPACLEN = Paclen; + Session->SESSPACLEN = Paclen; + + NewSess->L4STATE = 5; + NewSess->L4CIRCUITTYPE = DOWNLINK + PACTOR; + NewSess->L4TARGET.PORT = PORT; + + ptr = strtok_s(NULL, " ", &Context); + sess = count; + + // Replace command tail with original (before conversion to upper case + + Context = Context + (OrigCmdBuffer - COMMANDBUFFER); + + goto checkattachandcall; + + + memcpy(Bufferptr, OKMSG, 3); + Bufferptr += 3; + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + + return; + } + count--; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - No free streams on this port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Message.PORT = 0; + + ret = PORT->PORTTXCHECKCODE(PORT, Message.PORT); + + if ((ret & 0xff00) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - TNC Not Ready\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // See if "Attach and Call" (for VHF ports) + + ptr = strtok_s(NULL, " ", &Context); + + if (ptr && strcmp(ptr, "S") == 0) + { + Session->STAYFLAG = TRUE; + ptr = strtok_s(NULL, " ", &Context); + } + + if (ptr) + { + // we have another param + + // if it is a single char it is a channel number for vhf attach + + if (strlen(ptr) == 1) + { + // Only Allow Attach VHF from Secure Applications or if PERMITGATEWAY is set + + if (EXTPORT->PERMITGATEWAY == 0 && Session->Secure_Session == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, you are not allowed to use this port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + sess = ptr[0] - '@'; + + if (sess < 1 || sess > EXTPORT->MAXHOSTMODESESSIONS) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - Invalid Channel\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + ptr = strtok_s(NULL, " ", &Context); + + if (ptr && strcmp(ptr, "S") == 0) + { + Session->STAYFLAG = TRUE; + ptr = strtok_s(NULL, " ", &Context); + } + } + } + + if (ret & 0x8000) // Disconnecting + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - Port in use\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // Check Interlock. Only ports with a TNC record can be interlocked + + TNC = PORT->TNC; + + if (TNC) + { + // See if any interlocked ports are in use + + struct TNCINFO * OtherTNC; + int i; + int rxInterlock = TNC->RXRadio; + int txInterlock = TNC->TXRadio; + + if (rxInterlock || txInterlock) + { + for (i=1; i <= MAXBPQPORTS; i++) + { + OtherTNC = TNCInfo[i]; + + if (OtherTNC == NULL) + continue; + + if (OtherTNC == TNC) + continue; + + if (rxInterlock && rxInterlock == OtherTNC->RXRadio || txInterlock && txInterlock == OtherTNC->TXRadio) // Same Group + { + int n; + + for (n = 0; n <= 26; n++) + { + if (OtherTNC->PortRecord->ATTACHEDSESSIONS[n]) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Sorry, interlocked port %d is in use\r", i); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + } + } + } + } + } + + + + + if (EXTPORT->ATTACHEDSESSIONS[sess] || PORT->PortSuspended) + { + // In use + + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - Port in use\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + // GET CIRCUIT TABLE ENTRY FOR OTHER END OF LINK + + NewSess = SetupNewSession(Session, Bufferptr); + + if (NewSess == NULL) + return; + + // if a UZ7HO port, and the uplink is L2 or Uz7HO and multisession, + // invert SSID bits + + if (memcmp(EXTPORT->PORT_DLL_NAME, "UZ7HO", 5) != 0) + goto noFlip1; + + if (EXTPORT->MAXHOSTMODESESSIONS < 2) // Not multisession + goto noFlip1; + + if ((Session->L4CIRCUITTYPE & BPQHOST)) // host + goto noFlip1; + + if ((Session->L4CIRCUITTYPE & PACTOR)) + { + // incoming is Pactorlike - see if UZ7HO + + if (memcmp(Session->L4TARGET.EXTPORT->PORT_DLL_NAME, "UZ7HO", 5) != 0) + goto noFlip1; + else + NewSess->L4USER[6] ^= 0x1e; // UZ7HO Uplink - flip + } + else + + // Must be L2 uplink - flip + + NewSess->L4USER[6] ^= 0x1e; // Flip SSID +noFlip1: + + EXTPORT->ATTACHEDSESSIONS[sess] = NewSess; + + NewSess->KAMSESSION = sess; + + // Set paclen to lower of incoming and outgoing + + Paclen = Session->SESSPACLEN; // Incoming PACLEN + + if (Paclen == 0) + Paclen = 256; // 0 = 256 + + PortPaclen = PORT->PORTPACLEN; + + if (PortPaclen == 0) + PortPaclen = 256; // 0 = 256 + + if (PortPaclen < Paclen) + Paclen = PortPaclen; + + NewSess->SESSPACLEN = Paclen; + Session->SESSPACLEN = Paclen; + NewSess->L4STATE = 5; + NewSess->L4CIRCUITTYPE = DOWNLINK + PACTOR; + NewSess->L4TARGET.PORT = PORT; + +checkattachandcall: + + // If set freq on attach is defined, do it + + if (TNC && TNC->ActiveRXFreq && TNC->RXRadio) + { + char Msg[128]; + + sprintf(Msg, "R%d %f", TNC->RXRadio, TNC->ActiveRXFreq); + Rig_Command( (TRANSPORTENTRY *) -1, Msg); + } + + if (TNC && TNC->ActiveTXFreq && TNC->TXRadio && TNC->TXRadio != TNC->RXRadio) + { + char Msg[128]; + + sprintf(Msg, "R%d %f", TNC->TXRadio, TNC->ActiveTXFreq); + Rig_Command( (TRANSPORTENTRY *) -1, Msg); + } + + if (ptr) + { + // we have a call to connect to + + char Callstring[80]; + int len; + + Buffer = REPLYBUFFER; + Buffer->PORT = sess; + Buffer->PID = 0xf0; + + len = sprintf(Callstring,"C %s", ptr); + + ptr = strtok_s(NULL, " ", &Context); + + while (ptr) // if any other params (such as digis) copy them + { + if (strcmp(ptr, "S") == 0) + { + Session->STAYFLAG = TRUE; + } + else + len += sprintf(&Callstring[len], " %s", ptr); + + ptr = strtok_s(NULL, " ", &Context); + } + + Callstring[len++] = 13; + Callstring[len] = 0; + + Buffer->LENGTH = len + MSGHDDRLEN + 1; + memcpy(Buffer->L2DATA, Callstring, len); + C_Q_ADD(&PORT->PORTTX_Q, (UINT *)Buffer); + + return; + } + + memcpy(Bufferptr, OKMSG, 3); + Bufferptr += 3; + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + + return; +} + +// SYSOP COMMANDS + +CMDX COMMANDS[] = +{ + "SAVENODES ",8, SAVENODES, 0, + "TELRECONFIG ",4, RECONFIGTELNET, 0, + "SAVEMH ",6, SAVEMHCMD, 0, + "REBOOT ",6, REBOOT, 0, + "RIGRECONFIG ",8 , RIGRECONFIG, 0, + "RESTART ",7,RESTART,0, + "RESTARTTNC ",10,RESTARTTNC,0, + "SENDNODES ",8,SENDNODES,0, + "EXTRESTART ",10, EXTPORTVAL, offsetof(EXTPORTDATA, EXTRESTART), + "TXDELAY ",3, PORTVAL, offsetof(PORTCONTROLX, PORTTXDELAY), + "MAXFRAME ",3, PORTVAL, offsetof(PORTCONTROLX, PORTWINDOW), + "RETRIES ",3, PORTVAL, offsetof(PORTCONTROLX, PORTN2), + "FRACK ",3,PORTVAL, offsetof(PORTCONTROLX, PORTT1), + "RESPTIME ",3,PORTVAL, offsetof(PORTCONTROLX, PORTT2), + "PPACLEN ",3,PORTVAL, offsetof(PORTCONTROLX, PORTPACLEN), + "QUALITY ",3,PORTVAL, offsetof(PORTCONTROLX, PORTQUALITY), + "PERSIST ",2,PORTVAL, offsetof(PORTCONTROLX, PORTPERSISTANCE), + "TXTAIL ",3,PORTVAL, offsetof(PORTCONTROLX, PORTTAILTIME), + "XMITOFF ",7,PORTVAL, offsetof(PORTCONTROLX, PORTDISABLED), + "DIGIFLAG ",5,PORTVAL, offsetof(PORTCONTROLX, DIGIFLAG), + "DIGIPORT ",5,PORTVAL, offsetof(PORTCONTROLX, DIGIPORT), + "MAXUSERS ",4,PORTVAL, offsetof(PORTCONTROLX, USERS), + "L3ONLY ",6,PORTVAL, offsetof(PORTCONTROLX, PORTL3FLAG), + "BBSALIAS ",4,PORTVAL, offsetof(PORTCONTROLX, PORTBBSFLAG), + "VALIDCALLS ",5,VALNODES,0, + "WL2KSYSOP ",5,WL2KSYSOP,0, + "STOPPORT ",4,STOPPORT,0, + "STARTPORT ",5,STARTPORT,0, + "STOPCMS ",7,STOPCMS,0, + "STARTCMS ",8,STARTCMS,0, + + "FINDBUFFS ",4,FINDBUFFS,0, + "KISS ",4,KISSCMD,0, + "GETPORTCTEXT",9,GetPortCTEXT, 0, + +#ifdef EXCLUDEBITS + + "EXCLUDE ",4,ListExcludedCalls,0, + +#endif + + "FULLDUP ",4,PORTVAL, offsetof(PORTCONTROLX, FULLDUPLEX), + "SOFTDCD ",4,PORTVAL, offsetof(PORTCONTROLX, SOFTDCDFLAG), + "OBSINIT ",7,SWITCHVAL,(size_t)&OBSINIT, + "OBSMIN ",6,SWITCHVAL,(size_t)&OBSMIN, + "NODESINT ",8,SWITCHVAL,(size_t)&L3INTERVAL, + "L3TTL ",5,SWITCHVAL,(size_t)&L3LIVES, + "L4RETRIES ",5,SWITCHVAL,(size_t)&L4N2, + "L4TIMEOUT ",5,SWITCHVALW,(size_t)&L4T1, + "T3 ",2,SWITCHVALW,(size_t)&T3, + "NODEIDLETIME",8,SWITCHVALW,(size_t)&L4LIMIT, + "LINKEDFLAG ",10,SWITCHVAL,(size_t)&LINKEDFLAG, + "IDINTERVAL ",5,SWITCHVAL,(size_t)&IDINTERVAL, + "MINQUAL ",7,SWITCHVAL,(size_t)&MINQUAL, + "FULLCTEXT ",6,SWITCHVAL,(size_t)&FULL_CTEXT, + "HIDENODES ",8,SWITCHVAL,(size_t)&HIDENODES, + "L4DELAY ",7,SWITCHVAL,(size_t)&L4DELAY, + "L4WINDOW ",6,SWITCHVAL,(size_t)&L4DEFAULTWINDOW, + "BTINTERVAL ",5,SWITCHVAL,(size_t)&BTINTERVAL, + "PASSWORD ", 8, PWDCMD, 0, + + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, + "************", 12, APPLCMD, 0, // Apppl 32 is internal Terminal + "*** LINKED ",10,LINKCMD,0, + "CQ ",2,CQCMD,0, + "CONNECT ",1,CMDC00,0, + "NC ",2,CMDC00,0, + "BYE ",1,BYECMD,0, + "QUIT ",1,BYECMD,0, + "INFO ",1,CMDI00,0, + "HELP ",1,HELPCMD,0, + "VERSION ",1,CMDV00,0, + "NODES ",1,CMDN00,0, + "LINKS ",1,CMDL00,0, + "LISTEN ",3,LISTENCMD,0, + "L4T1 ",2,CMDT00,0, + "PORTS ",1,CMDP00,0, + "PACLEN ",3,CMDPAC,0, + "IDLETIME ",4,CMDIDLE,0, + "ROUTES ",1,CMDR00,0, + "STATS ",1,CMDSTATS,0, + "USERS ",1,CMDS00,0, + "UNPROTO ",2,UNPROTOCMD,0, + "? ",1,CMDQUERY,0, + "DUMP ",4,DUMPCMD,0, + "MHU ",3,MHCMD,0, // UTC Times + "MHL ",3,MHCMD,0, // Local Times + "MHV ",3,MHCMD,0, + "MHEARD ",1,MHCMD,0, + "APRS ",2,APRSCMD,0, + "ATTACH ",1,ATTACHCMD,0, + "RADIO ",3,RADIOCMD,0, + "AXRESOLVER ",3,AXRESOLVER,0, + "AXMHEARD ",3,AXMHEARD,0, + "TELSTATUS ",3,SHOWTELNET,0, + "NRR ",1,NRRCMD,0, + "PING ",2,PING,0, + "AGWSTATUS ",3,SHOWAGW,0, + "ARP ",3,SHOWARP,0, + "NAT ",3,SHOWNAT,0, + "IPROUTE ",3,SHOWIPROUTE,0, + "UZ7HO ",5,UZ7HOCMD,0, + "QTSM ",4,QTSMCMD,0, + + "..FLMSG ",7,FLMSG,0 +}; + +CMDX * CMD = NULL; + +int NUMBEROFCOMMANDS = sizeof(COMMANDS)/sizeof(CMDX); + +char * ReplyPointer; // Pointer into reply buffer + +int DecodeNodeName(char * NodeName, char * ptr) +{ + // NodeName is TABLE ENTRY WITH AX25 CALL AND ALIAS + + // Copyies 20 byte 20 DECODED NAME IN FORM ALIAS:CALL to ptr + // Returns significant length of string + + int len; + char Normcall[10]; + char * alias = &NodeName[7]; + int n = 6; + char * start = ptr; + + memset(ptr, ' ', 20); + + len = ConvFromAX25(NodeName, Normcall); + + if (*(alias) > ' ') // Does alias start with a null or a space ? + { + while (*(alias) > ' ' && n--) + { + *ptr++ = *alias++; + } + *ptr++ = ':'; + } + + memcpy(ptr, Normcall, len); + ptr += len; + + return (int)(ptr - start); +} + +char * SetupNodeHeader(struct DATAMESSAGE * Buffer) +{ + char Header[20]; + int len; + + char * ptr = &Buffer->L2DATA[0]; + + len = DecodeNodeName(MYCALLWITHALIAS, Header); + + memcpy (ptr, Header, len); + ptr += len; + + (*ptr++) = HEADERCHAR; + (*ptr++) = ' '; + + return ptr; +} + +VOID SendCommandReply(TRANSPORTENTRY * Session, struct DATAMESSAGE * Buffer, int Len) +{ + if (Len == (4 + sizeof(void *))) // Null Packet + { + ReleaseBuffer((UINT *)Buffer); + return; + } + + Buffer->LENGTH = Len; + + C_Q_ADD(&Session->L4TX_Q, (UINT *)Buffer); + + PostDataAvailable(Session); +} + + +VOID CommandHandler(TRANSPORTENTRY * Session, struct DATAMESSAGE * Buffer) +{ + // ignore frames with single NULL (Keepalive) + + if (Buffer->LENGTH == sizeof(void *) + 5 && Buffer->L2DATA[0] == 0) + { + ReleaseBuffer(Buffer); + return; + } + + if (Buffer->LENGTH > 100) + { +// Debugprintf("BPQ32 command too long %s", Buffer->L2DATA); + ReleaseBuffer(Buffer); + return; + } + +InnerLoop: + + InnerCommandHandler(Session, Buffer); + +// See if any more commands in buffer + + if (Session->PARTCMDBUFFER) + { + char * ptr1, * ptr2; + int len; + + Buffer = Session->PARTCMDBUFFER; + + // Check that message has a CR, if not save buffer and exit + + len = Buffer->LENGTH - (4 + sizeof(void *)); + ptr1 = &Buffer->L2DATA[0]; + + ptr2 = memchr(ptr1, 13, len); + + if (ptr2 == NULL) + return; + + Session->PARTCMDBUFFER = NULL; + + goto InnerLoop; + } +} + + +VOID InnerCommandHandler(TRANSPORTENTRY * Session, struct DATAMESSAGE * Buffer) +{ + char * ptr1, * ptr2, *ptr3; + int len, oldlen, newlen, rest, n; + struct DATAMESSAGE * OldBuffer; + struct DATAMESSAGE * SaveBuffer; + char c; + + // If a partial command is stored, append this data to it. + + if (Session->PARTCMDBUFFER) + { + len = Buffer->LENGTH - (sizeof(void *) + 4); + ptr1 = &Buffer->L2DATA[0]; + + OldBuffer = Session->PARTCMDBUFFER; // Old Data + + if (OldBuffer == Buffer) + { + // something has gone horribly wrong + + Session->PARTCMDBUFFER = NULL; + return; + } + + oldlen = OldBuffer->LENGTH; + + newlen = len + oldlen; + + if (newlen > 200) + { + // Command far too long - ignore previous + + OldBuffer->LENGTH = oldlen = sizeof(void *) + 4; + } + + OldBuffer->LENGTH += len; + memcpy(&OldBuffer->L2DATA[oldlen - (sizeof(void *) + 4)], Buffer->L2DATA, len); + + ReleaseBuffer((UINT *)Buffer); + + Buffer = OldBuffer; + + Session->PARTCMDBUFFER = NULL; + } + + // Check that message has a CR, if not save buffer and exit + + len = Buffer->LENGTH - (sizeof(void *) + 4); + ptr1 = &Buffer->L2DATA[0]; + + // Check for sending YAPP to Node + + if (len == 2 && ptr1[0] == 5 && ptr1[1] == 1) + { + ptr1[0] = 0x15; // NAK + + ptr1[1] = sprintf(&ptr1[2], "Node doesn't support YAPP Transfers"); + + Buffer->LENGTH += ptr1[1]; + + C_Q_ADD(&Session->L4TX_Q, (UINT *)Buffer); + PostDataAvailable(Session); + return; + } + + + ptr2 = memchr(ptr1, ';', len); + + if (ptr2 == 0) + { + ptr2 = memchr(ptr1, 13, len); + + if (ptr2 == 0) + { + // No newline + + Session->PARTCMDBUFFER = Buffer; + return; + } + } + + ptr2++; + + rest = len - (int)(ptr2 - ptr1); + + if (rest) + { + // there are chars beyond the cr in the buffer + + // see if LF after CR + + if ((*ptr2) == 10) // LF + { + ptr2++; + rest--; + } + + if (rest) // May only have had LF + { + // Get a new buffer, and copy extra data to it. + + SaveBuffer = (struct DATAMESSAGE *)GetBuff(); + + if (SaveBuffer) //`Just ignore if no buffers + { + SaveBuffer->LENGTH = rest + MSGHDDRLEN + 1; + SaveBuffer->PID = 0xf0; + memcpy(&SaveBuffer->L2DATA[0], ptr2, rest); + Session->PARTCMDBUFFER = SaveBuffer; + } + } + } + + // GET PACLEN FOR THIS CONNECTION + + CMDPACLEN = Session->SESSPACLEN; + + if (CMDPACLEN == 0) + CMDPACLEN = PACLEN; // Use default if no Session PACLEN + + // If sesion is in UNPROTO Mode, send message as a UI message + + if (Session->UNPROTO) + { +// char LongMsg[512] = +// "VeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessage" +// "VeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessageVeryLongMessage"; + + DIGIMESSAGE Msg; + int Port = Session->UNPROTO; + int Len = Buffer->LENGTH - (MSGHDDRLEN -1); // Need PID + + // First check for UNPROTO exit - ctrl/z or /ex + + if (Buffer->L2DATA[0] == 26 || (Len == 6 && _memicmp(&Buffer->L2DATA[0], "/ex", 3) == 0)) // CTRL/Z or /ex + { + REPLYBUFFER = Buffer; + + Session->UNPROTO = 0; + memset(Session->UADDRESS, 0, 64); + + // SET UP HEADER + + Buffer->PID = 0xf0; + ptr1 = SetupNodeHeader(Buffer); + memcpy(ptr1, OKMSG, 3); + ptr1 += 3; + SendCommandReply(Session, Buffer, (int)(ptr1 - (char *)Buffer)); + + return; + } + + memset(&Msg, 0, sizeof(Msg)); + + Msg.PORT = Port; + Msg.CTL = 3; // UI + memcpy(Msg.DEST, Session->UADDRESS, 7); + Msg.DEST[6] |= 0x80; // set Command Bit + memcpy(Msg.ORIGIN, Session->L4USER, 7); + memcpy(Msg.DIGIS, &Session->UADDRESS[7], Session->UAddrLen - 7); + memcpy(&Msg.PID, &Buffer->PID, Len); + Send_AX_Datagram(&Msg, Len, Port); // Len is Payload - CTL, PID and Data + +// memcpy(&Msg.PID + 1, LongMsg, 260); +// Send_AX_Datagram(&Msg, 241, Port); // Len is Payload - CTL, PID and Data + + +// SendUIModeFrame(Session, (PMESSAGE)Buffer, Session->UNPROTO); + + ReleaseBuffer((UINT *)Buffer); // Not using buffer for reply + + // Assume we don't allow multiple lines in buffer with UI + + if (Session->PARTCMDBUFFER) + { + Buffer = Session->PARTCMDBUFFER; + ReleaseBuffer((UINT *)Buffer); // Not using buffer for reply + Session->PARTCMDBUFFER = NULL; + } + return; + } + + memset(COMMANDBUFFER, 32, 80); // Clear to spaces + + ptr1 = &Buffer->L2DATA[0]; + ptr2 = &COMMANDBUFFER[0]; + ptr3 = &OrigCmdBuffer[0]; + + memset(OrigCmdBuffer, 0, 80); + n = 80; + + while (n--) + { + c = *(ptr1++) & 0x7f; // Mask paritu + + if (c == 13 || c == ';') + break; // CR + + *(ptr3++) = c; // Original Case + + c = toupper(c); + *(ptr2++) = c; + } + + + // USE INPUT MESSAGE _BUFFER FOR REPLY + + REPLYBUFFER = Buffer; + + // SET UP HEADER + + Buffer->PID = 0xf0; + ptr1 = SetupNodeHeader(Buffer); + + ReplyPointer = ptr1; + + ALIASINVOKED = 0; // Clear "Invoked by APPL ALIAS flag" + + DoTheCommand(Session); // We also call DotheCommand when we need to reprocess - eg for alias handling +} + +VOID DoTheCommand(TRANSPORTENTRY * Session) +{ + struct DATAMESSAGE * Buffer = REPLYBUFFER; + char * ptr1, * ptr2; + int n; + + ptr1 = &COMMANDBUFFER[0]; // + + n = 10; + + while ((*ptr1 == ' ' || *ptr1 == 0) && n--) + ptr1++; // STRIP LEADING SPACES and nulls (from keepalive) + + if (n == -1) + { + // Null command + + ReleaseBuffer((UINT *)Buffer); + return; + } + + ptr2 = ptr1; // Save + + + CMD = &COMMANDS[0]; + n = 0; + + for (n = 0; n < NUMBEROFCOMMANDS; n++) + { + int CL = CMD->CMDLEN; + + ptr1 = ptr2; + + CMDPTR = CMD; + + if (n == APPL1) // First APPL command + { + APPLMASK = 1; // FOR APPLICATION ATTACH REQUESTS + ALIASPTR = &CMDALIAS[0][0]; + } + + // ptr1 is input command + + if (memcmp(CMD->String, ptr1, CL) == 0) + { + // Found match so far - check rest + + char * ptr2 = &CMD->String[CL]; + + ptr1 += CL; + + if (*(ptr1) != ' ') + { + while(*(ptr1) == *ptr2 && *(ptr1) != ' ') + { + ptr1++; + ptr2++; + } + } + + if (*(ptr1) == ' ') + { + Session->BADCOMMANDS = 0; // RESET ERROR COUNT + + // SEE IF SYSOP COMMAND, AND IF SO IF PASSWORD HAS BEEN ENTERED + + if (n < PASSCMD) + { + //NEEDS PASSWORD FOR SYSOP COMMANDS + + if (Session->PASSWORD != 0xFFFF) + { + ptr1 = ReplyPointer; + + memcpy(ptr1, PASSWORDMSG, LPASSMSG); + ptr1 += LPASSMSG; + + SendCommandReply(Session, Buffer, (int)(ptr1 - (char *)Buffer)); + return; + } + } +// VALNODESFLAG = 0; // NOT VALID NODES COMMAND + + ptr1++; // Skip space + + CMD->CMDPROC(Session, ReplyPointer, ptr1, CMD); + return; + } + } + + APPLMASK <<= 1; + ALIASPTR += ALIASLEN; + + CMD++; + + } + Session->BADCOMMANDS++; + + if (Session->BADCOMMANDS > 6) // TOO MANY ERRORS + { + ReleaseBuffer((UINT *)Buffer); + Session->STAYFLAG = 0; + CLOSECURRENTSESSION(Session); + return; + } + + ptr1 = ReplyPointer; + + memcpy(ptr1, CMDERRMSG, CMDERRLEN); + ptr1 += CMDERRLEN; + + SendCommandReply(Session, Buffer, (int)(ptr1 - (char *)Buffer)); +} + + +VOID StatsTimer() +{ + struct PORTCONTROL * PORT = PORTTABLE; + uint64_t sum, sum2; + + // Interval is 60 secs + + while(PORT) + { + int index = PORT->StatsPointer++; + + if (index == 1439) + PORT->StatsPointer = 0; // Cyclic through 24 hours (1440 Mins) + + if (PORT->TNC) + { + struct TNCINFO * TNC = PORT->TNC; + if (TNC->Hardware == H_ARDOP || TNC->Hardware == H_VARA) + { + sum = TNC->PTTActivemS / 600; // ms but want % + PORT->AVSENDING = (UCHAR)sum; + TNC->PTTActivemS = 0; + + sum2 = TNC->BusyActivemS / 600; // ms but want % + PORT->AVACTIVE = (UCHAR)(sum + sum2); + TNC->BusyActivemS = 0; + } + } + else + { + // if KISS port using QtSM Average is already updated + + struct KISSINFO * KISS = (struct KISSINFO *)PORT; + + if (PORT->PORTNUMBER == 17) + { + int x = 17; + } + + if (PORT->PORTTXROUTINE == KISSTX && (KISS->QtSMStats || KISS->FIRSTPORT->PORT.QtSMPort)) // KISS Port QtSM Stats + { + } + else + { + sum = PORT->SENDING / 11; + PORT->AVSENDING = (UCHAR)sum; + + sum = (PORT->SENDING + PORT->ACTIVE) /11; + PORT->AVACTIVE = (UCHAR)sum; + } + } + + if (PORT->TX == NULL && PORT->AVACTIVE) + { + PORT->TX = zalloc(1440); // Keep 1 day history + PORT->BUSY = zalloc(1440); + } + if (PORT->TX) + { + PORT->TX[index] = PORT->AVSENDING; + PORT->BUSY[index] = PORT->AVACTIVE; + } + + PORT->SENDING = 0; + PORT->ACTIVE = 0; + + PORT = PORT->PORTPOINTER; + } +} + + + +extern struct AXIPPORTINFO * Portlist[]; + +#define TCPConnected 4 + + +VOID AXRESOLVER(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // DISPLAY AXIP Resolver info + + int Port = 0, index =0; + char * ptr, *Context; + struct PORTCONTROL * PORT = NULL; + struct AXIPPORTINFO * AXPORT; + char Normcall[11]; + char Flags[10]; + struct arp_table_entry * arp; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + Port = atoi(ptr); + + if (Port) + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + AXPORT = Portlist[Port]; + + if (AXPORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not an AXIP port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "AXIP Resolver info for Port %d\r", Port); + + while (index < AXPORT->arp_table_len) + { + arp = &AXPORT->arp_table[index]; + + if (arp->ResolveFlag && arp->error != 0) + { + // resolver error - Display Error Code + sprintf(AXPORT->hostaddr, "Error %d", arp->error); + } + else + { + if (arp->IPv6) + Format_Addr((unsigned char *)&arp->destaddr6.sin6_addr, AXPORT->hostaddr, TRUE); + else + Format_Addr((unsigned char *)&arp->destaddr.sin_addr, AXPORT->hostaddr, FALSE); + } + + ConvFromAX25(arp->callsign, Normcall); + + Flags[0] = 0; + + if (arp->BCFlag) + strcat(Flags, "B "); + + if (arp->TCPState == TCPConnected) + strcat(Flags, "C "); + + if (arp->AutoAdded) + strcat(Flags, "A"); + + if (arp->port == arp->SourcePort) + Bufferptr = Cmdprintf(Session, Bufferptr,"%.10s = %.64s %d = %-.42s %s\r", + Normcall, + arp->hostname, + arp->port, + AXPORT->hostaddr, + Flags); + + else + Bufferptr = Cmdprintf(Session, Bufferptr,"%.10s = %.64s %d<%d = %-.42s %s\r", + Normcall, + arp->hostname, + arp->port, + arp->SourcePort, + AXPORT->hostaddr, + Flags); + + index++; + } + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID AXMHEARD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + // DISPLAY AXIP Mheard info + + int Port = 0, index = 0; + char * ptr, *Context; + struct PORTCONTROL * PORT = NULL; + struct AXIPPORTINFO * AXPORT; + int n = MHENTRIES; + char Normcall[11]; + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + Port = atoi(ptr); + + if (Port) + PORT = GetPortTableEntryFromPortNum(Port); + + if (PORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + AXPORT = Portlist[Port]; + + if (AXPORT == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not an AXIP port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "AXIP Mheard for Port %d\r", Port); + + while (index < MaxMHEntries) + { + if (AXPORT->MHTable[index].proto != 0) + { + char Addr[80]; + + Format_Addr((unsigned char *)&AXPORT->MHTable[index].ipaddr6, Addr, AXPORT->MHTable[index].IPv6); + + Normcall[ConvFromAX25(AXPORT->MHTable[index].callsign, Normcall)] = 0; + + Bufferptr = Cmdprintf(Session, Bufferptr, "%-10s%-15s %c %-6d %-25s%c\r", Normcall, + Addr, + AXPORT->MHTable[index].proto, + AXPORT->MHTable[index].port, + asctime(gmtime( &AXPORT->MHTable[index].LastHeard )), + (AXPORT->MHTable[index].Keepalive == 0) ? ' ' : 'K'); + + Bufferptr[-3] = ' '; // Clear CR returned by asctime + } + + index++; + } + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +#pragma pack() + +extern char WL2KCall[10]; +extern char WL2KLoc[7]; + +BOOL GetWL2KSYSOPInfo(char * Call, char * _REPLYBUFFER); +BOOL UpdateWL2KSYSOPInfo(char * Call, char * SQL); + +VOID WL2KSYSOP(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char _REPLYBUFFER[1000] = ""; + + char LastUpdated[100]; + char Name[100] = ""; + char Addr1[100] = ""; + char Addr2[100] = ""; + char City[100] = ""; + char State[100] = ""; + char Country[100] = ""; + char PostCode[100] = ""; + char Email[100] = ""; + char Website[100] = ""; + char Phone[100] = ""; + char Data[100] = ""; + char LOC[100] = ""; + BOOL Exists = TRUE; + time_t LastUpdateSecs = 0; + char * ptr1, * ptr2; + + SOCKET sock; + + int Len; + char Message[2048]; + + if (WL2KCall[0] < 33) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Winlink reporting is not configured\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + + if (GetWL2KSYSOPInfo(WL2KCall, _REPLYBUFFER) == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Failed to connect to WL2K Database\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (strstr(_REPLYBUFFER, "\"ErrorMessage\":")) + Exists = FALSE; + + GetJSONValue(_REPLYBUFFER, "\"SysopName\":", Name); + GetJSONValue(_REPLYBUFFER, "\"StreetAddress1\":", Addr1); + GetJSONValue(_REPLYBUFFER, "\"StreetAddress2\":", Addr2); + GetJSONValue(_REPLYBUFFER, "\"City\":", City); + GetJSONValue(_REPLYBUFFER, "\"State\":", State); + GetJSONValue(_REPLYBUFFER, "\"Country\":", Country); + GetJSONValue(_REPLYBUFFER, "\"PostalCode\":", PostCode); + GetJSONValue(_REPLYBUFFER, "\"Email\":", Email); + GetJSONValue(_REPLYBUFFER, "\"Website\":", Website); + GetJSONValue(_REPLYBUFFER, "\"Phones\":", Phone); + GetJSONValue(_REPLYBUFFER, "\"Comments\":", Data); + GetJSONValue(_REPLYBUFFER, "\"GridSquare\":", LOC); + GetJSONValue(_REPLYBUFFER, "\"Timestamp\":", LastUpdated); + + ptr1 = strchr(LastUpdated, '('); + + if (ptr1) + { + ptr2 = strchr(++ptr1, ')'); + + if (ptr2) + { + *(ptr2 - 3) = 0; // remove millisecs + LastUpdateSecs = atoi(ptr1); + + FormatTime3(LastUpdated, LastUpdateSecs); + } + } + + if (_memicmp(CmdTail, "SET ", 4) == 0) + { + if (Exists) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Record already exists in WL2K Database\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // Set New Values. Any other params are values to set, separated by | + +// ptr1 = strtok_s(&CmdTail[4], ",", &Context); + +// if (ptr1 == NULL) +// goto DoReplace; + +// strcpy(Name, ptr1); + +//DoReplace: + + Len = sprintf(Message, + "\"Callsign\":\"%s\"," + "\"GridSquare\":\"%s\"," + "\"SysopName\":\"%s\"," + "\"StreetAddress1\":\"%s\"," + "\"StreetAddress2\":\"%s\"," + "\"City\":\"%s\"," + "\"State\":\"%s\"," + "\"Country\":\"%s\"," + "\"PostalCode\":\"%s\"," + "\"Email\":\"%s\"," + "\"Phones\":\"%s\"," + "\"Website\":\"%s\"," + "\"Comments\":\"%s\",", + + WL2KCall, WL2KLoc, Name, Addr1, Addr2, City, State, Country, PostCode, Email, Phone, Website, Data); + + Debugprintf("Sending %s", Message); + + sock = OpenWL2KHTTPSock(); + + if (sock) + SendHTTPRequest(sock, "api.winlink.org", 80, + "/sysop/add", Message, Len, NULL); + + closesocket(sock); + + Bufferptr = Cmdprintf(Session, Bufferptr, "Database Updated\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (Exists) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "\rWL2K SYSOP Info for %s\r", WL2KCall); + Bufferptr = Cmdprintf(Session, Bufferptr, "Grid Square: %s\r", LOC); + Bufferptr = Cmdprintf(Session, Bufferptr, "Name: %s\r", Name); + Bufferptr = Cmdprintf(Session, Bufferptr, "Addr Line 1: %s\r", Addr1); + Bufferptr = Cmdprintf(Session, Bufferptr, "Addr Line 2: %s\r", Addr2); + Bufferptr = Cmdprintf(Session, Bufferptr, "City: %s\r", City); + Bufferptr = Cmdprintf(Session, Bufferptr, "State: %s\r", State); + Bufferptr = Cmdprintf(Session, Bufferptr, "Country: %s\r", Country); + Bufferptr = Cmdprintf(Session, Bufferptr, "PostCode: %s\r", PostCode); + Bufferptr = Cmdprintf(Session, Bufferptr, "Email Address: %s\r", Email); + Bufferptr = Cmdprintf(Session, Bufferptr, "Website: %s\r", Website); + Bufferptr = Cmdprintf(Session, Bufferptr, "Phone: %s\r", Phone); + Bufferptr = Cmdprintf(Session, Bufferptr, "Additional Data: %s\r", Data); + Bufferptr = Cmdprintf(Session, Bufferptr, "Last Updated: %s\r", LastUpdated); + } + else + Bufferptr = Cmdprintf(Session, Bufferptr, "No SYSOP record for %s\r", WL2KCall); + + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + +VOID CloseKISSPort(struct PORTCONTROL * PortVector); + +VOID STOPCMS(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char _REPLYBUFFER[1000] = ""; + char * ptr, * Context; + + int portno; + + struct TNCINFO * TNC; + struct TCPINFO * TCP; + struct PORTCONTROL * PORT = PORTTABLE; + int n = NUMBEROFPORTS; + + // Get port number + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + { + portno = atoi (ptr); + + if (portno) + { + while (n--) + { + if (PORT->PORTNUMBER == portno) + { + TNC = TNCInfo[portno]; + + if (!TNC || !TNC->TCPInfo) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not a Telnet Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + TCP = TNC->TCPInfo; + + TCP->CMS = 0; + TCP->CMSOK = FALSE; +#ifndef LINBPQ + CheckMenuItem(TCP->hActionMenu, 3, MF_BYPOSITION | TCP->CMS<<3); + SetWindowText(TCP->hCMSWnd, "CMS Off"); +#endif + Bufferptr = Cmdprintf(Session, Bufferptr, "CMS Server Disabled\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + PORT = PORT->PORTPOINTER; + } + } + } + + // Bad port + + strcpy(Bufferptr, BADPORT); + Bufferptr += (int)strlen(BADPORT); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + + +VOID STARTCMS(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char _REPLYBUFFER[1000] = ""; + char * ptr, * Context; + + int portno; + + struct TNCINFO * TNC; + struct TCPINFO * TCP; + struct PORTCONTROL * PORT = PORTTABLE; + int n = NUMBEROFPORTS; + + // Get port number + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + { + portno = atoi (ptr); + + if (portno) + { + while (n--) + { + if (PORT->PORTNUMBER == portno) + { + TNC = TNCInfo[portno]; + + if (!TNC || !TNC->TCPInfo) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not a Telnet Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + TCP = TNC->TCPInfo; + TCP->CMS = 1; +#ifndef LINBPQ + CheckMenuItem(TCP->hActionMenu, 3, MF_BYPOSITION | TCP->CMS<<3); +#endif + CheckCMS(TNC); + + Bufferptr = Cmdprintf(Session, Bufferptr, "CMS Server Enabled\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + PORT = PORT->PORTPOINTER; + } + } + } + + // Bad port + + strcpy(Bufferptr, BADPORT); + Bufferptr += (int)strlen(BADPORT); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + + +VOID STOPPORT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char _REPLYBUFFER[1000] = ""; + char * ptr, * Context; + + int portno; + struct PORTCONTROL * PORT = PORTTABLE; + int n = NUMBEROFPORTS; + + // Get port number + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + { + portno = atoi (ptr); + + if (portno) + { + while (n--) + { + if (PORT->PORTNUMBER == portno) + { + struct KISSINFO * KISS; + + if (PORT->PORTSTOPCODE) + { + // Port has Close Routine + + PORT->PortStopped = TRUE; + + if (PORT->PORTSTOPCODE(PORT)) + Bufferptr = Cmdprintf(Session, Bufferptr, "Port Closed\r"); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Port Close Failed\r"); + + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + if (PORT->PORTTYPE != 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not a KISS Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (PORT->PORTIPADDR.s_addr || PORT->KISSSLAVE) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not a serial port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + KISS = (struct KISSINFO *) PORT; + + if (KISS->FIRSTPORT != KISS) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not first port of a Multidrop Set\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + CloseKISSPort(PORT); + PORT->PortStopped = TRUE; + Bufferptr = Cmdprintf(Session, Bufferptr, "Port Closed\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + + return; + } + PORT = PORT->PORTPOINTER; + } + } + } + + // Bad port + + strcpy(Bufferptr, BADPORT); + Bufferptr += (int)strlen(BADPORT); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + + +VOID STARTPORT(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char _REPLYBUFFER[1000] = ""; + char * ptr, * Context; + + int portno; + struct PORTCONTROL * PORT = PORTTABLE; + int n = NUMBEROFPORTS; + + // Get port number + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + { + portno = atoi (ptr); + + if (portno) + { + while (n--) + { + if (PORT->PORTNUMBER == portno) + { + struct KISSINFO * KISS; + + if (PORT->PORTSTARTCODE) + { + // Port has Open Routine + + PORT->PortStopped = FALSE; + + if (PORT->PORTSTARTCODE(PORT)) + Bufferptr = Cmdprintf(Session, Bufferptr, "Port Opened\r"); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Port Open Failed\r"); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + + if (PORT->PORTTYPE != 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not a KISS Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (PORT->PORTIPADDR.s_addr || PORT->KISSSLAVE) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not a serial port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + KISS = (struct KISSINFO *) PORT; + + if (KISS->FIRSTPORT != KISS) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not first port of a Multidrop Set\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (OpenConnection(PORT)) + Bufferptr = Cmdprintf(Session, Bufferptr, "Port Opened\r"); + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Port Open Failed\r"); + + PORT->PortStopped = FALSE; + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + PORT = PORT->PORTPOINTER; + } + } + } + + // Bad port + + strcpy(Bufferptr, BADPORT); + Bufferptr += (int)strlen(BADPORT); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + + + +int ASYSEND(struct PORTCONTROL * PortVector, char * buffer, int count); +int KissEncode(UCHAR * inbuff, UCHAR * outbuff, int len); + +VOID KISSCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char _REPLYBUFFER[1000] = ""; + char * ptr, * Context; + + int portno = 0; + struct PORTCONTROL * PORT = PORTTABLE; + int n = NUMBEROFPORTS; + UCHAR KissString[128]; + UCHAR ENCBUFF[256]; + int KissLen = 0; + unsigned char * Kissptr = KissString; + + // Send KISS Command to TNC + + // Get port number + + ptr = strtok_s(CmdTail, " ", &Context); + + if (ptr) + { + portno = atoi (ptr); + ptr = strtok_s(NULL, " ", &Context); + + while (ptr && ptr[0] && KissLen < 120) + { + *(Kissptr++) = atoi (ptr); + KissLen++; + ptr = strtok_s(NULL, " ", &Context); + + } + } + + if (portno == 0 || KissLen == 0) + { + strcpy(Bufferptr, BADMSG); + Bufferptr += (int)strlen(BADMSG); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + while (n--) + { + if (PORT->PORTNUMBER == portno) + { + struct KISSINFO * KISS; + + if (PORT->PORTTYPE != 0 && PORT->PORTTYPE != 22) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not a KISS Port\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + KISS = (struct KISSINFO *) PORT; + + if (KISS->FIRSTPORT != KISS) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Not first port of a Multidrop Set\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + // Send Command + + KissLen = KissEncode(KissString, ENCBUFF, KissLen); + + PORT = (struct PORTCONTROL *)KISS->FIRSTPORT; // ALL FRAMES GO ON SAME Q + + PORT->Session = Session; + PORT->LastKISSCmdTime = time(NULL); + + ASYSEND(PORT, ENCBUFF, KissLen); + + Bufferptr = Cmdprintf(Session, Bufferptr, "Command Sent\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + PORT = PORT->PORTPOINTER; + } + + + // Bad port + + strcpy(Bufferptr, BADPORT); + Bufferptr += (int)strlen(BADPORT); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + + +VOID FINDBUFFS(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + FindLostBuffers(); + +#ifdef WIN32 + Bufferptr = Cmdprintf(Session, Bufferptr, "Lost buffer info dumped to Debugview\r"); +#else + Bufferptr = Cmdprintf(Session, Bufferptr, "Lost buffer info dumped to syslog\r"); +#endif + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +VOID FLMSG(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * UserCMD) +{ + // Telnet Connection from FLMSG + CLOSECURRENTSESSION(Session); // Kills any crosslink, plus local link + ReleaseBuffer((UINT *)REPLYBUFFER); +} + +BOOL CheckExcludeList(UCHAR * Call) +{ + UCHAR * ptr1 = ExcludeList; + + while (*ptr1) + { + if (memcmp(Call, ptr1, 6) == 0) + return FALSE; + + ptr1 += 7; + } + + return TRUE; +} + + +void ListExcludedCalls(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + + UCHAR * ptr = ExcludeList; + char Normcall[10] = ""; + UCHAR AXCall[8] = ""; + + if (*CmdTail == ' ') + goto DISPLIST; + + if (*CmdTail == 'Z') + { + // CLEAR LIST + + memset(ExcludeList, 0, 70); + goto DISPLIST; + } + + ConvToAX25(CmdTail, AXCall); + + if (strlen(ExcludeList) < 70) + strcat(ExcludeList, AXCall); + +DISPLIST: + + while (*ptr) + { + Normcall[ConvFromAX25(ptr, Normcall)] = 0; + Bufferptr = Cmdprintf(Session, Bufferptr, "%s ", Normcall); + ptr += 7; + } + + *(Bufferptr++) = '\r'; + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +BOOL isSYSOP(TRANSPORTENTRY * Session, char * Bufferptr) +{ + if (Session->PASSWORD != 0xFFFF) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "%s", PASSWORDMSG); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + + return FALSE; + } + + return TRUE; +} + +VOID HELPCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + int FileSize; + char MsgFile[MAX_PATH]; + FILE * hFile; + char * MsgBytes; + struct stat STAT; + char * ptr1, * ptr, * ptr2; + + sprintf_s(MsgFile, sizeof(MsgFile), "%s/%s", BPQDirectory, "NodeHelp.txt"); + + if (stat(MsgFile, &STAT) == -1) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Help file not found\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile == NULL) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Help file not found\r"); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + MsgBytes = malloc(FileSize+1); + + fread(MsgBytes, 1, FileSize, hFile); + + fclose(hFile); + + MsgBytes[FileSize] = 0; + + ptr1 = MsgBytes; + + // Replace LF or CRLF with CR + + // First remove cr from crlf + + while(ptr2 = strstr(ptr1, "\r\n")) + { + memmove(ptr2, ptr2 + 1, strlen(ptr2)); + } + + // Now replace lf with cr + + ptr1 = MsgBytes; + + while (*ptr1) + { + if (*ptr1 == '\n') + *(ptr1) = '\r'; + + ptr1++; + } + + ptr = ptr1 = MsgBytes; + + Bufferptr = Cmdprintf(Session, Bufferptr, "\r"); + + // Read and send a line at a time, converting any line endings into CR + + while (*ptr1) + { + if (*ptr1 == '\r') + { + *(ptr1++) = 0; + + Bufferptr = Cmdprintf(Session, Bufferptr, "%s\r", ptr); + + ptr = ptr1; + } + else + ptr1++; + } + + free(MsgBytes); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); +} + +int UZ7HOSetFreq(int port, struct TNCINFO * TNC, struct AGWINFO * AGW, PDATAMESSAGE buff, PMSGWITHLEN buffptr); +int UZ7HOSetModem(int port, struct TNCINFO * TNC, struct AGWINFO * AGW, PDATAMESSAGE buff, PMSGWITHLEN buffptr); +int UZ7HOSetFlags(int port, struct TNCINFO * TNC, struct AGWINFO * AGW, PDATAMESSAGE buff, PMSGWITHLEN buffptr); + + +VOID UZ7HOCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + char * Cmd; + int port; + struct TNCINFO * TNC; + struct AGWINFO * AGW = 0; + PDATAMESSAGE buff; + PMSGWITHLEN buffptr; + + CmdTail = CmdTail + (OrigCmdBuffer - COMMANDBUFFER); // Replace with original case version + + Cmd = strlop(CmdTail, ' '); + port = atoi(CmdTail); + + // remove trailing spaces + + while(strlen(Cmd) && Cmd[strlen(Cmd) - 1] == ' ') + Cmd[strlen(Cmd) - 1] = 0; + + TNC = TNCInfo[port]; + + if (TNC) + AGW = TNC->AGWInfo; + + if (TNC == 0 || AGW == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - %d is not UZ7HO port\r", port); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + if (_memicmp(Cmd, "FREQ", 4) == 0 || _memicmp(Cmd, "MODEM", 5) == 0 || _memicmp(Cmd, "FLAGS", 5) == 0) + { + // Pass to procesing code in UZ7HO driver. This expects command in a PDATAMESSAGE amd places response in a PMSGWITHLEN buffer + + buff = (PDATAMESSAGE)GetBuff(); + buffptr = (PMSGWITHLEN)GetBuff(); + + if (buffptr == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "UZ7HO Command Failed - no buffers\r"); + if (buff) + ReleaseBuffer(buff); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + + + buff->LENGTH = sprintf(buff->L2DATA, "%s\r", Cmd) + MSGHDDRLEN + 1; + + if (_memicmp(Cmd, "FREQ", 4) == 0) + UZ7HOSetFreq(port, TNC, AGW, buff, buffptr); + else if (_memicmp(Cmd, "FLAGS", 5) == 0) + UZ7HOSetFlags(port, TNC, AGW, buff, buffptr); + else + UZ7HOSetModem(port, TNC, AGW, buff, buffptr); + + + Bufferptr = Cmdprintf(Session, Bufferptr, buffptr->Data); + + ReleaseBuffer(buff); + ReleaseBuffer(buffptr); + } + else + Bufferptr = Cmdprintf(Session, Bufferptr, "Invalid UZ7HO Command (not Freq Modem or FLAGS)\r"); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + +VOID QTSMCMD(TRANSPORTENTRY * Session, char * Bufferptr, char * CmdTail, CMDX * CMD) +{ + int port; + struct PORTCONTROL * PORT; + struct KISSINFO * KISS; + + CmdTail = CmdTail + (OrigCmdBuffer - COMMANDBUFFER); // Replace with original case version + + port = atoi(CmdTail); + + PORT = GetPortTableEntryFromPortNum(port); + + if (PORT == NULL || PORT->PORTTXROUTINE != KISSTX) // Must be a kiss like port + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - Port %d is not a KISS port\r", port); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + KISS = (struct KISSINFO *)PORT; + + if (KISS->QtSMModem == 0) + { + Bufferptr = Cmdprintf(Session, Bufferptr, "Error - Port %d has no QtSM information\r", port); + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; + } + + Bufferptr = Cmdprintf(Session, Bufferptr, "Modem %s Centre frequency %d\r", + (KISS->QtSMModem) ? KISS->QtSMModem : "Not Available", KISS->QtSMFreq); + + SendCommandReply(Session, REPLYBUFFER, (int)(Bufferptr - (char *)REPLYBUFFER)); + return; +} + + + + + + + + + + + + + diff --git a/CommonCode.c b/CommonCode.c index a392b17..e3504d1 100644 --- a/CommonCode.c +++ b/CommonCode.c @@ -4912,7 +4912,7 @@ SOCKET OpenHTTPSock(char * Host) { err = WSAGetLastError(); - Debugprintf("Resolve Failed for %s %d %x", "api.winlink.org", err, err); + Debugprintf("Resolve Failed for %s %d %x", Host, err, err); return 0 ; // Resolve failed } @@ -4945,7 +4945,7 @@ SOCKET OpenHTTPSock(char * Host) } static char HeaderTemplate[] = "POST %s HTTP/1.1\r\n" - "Accept: application/json\r\n" + "Accept: app N B lication/json\r\n" // "Accept-Encoding: gzip,deflate,gzip, deflate\r\n" "Content-Type: application/json\r\n" "Host: %s:%d\r\n" @@ -4955,14 +4955,24 @@ static char HeaderTemplate[] = "POST %s HTTP/1.1\r\n" "\r\n"; -VOID SendWebRequest(SOCKET sock, char * Host, char * Request, char * Params, int Len, char * Return) +DllExport VOID WINAPI SendWebRequest(char * Host, char * Request, char * Params, char * Return) { + SOCKET sock; int InputLen = 0; int inptr = 0; char Buffer[4096]; char Header[256]; char * ptr, * ptr1; int Sent; + int Len = strlen(Params); + + if (M0LTEMap == 0) + return; + + sock = OpenHTTPSock(Host); + + if (sock == 0) + return; #ifdef LINBPQ sprintf(Header, HeaderTemplate, Request, Host, 80, Len, "linbpq/", VersionString, Params); @@ -4976,6 +4986,7 @@ VOID SendWebRequest(SOCKET sock, char * Host, char * Request, char * Params, int { int Err = WSAGetLastError(); Debugprintf("Error %d from Web Update send()", Err); + closesocket(sock); return; } @@ -4987,12 +4998,10 @@ VOID SendWebRequest(SOCKET sock, char * Host, char * Request, char * Params, int { int Err = WSAGetLastError(); Debugprintf("Error %d from Web Update recv()", Err); + closesocket(sock); return; } - // As we are using a persistant connection, can't look for close. Check - // for complete message - inptr += InputLen; Buffer[inptr] = 0; @@ -5035,6 +5044,7 @@ VOID SendWebRequest(SOCKET sock, char * Host, char * Request, char * Params, int Debugprintf("Map Update failed - %s", Buffer); } + closesocket(sock); return; } } @@ -5046,6 +5056,7 @@ VOID SendWebRequest(SOCKET sock, char * Host, char * Request, char * Params, int { // Just accept anything until I've sorted things with Lee Debugprintf("%s", ptr1); + closesocket(sock); Debugprintf("Web Database update ok"); return; } @@ -5584,19 +5595,11 @@ void SendDataToPktMap(char *Msg) } ], - - */ // "contact": "string", // "neighbours": [{"node": "G7TAJ","port": "30"}] - sock = OpenHTTPSock("packetnodes.spots.radio"); - - if (sock == 0) - return; - - SendWebRequest(sock, "packetnodes.spots.radio", Request, Params, strlen(Params), Return); - closesocket(sock); + SendWebRequest("packetnodes.spots.radio", Request, Params, Return); } // ="{\"neighbours\": [{\"node\": \"G7TAJ\",\"port\": \"30\"}]}"; diff --git a/FormatHTML.vcproj.LAPTOP-Q6S4RP5Q.johnw.user b/FormatHTML.vcproj.LAPTOP-Q6S4RP5Q.johnw.user new file mode 100644 index 0000000..bed4096 --- /dev/null +++ b/FormatHTML.vcproj.LAPTOP-Q6S4RP5Q.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/L2Code-skigdebian.c b/L2Code-skigdebian.c new file mode 100644 index 0000000..a36854d --- /dev/null +++ b/L2Code-skigdebian.c @@ -0,0 +1,4143 @@ +/* +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 +*/ + +// +// C replacement for L2Code.asm +// +#define Kernel + +#define _CRT_SECURE_NO_DEPRECATE + + +#pragma data_seg("_BPQDATA") + +#include "time.h" +#include "stdio.h" + +#include "CHeaders.h" +#include "tncinfo.h" + +#define PFBIT 0x10 // POLL/FINAL BIT IN CONTROL BYTE + +#define REJSENT 1 // SET WHEN FIRST REJ IS SENT IN REPLY + // TO AN I(P) +#define RNRSET 0x2 // RNR RECEIVED FROM OTHER END +#define DISCPENDING 8 // SEND DISC WHEN ALL DATA ACK'ED +#define RNRSENT 0x10 // WE HAVE SEND RNR +#define POLLSENT 0x20 // POLL BIT OUTSTANDING + +#define ONEMINUTE 60*3 +#define TENSECS 10*3 +#define THREESECS 3*3 + + +VOID L2SENDCOMMAND(); +VOID L2ROUTINE(); +MESSAGE * SETUPL2MESSAGE(struct _LINKTABLE * LINK, UCHAR CMD); +VOID SendSupervisCmd(struct _LINKTABLE * LINK); +void SEND_RR_RESP(struct _LINKTABLE * LINK, UCHAR PF); +VOID L2SENDRESPONSE(struct _LINKTABLE * LINK, int CMD); +VOID L2SENDCOMMAND(struct _LINKTABLE * LINK, int CMD); +VOID ACKMSG(struct _LINKTABLE * LINK); +VOID InformPartner(struct _LINKTABLE * LINK, int Reason); +UINT RR_OR_RNR(struct _LINKTABLE * LINK); +VOID L2TIMEOUT(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT); +VOID CLEAROUTLINK(struct _LINKTABLE * LINK); +VOID SENDFRMR(struct _LINKTABLE * LINK); +char * SetupNodeHeader(struct DATAMESSAGE * Buffer); +VOID CLEARSESSIONENTRY(TRANSPORTENTRY * Session); +VOID SDFRMR(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT); +VOID SDNRCHK(struct _LINKTABLE * LINK, UCHAR CTL); +VOID RESETNS(struct _LINKTABLE * LINK, UCHAR NS); +VOID PROC_I_FRAME(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer); +VOID RESET2X(struct _LINKTABLE * LINK); +VOID RESET2(struct _LINKTABLE * LINK); +VOID CONNECTREFUSED(struct _LINKTABLE * LINK); +VOID SDUFRM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR CTL); +VOID SFRAME(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, UCHAR CTL, UCHAR MSGFLAG); +VOID SDIFRM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR CTL, UCHAR MSGFLAG); +VOID SENDCONNECTREPLY(struct _LINKTABLE * LINK); +VOID SETUPNEWL2SESSION(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR MSGFLAG); +BOOL FindNeighbour(UCHAR * Call, int Port, struct ROUTE ** REQROUTE); +VOID L2SABM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR MSGFLAG); +VOID L2SENDUA(struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER); +VOID L2SENDDM(struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER); +VOID L2SENDRESP(struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL); +int COUNTLINKS(int Port); +VOID L2_PROCESS(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR CTL, UCHAR MSGFLAG); +TRANSPORTENTRY * SetupSessionForL2(struct _LINKTABLE * LINK); +BOOL cATTACHTOBBS(TRANSPORTENTRY * Session, UINT Mask, int Paclen, int * AnySessions); +VOID PUT_ON_PORT_Q(struct PORTCONTROL * PORT, MESSAGE * Buffer); +VOID L2SWAPADDRESSES(MESSAGE * Buffer); +BOOL FindLink(UCHAR * LinkCall, UCHAR * OurCall, int Port, struct _LINKTABLE ** REQLINK); +VOID SENDSABM(struct _LINKTABLE * LINK); +VOID L2SENDXID(struct _LINKTABLE * LINK); +VOID __cdecl Debugprintf(const char * format, ...); +VOID Q_IP_MSG(MESSAGE * Buffer); +VOID PROCESSNODEMESSAGE(MESSAGE * Msg, struct PORTCONTROL * PORT); +VOID L2LINKACTIVE(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL, UCHAR MSGFLAG); +BOOL CompareAliases(UCHAR * c1, UCHAR * c2); +VOID L2FORUS(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL, UCHAR MSGFLAG); +VOID Digipeat(struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR * OurCall, int toPort, int UIOnly); +VOID DigiToMultiplePorts(struct PORTCONTROL * PORTVEC, PMESSAGE Msg); +VOID MHPROC(struct PORTCONTROL * PORT, MESSAGE * Buffer); +BOOL CheckForListeningSession(struct PORTCONTROL * PORT, MESSAGE * Msg); +VOID L2SENDINVALIDCTRL(struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL); +UCHAR * SETUPADDRESSES(struct _LINKTABLE * LINK, PMESSAGE Msg); +VOID ProcessXIDCommand(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL, UCHAR MSGFLAG); +int CountBits(uint32_t in); +void AttachKISSHF(struct PORTCONTROL * PORT, MESSAGE * Buffer); +void DetachKISSHF(struct PORTCONTROL * PORT); +void KISSHFConnected(struct PORTCONTROL * PORT, struct _LINKTABLE * LINK); +void WriteConnectLog(char * fromcall, char * tocall, UCHAR * Mode); +int seeifInterlockneeded(struct PORTCONTROL * PORT); +int seeifUnlockneeded(struct _LINKTABLE * LINK); +int CheckKissInterlock(struct PORTCONTROL * MYPORT, int Exclusive); +void hookL2SessionAccepted(int Port, char * fromCall, char * toCall, struct _LINKTABLE * LINK); +void hookL2SessionDeleted(int Port, char * fromCall, char * toCall, struct _LINKTABLE * LINK); +void hookL2SessionAttempt(int Port, char * fromCall, char * toCall, struct _LINKTABLE * LINK); + + +extern int REALTIMETICKS; + +// MSGFLAG contains CMD/RESPONSE BITS + +#define CMDBIT 4 // CURRENT MESSAGE IS A COMMAND +#define RESP 2 // CURRENT MSG IS RESPONSE +#define VER1 1 // CURRENT MSG IS VERSION 1 + +// FRMR REJECT FLAGS + +#define SDINVC 1 // INVALID COMMAND +#define SDNRER 8 // INVALID N(R) + + + +UCHAR NO_CTEXT = 0; +UCHAR ALIASMSG = 0; +extern UINT APPLMASK; +static UCHAR ISNETROMMSG = 0; +UCHAR MSGFLAG = 0; +extern char * ALIASPTR; + +UCHAR QSTCALL[7] = {'Q'+'Q','S'+'S','T'+'T',0x40,0x40,0x40,0xe0}; // QST IN AX25 +UCHAR NODECALL[7] = {0x9C, 0x9E, 0x88, 0x8A, 0xA6, 0x40, 0xE0}; // 'NODES' IN AX25 FORMAT + +extern BOOL LogAllConnects; + +APPLCALLS * APPL; + +VOID L2Routine(struct PORTCONTROL * PORT, PMESSAGE Buffer) +{ + // LEVEL 2 PROCESSING + + MESSAGE * ADJBUFFER; + struct _LINKTABLE * LINK; + UCHAR * ptr; + int n; + UCHAR CTL; + uintptr_t Work; + UCHAR c; + + // Check for invalid length (< 22 7Header + 7Addr + 7Addr + CTL + + if (Buffer->LENGTH < (18 + sizeof(void *))) + { + Debugprintf("BPQ32 Bad L2 Msg Port %d Len %d", PORT->PORTNUMBER, Buffer->LENGTH); + ReleaseBuffer(Buffer); + return; + } + + PORT->L2FRAMES++; + + ALIASMSG = 0; + APPLMASK = 0; + ISNETROMMSG = 0; + + MSGFLAG = 0; // CMD/RESP UNDEFINED + + // Check for Corrupted Callsign in Origin (to keep MH list clean) + + ptr = &Buffer->ORIGIN[0]; + n = 6; + + c = *(ptr) >> 1; + + if (c == ' ') // Blank Call + { + Debugprintf("BPQ32 Blank Call Port %d", PORT->PORTNUMBER); + ReleaseBuffer(Buffer); + return; + } + + while(n--) + { + // Try a bit harder to detect corruption + + c = *(ptr++); + + if (c & 1) + { + ReleaseBuffer(Buffer); + return; + } + + c = c >> 1; + + if (!isalnum(c) && !(c == '#') && !(c == ' ')) + { + ReleaseBuffer(Buffer); + return; + } + } + + // Check Digis if present + + if ((Buffer->ORIGIN[6] & 1) == 0) // Digis + { + ptr = &Buffer->CTL; + n = 6; + + while(n--) + { + c = *(ptr++); + + if (c & 1) + { + ReleaseBuffer(Buffer); + return; + } + + c = c >> 1; + + if (!isalnum(c) && !(c == '#') && !(c == ' ')) + { + ReleaseBuffer(Buffer); + return; + } + } + } + + BPQTRACE(Buffer, TRUE); // TRACE - RX frames to APRS + + if (PORT->PORTMHEARD) + MHPROC(PORT, Buffer); + + /// TAJ added 07/12/2020 for 'all RX traffic as IfinOctects + + InOctets[PORT->PORTNUMBER] += Buffer->LENGTH - MSGHDDRLEN; + + // CHECK THAT ALL DIGIS HAVE BEEN ACTIONED, + // AND ADJUST FOR DIGIPEATERS IF PRESENT + + n = 8; // MAX DIGIS + ptr = &Buffer->ORIGIN[6]; // End of Address bit + + while ((*ptr & 1) == 0) + { + // MORE TO COME + + ptr += 7; + + if ((*ptr & 0x80) == 0) // Digi'd bit + { + // FRAME HAS NOT BEEN REPEATED THROUGH CURRENT DIGI - + // SEE IF WE ARE MEANT TO DIGI IT + + struct XDIGI * XDigi = PORT->XDIGIS; // Cross port digi setup + + ptr -= 6; // To start of Call + + if (CompareCalls(ptr, MYCALL) || CompareAliases(ptr, MYALIAS) || + CompareCalls(ptr, PORT->PORTALIAS) || CompareCalls(ptr, PORT->PORTALIAS2)) + { + Digipeat(PORT, Buffer, ptr, 0, 0); // Digi it (if enabled) + return; + } + + while (XDigi) + { + if (CompareCalls(ptr, XDigi->Call)) + { + Digipeat(PORT, Buffer, ptr, XDigi->Port, XDigi->UIOnly); // Digi it (if enabled) + return; + } + XDigi = XDigi->Next; + } + + ReleaseBuffer(Buffer); + return; // not complete and not for us + } + n--; + + if (n == 0) + { + ReleaseBuffer(Buffer); + return; // Corrupt - no end of address bit + } + } + + // Reached End of digis, and all actioned, so can process it + + Work = (uintptr_t)&Buffer->ORIGIN[6]; + ptr -= Work; // ptr is now length of digis + + Work = (uintptr_t)Buffer; + ptr += Work; + + ADJBUFFER = (MESSAGE * )ptr; // ADJBUFFER points to CTL, etc. allowing for digis + + // GET CMD/RESP BITS + + if (Buffer->DEST[6] & 0x80) + { + if (Buffer->ORIGIN[6] & 0x80) // Both set, assume V1 + MSGFLAG |= VER1; + else + MSGFLAG |= CMDBIT; + } + else + { + if (Buffer->ORIGIN[6] & 0x80) // Only Dest Set + MSGFLAG |= RESP; + else + MSGFLAG |= VER1; // Neither, assume V1 + } + + // SEE IF FOR AN ACTIVE LINK SESSION + + CTL = ADJBUFFER->CTL; + + // IF A UI, THERE IS NO SESSION + + if (FindLink(Buffer->ORIGIN, Buffer->DEST, PORT->PORTNUMBER, &LINK)) + { + L2LINKACTIVE(LINK, PORT, Buffer,ADJBUFFER, CTL, MSGFLAG); + return; + } + + // NOT FOR ACTIVE LINK - SEE IF ADDRESSED TO OUR ADDRESSES + + // FIRST TRY PORT ADDR/ALIAS + + if(PORT->PORTBBSFLAG == 1) + goto PORTCALLISBBS; // PORT CALL/ALIAS ARE FOR BBS + + if (NODE) + goto USING_NODE; + +PORTCALLISBBS: + + // NODE IS NOT ACTIVE, SO PASS CALLS TO PORTCALL/ALIAS TO BBS + + APPLMASK = 1; + + if (CompareCalls(Buffer->DEST, NETROMCALL)) + { + ISNETROMMSG = 1; + goto FORUS; + } + if (PORT->PORTL3FLAG) // L3 Only Port? + goto NOTFORUS; // If L3ONLY, only accept calls to NETROMCALL + + ISNETROMMSG = 0; + +USING_NODE: + + if (CompareCalls(Buffer->DEST, PORT->PORTCALL)) + goto FORUS; + + ALIASMSG = 1; + + if (CompareAliases(Buffer->DEST, PORT->PORTALIAS)) // only compare 6 bits - allow any ssid + goto FORUS; + + if (NODE == 0) + goto TRYBBS; // NOT USING NODE SYSTEM + + ALIASMSG = 0; + + if (CompareCalls(Buffer->DEST, MYCALL)) + goto FORUS; + + ALIASMSG = 1; + + if (CompareAliases(Buffer->DEST, MYALIAS)) // only compare 6 bits - allow any ssid + goto FORUS; + +TRYBBS: + + if (BBS == 0) + goto NOWTRY_NODES; // NOT USING BBS CALLS + + // TRY APPLICATION CALLSIGNS/ALIASES + + + APPLMASK = 1; + ALIASPTR = &CMDALIAS[0][0]; + + n = NumberofAppls; + + APPL = APPLCALLTABLE; + + while (n--) + { + if (APPL->APPLCALL[0] > 0x40) // Valid ax.25 addr + { + // WE MAY NOT BE ALLOWED TO USE THE BBS CALL ON SOME BANDS DUE TO + // THE RATHER ODD UK LICENCING RULES! + // For backward compatibility only apply to appl 1 + + if ((PORT->PERMITTEDAPPLS & APPLMASK) != 0) + { + ALIASMSG = 0; + + if (CompareCalls(Buffer->DEST, APPL->APPLCALL)) + goto FORUS; + + ALIASMSG = 1; + + if (CompareAliases(Buffer->DEST, APPL->APPLALIAS)) // only compare 6 bits - allow any ssid + goto FORUS; + + if (CompareAliases(Buffer->DEST, APPL->L2ALIAS)) // only compare 6 bits - allow any ssid + goto FORUS; + } + } + APPLMASK <<= 1; + ALIASPTR += ALIASLEN; + APPL++; + } + + // NOT FOR US - SEE IF 'NODES' OR IP/ARP BROADCAST MESSAGE + +NOWTRY_NODES: + + if (CompareCalls(Buffer->DEST, QSTCALL)) + { + Q_IP_MSG(Buffer); // IP BROADCAST + return; + } + + if (ADJBUFFER->PID != 0xCF) // NETROM MSG? + goto NOTFORUS; // NO + + if (CompareCalls(Buffer->DEST, NODECALL)) + { + if (Buffer->L2DATA[0] == 0xff) // Valid NODES Broadcast + { + PROCESSNODEMESSAGE(Buffer, PORT); + } + } + + ReleaseBuffer(Buffer); + return; + +NOTFORUS: + // + // MAY JUST BE A REPLY TO A 'PRIMED' CQ CALL + // + if ((CTL & ~PFBIT) == SABM) + if (CheckForListeningSession(PORT, Buffer)) + return; // Used buffer to send UA + + ReleaseBuffer(Buffer); + return; + +FORUS: + + // if a UI frame and UIHook Specified, call it + + if (PORT->UIHook && CTL == 3) + PORT->UIHook(LINK, PORT, Buffer, ADJBUFFER, CTL, MSGFLAG); + + L2FORUS(LINK, PORT, Buffer, ADJBUFFER, CTL, MSGFLAG); +} + + +VOID MHPROC(struct PORTCONTROL * PORT, MESSAGE * Buffer) +{ + PMHSTRUC MH = PORT->PORTMHEARD; + PMHSTRUC MHBASE = MH; + int i; + int OldCount = 0; + char Freq[64] = ""; + char DIGI = '*'; + double ReportFreq = 0; + + // if port has a freq associated with it use it + + GetPortFrequency(PORT->PORTNUMBER, Freq); + + // if (Buffer->ORIGIN[6] & 1) + DIGI = 0; // DOn't think we want to do this + + // See if in list + + for (i = 0; i < MHENTRIES; i++) + { + if ((MH->MHCALL[0] == 0) || (CompareCalls(Buffer->ORIGIN, MH->MHCALL) && MH->MHDIGI == DIGI)) // Spare or our entry + { + OldCount = MH->MHCOUNT; + goto DoMove; + } + MH++; + } + + // TABLE FULL AND ENTRY NOT FOUND - MOVE DOWN ONE, AND ADD TO TOP + + i = MHENTRIES - 1; + + // Move others down and add at front +DoMove: + if (i != 0) // First + memmove(MHBASE + 1, MHBASE, i * sizeof(MHSTRUC)); + + memcpy (MHBASE->MHCALL, Buffer->ORIGIN, 7 * 9); // Save Digis + MHBASE->MHDIGI = DIGI; + MHBASE->MHTIME = time(NULL); + MHBASE->MHCOUNT = ++OldCount; + strcpy(MHBASE->MHFreq, Freq); + MHBASE->MHLocator[0] = 0; + + return; +} + + +int CountFramesQueuedOnSession(TRANSPORTENTRY * Session) +{ + // COUNT NUMBER OF FRAMES QUEUED ON A SESSION + + if (Session == 0) + return 0; + + if (Session->L4CIRCUITTYPE & BPQHOST) + { + return C_Q_COUNT(&Session->L4TX_Q); + } + + if (Session->L4CIRCUITTYPE & SESSION) + { + // L4 SESSION - GET NUMBER UNACKED, AND ADD NUMBER ON TX QUEUE + + int Count = C_Q_COUNT(&Session->L4TX_Q); + UCHAR Unacked = Session->TXSEQNO - Session->L4WS; + + return Count + Unacked; + } + + if (Session->L4CIRCUITTYPE & PACTOR) + { + // PACTOR Type - Frames are queued on the Port Entry + + struct PORTCONTROL * PORT = Session->L4TARGET.PORT; + EXTPORTDATA * EXT = (EXTPORTDATA *)PORT; + + int ret = EXT->FramesQueued; + + // Check L4 Queue as messages can stay there briefly + + ret += C_Q_COUNT(&Session->L4RX_Q); + + return ret + C_Q_COUNT(&PORT->PORTTX_Q); + } + + // L2 CIRCUIT + + { + int SessCount = C_Q_COUNT(&Session->L4TX_Q); + struct _LINKTABLE * LINK = Session->L4TARGET.LINK; + int L2 = COUNT_AT_L2(LINK); + + return SessCount + L2; + } +} + +int CHECKIFBUSYL2(TRANSPORTENTRY * Session) +{ + // RETURN TOP BIT OF AL SET IF SESSION PARTNER IS BUSY + + if (Session->L4CROSSLINK) // CONNECTED? + { + Session = Session->L4CROSSLINK; + + if (CountFramesQueuedOnSession(Session) > 10) + return L4BUSY;; + } + return 0; +} + +VOID L2FORUS(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL, UCHAR MSGFLAG) +{ + // MESSAGE ADDRESSED TO OUR CALL OR ALIAS, BUT NOT FOR AN ACTIVE SESSION + + // LINK points to an empty link table entry + + struct ROUTE * ROUTE; + int CTLlessPF = CTL & ~PFBIT; + + PORT->L2FRAMESFORUS++; + + NO_CTEXT = 0; + + // ONLY SABM or UI ALLOWED IF NO SESSION + // Plus XID/TEST/SABME if V2.2 support enabled + + if (CTLlessPF == 3) // UI + { + // A UI ADDRESSED TO US - SHOULD ONLY BE FOR IP, or possibly addressed NODES + + switch(ADJBUFFER->PID) + { + case 0xcf: // Netrom + + if (Buffer->L2DATA[0] == 0xff) // NODES + PROCESSNODEMESSAGE(Buffer, PORT); + + break; + + case 0xcc: // TCP + case 0xcd: // ARP + case 0x08: // NOS FRAGMENTED AX25 TCP/IP + + Q_IP_MSG( Buffer); + return; + } + + ReleaseBuffer(Buffer); + return; + } + + if (PORT->PortUIONLY) // Port is for UI only + { + ReleaseBuffer(Buffer); + return; + } + + if (CTLlessPF == SABME) + { + // Although some say V2.2 requires SABME I don't agree! + + // Reject until we support Mod 128 + + L2SENDINVALIDCTRL(PORT, Buffer, ADJBUFFER, CTL); + return; + } + + if (CTLlessPF == SREJ) // Used to see if other end supports SREJ on 2.0 + { + // Send FRMR if dont support SREJ + // Send DM if we do + + if (SUPPORT2point2) + L2SENDRESP(PORT, Buffer, ADJBUFFER, DM); + else + L2SENDINVALIDCTRL(PORT, Buffer, ADJBUFFER, CTL); + + return; + } + + if (CTLlessPF == XID) + { + // Send FRMR if we only support V 2.0 + + if (SUPPORT2point2 == FALSE) + { + L2SENDINVALIDCTRL(PORT, Buffer, ADJBUFFER, CTL); + return; + } + // if Support 2.2 drop through + } + + if (CTLlessPF == TEST) + { + // I can't see amy harm in replying to TEST + + L2SENDRESP(PORT, Buffer, ADJBUFFER, TEST); + return; + } + + +// if (CTLlessPF != SABM && CTLlessPF != SABME) + if (CTLlessPF != SABM && CTLlessPF != XID) + { + if ((MSGFLAG & CMDBIT) && (CTL & PFBIT)) // Command with P? + L2SENDDM(PORT, Buffer, ADJBUFFER); + else + ReleaseBuffer(Buffer); // Ignore if not + + return; + } + + // Exclude and limit tests are done for XID and SABM + + if (NODE == 0 && BBS == 0) // Don't want any calls + { + ReleaseBuffer(Buffer); + return; + } + +#ifdef EXCLUDEBITS + + // CHECK ExcludeList + + if (CheckExcludeList(Buffer->ORIGIN) == 0) + { + ReleaseBuffer(Buffer); + return; + } +#endif + + // IF WE HAVE A PERMITTED CALLS LIST, SEE IF HE IS IN IT + + if (PORT->PERMITTEDCALLS) + { + UCHAR * ptr = PORT->PERMITTEDCALLS; + + while (TRUE) + { + if (memcmp(Buffer->ORIGIN, ptr, 6) == 0) // Ignore SSID + break; + + ptr += 7; + + if ((*ptr) == 0) // Not in list + { + ReleaseBuffer(Buffer); + return; + } + } + } + + // IF CALL REQUEST IS FROM A LOCKED NODE WITH QUALITY ZERO, IGNORE IT + + if (FindNeighbour(Buffer->ORIGIN, PORT->PORTNUMBER, &ROUTE)) + { + // From a known node + + NO_CTEXT = 1; + + if (ROUTE->NEIGHBOUR_FLAG == 1 && ROUTE->NEIGHBOUR_QUAL == 0) // Locked, qual 0 + { + ReleaseBuffer(Buffer); + return; + } + } + + // CHECK PORT CONNECT LIMITS + + if (PORT->USERS) + { + if (COUNTLINKS(PORT->PORTNUMBER) >= PORT->USERS) + { + L2SENDDM(PORT, Buffer, ADJBUFFER); + return; + } + } + + // if KISSHF, check if attached. If so, reject. If not, attach. + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + { + struct TNCINFO * TNC = PORT->TNC; + + if (TNC->PortRecord->ATTACHEDSESSIONS[0]) + { + L2SENDDM(PORT, Buffer, ADJBUFFER); + return; + } + } + + // OK to accept SABM or XID + + if (CTLlessPF == XID) + { + ProcessXIDCommand(LINK, PORT, Buffer, ADJBUFFER, CTL, MSGFLAG); + return; + } + + // Not XID, so must be SABM + + L2SABM(LINK, PORT, Buffer, ADJBUFFER, MSGFLAG); // Process the SABM +} + + +VOID ProcessXIDCommand(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL, UCHAR MSGFLAG) +{ + // I think it is fairly safe to accept XID as soon as we + // can process SREJ, but only accept Mod 8 and 256 Byte frames + + // I think the only way to run 2.2 Mod 8 is to preceed a + // SABM with XID, but others don't seem to agree! + + // Run through XID fields, changing any we don't like, + // then return an XID response + + // Decode and process XID + + UCHAR * ptr = &ADJBUFFER->PID; + UCHAR * ptr1, * ptr2; + UCHAR TEMPDIGI[57]; + int n; + + // Check Interlock - should we also check exclude etc?. No, checked in L2FORUS + + if (CheckKissInterlock(PORT, TRUE)) // Interlock with ARDOP/VARA etc + { + L2SENDDM(PORT, Buffer, ADJBUFFER); + return; + } + + if (*ptr++ == 0x82 && *ptr++ == 0x80) + { + int Type; + int Len; + unsigned int value; + int xidlen = *(ptr++) << 8; + xidlen += *ptr++; + + // XID is set of Type, Len, Value n-tuples + + while (xidlen > 0) + { + Type = *ptr++; + Len = *ptr++; + + value = 0; + xidlen -= (Len + 2); + + while (Len--) + { + value <<=8; + value += *ptr++; + } + switch(Type) + { + case 2: //Bin fields + + break; + + case 3: + + if ((value & OPMustHave) != OPMustHave) + goto BadXID; + + if ((value & OPMod8) == 0) + goto BadXID; + + if ((value & OPSREJMult) == 0) + goto BadXID; + + + // Reply Mod 8 SREJMULTI + + value = OPMustHave | OPSREJMult | OPMod8; + ptr -=3; + *ptr++ = value >> 16; + *ptr++ = value >> 8; + *ptr++ = value; + + + break; + + case 6: //RX Size + + break; + + case 8: //RX Window + + break; + } + } + + // Send back as XID response + + LINK->L2STATE = 1; // XID received + LINK->Ver2point2 = TRUE; // Must support 2.2 if sent XID + LINK->L2TIME = PORT->PORTT1; + + LINK->LINKPORT = PORT; + + // save calls so we can match up SABM when it comes + + memcpy(LINK->LINKCALL, Buffer->ORIGIN, 7); + LINK->LINKCALL[6] &= 0x1e; // Mask SSID + + memcpy(LINK->OURCALL, Buffer->DEST, 7); + + LINK->OURCALL[6] &= 0x1e; // Mask SSID + + memset(LINK->DIGIS, 0, 56); // CLEAR DIGI FIELD IN CASE RECONNECT + + if ((Buffer->ORIGIN[6] & 1) == 0) // End of Address + { + // THERE ARE DIGIS TO PROCESS - COPY TO WORK AREA reversed, THEN COPY BACK + + memset(TEMPDIGI, 0, 57); // CLEAR DIGI FIELD IN CASE RECONNECT + + ptr1 = &Buffer->ORIGIN[6]; // End of add + ptr2 = &TEMPDIGI[7 * 7]; // Last Temp Digi + + while((*ptr1 & 1) == 0) // End of address bit + { + ptr1++; + memcpy(ptr2, ptr1, 7); + ptr2[6] &= 0x1e; // Mask Repeated and Last bits + ptr2 -= 7; + ptr1 += 6; + } + + // LIST OF DIGI CALLS COMPLETE - COPY TO LINK CONTROL ENTRY + + n = PORT->PORTMAXDIGIS; + + ptr1 = ptr2 + 7; // First in TEMPDIGIS + ptr2 = &LINK->DIGIS[0]; + + while (*ptr1) + { + if (n == 0) + { + // Too many for us + + CLEAROUTLINK(LINK); + ReleaseBuffer(Buffer); + return; + } + + memcpy(ptr2, ptr1, 7); + ptr1 += 7; + ptr2 += 7; + n--; + } + } + + ADJBUFFER->CTL = CTL | PFBIT; + + // Buffer->LENGTH = (UCHAR *)ADJBUFFER - (UCHAR *)Buffer + MSGHDDRLEN + 15; // SET UP BYTE COUNT + + L2SWAPADDRESSES(Buffer); // SWAP ADDRESSES AND SET RESP BITS + + // We need to save APPLMASK and ALIASPTR so following SABM connects to application + + LINK->APPLMASK = APPLMASK; + LINK->ALIASPTR = ALIASPTR; + + PUT_ON_PORT_Q(PORT, Buffer); + return; + } +BadXID: + L2SENDINVALIDCTRL(PORT, Buffer, ADJBUFFER, CTL); + return; +} + + + +int COUNTLINKS(int Port) +{ + //COUNT LINKS ON PORT + + int i = MAXLINKS, n = 0; + struct _LINKTABLE * LINK = LINKS; + + while (i--) + { + if (LINK->LINKPORT && LINK->LINKPORT->PORTNUMBER == Port) + n++; + + LINK++; + } + + return n; +} + + +VOID L2LINKACTIVE(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL, UCHAR MSGFLAG) +{ + // MESSAGE ON AN ACTIVE LINK + + int CTLlessPF = CTL & ~PFBIT; + + PORT->L2FRAMESFORUS++; + + // ONLY SABM or UI ALLOWED IF NO SESSION + + if (CTLlessPF == 3) // UI + { + // A UI ADDRESSED TO US - SHOULD ONLY BE FOR IP, or possibly addressed NODES + + switch(ADJBUFFER->PID) + { + case 0xcf: // Netrom + + if (Buffer->L2DATA[0] == 0xff) // NODES + PROCESSNODEMESSAGE(Buffer, PORT); + + break; + + case 0xcc: // TCP + case 0xcd: // ARP + case 0x08: // NOS FRAGMENTED AX25 TCP/IP + + Q_IP_MSG( Buffer); + return; + } + + ReleaseBuffer(Buffer); + return; + } + + if (CTLlessPF == DISC) + { + InformPartner(LINK, NORMALCLOSE); // SEND DISC TO OTHER END + CLEAROUTLINK(LINK); + L2SENDUA(PORT, Buffer, ADJBUFFER); + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + DetachKISSHF(PORT); + + return; + } + + + if (LINK->L2STATE == 1) + { + // XID State. Should be XID response if 2.2 ok or DM/FRMR if not + + if (MSGFLAG & RESP) + { + if (CTLlessPF == DM || CTLlessPF == FRMR) + { + // Doesn't support XID - Send SABM + + LINK->L2STATE = 2; + LINK->Ver2point2 = FALSE; + LINK->L2TIMER = 1; // USe retry to send SABM + } + else if (CTLlessPF == XID) + { + // Process response to make sure ok, Send SABM or DISC + + LINK->L2STATE = 2; + LINK->Ver2point2 = TRUE;// Must support 2.2 if responded to XID + LINK->L2TIMER = 1; // USe retry to send SABM + } + + ReleaseBuffer(Buffer); + return; + } + + // Command on existing session. Could be due to other end missing + // the XID response, so if XID just resend response + + } + + if (CTLlessPF == XID && (MSGFLAG & CMDBIT)) + { + // XID Command on active session. Other end may be restarting. Send Response + + ProcessXIDCommand(LINK, PORT, Buffer, ADJBUFFER, CTL, MSGFLAG); + return; + } + + + if (CTLlessPF == SABM) + { + // SABM ON EXISTING SESSION - IF DISCONNECTING, REJECT + + if (LINK->L2STATE == 1) // Sent XID? + { + APPLMASK = LINK->APPLMASK; + ALIASPTR = LINK->ALIASPTR; + + L2SABM(LINK, PORT, Buffer, ADJBUFFER, MSGFLAG); // Process the SABM + return; + } + + if (LINK->L2STATE == 4) // DISCONNECTING? + { + L2SENDDM(PORT, Buffer, ADJBUFFER); + return; + } + + // THIS IS A SABM ON AN EXISTING SESSION + + // THERE ARE SEVERAL POSSIBILITIES: + + // 1. RECONNECT COMMAND TO TNC + // 2. OTHER END THINKS LINK HAS DIED + // 3. RECOVERY FROM FRMR CONDITION + // 4. REPEAT OF ORIGINAL SABM COS OTHER END MISSED UA + + // FOR 1-3 IT IS REASONABLE TO FULLY RESET THE CIRCUIT, BUT IN 4 + // SUCH ACTION WILL LOSE THE INITIAL SIGNON MSG IF CONNECTING TO A + // BBS. THE PROBLEM IS TELLING THE DIFFERENCE. I'M GOING TO SET A FLAG + // WHEN FIRST INFO RECEIVED - IF SABM REPEATED BEFORE THIS, I'LL ASSUME + // CONDITION 4, AND JUST RESEND THE UA + + + if (LINK->SESSACTIVE == 0) // RESET OF ACTIVE CIRCUIT? + { + L2SENDUA(PORT, Buffer, ADJBUFFER); // No, so repeat UA + return; + } + + InformPartner(LINK, NORMALCLOSE); // SEND DISC TO OTHER END + LINK->CIRCUITPOINTER = 0; + + L2SABM(LINK, PORT, Buffer, ADJBUFFER, MSGFLAG); // Process the SABM + return; + } + + L2_PROCESS(LINK, PORT, Buffer, CTL, MSGFLAG); +} + + +VOID L2SABM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR MSGFLAG) +{ + // SET UP NEW SESSION (OR RESET EXISTING ONE) + + TRANSPORTENTRY * Session; + int CONERROR; + + char toCall[12], fromCall[12]; + + + if (LINK == 0) // NO LINK ENTRIES - SEND DM RESPONSE + { + L2SENDDM(PORT, Buffer, ADJBUFFER); + return; + } + + if (CheckKissInterlock(PORT, TRUE)) // Interlock with ARDOP/VARA etc + { + L2SENDDM(PORT, Buffer, ADJBUFFER); + return; + } + + SETUPNEWL2SESSION(LINK, PORT, Buffer, MSGFLAG); + + if (LINK->L2STATE != 5) // Setup OK? + { + L2SENDDM(PORT, Buffer, ADJBUFFER); // Failed + return; + } + + // See if need to Interlock non-sharable modes, eg ARDOP and VARA + + seeifInterlockneeded(PORT); + + toCall[ConvFromAX25(ADJBUFFER->DEST, toCall)] = 0; + fromCall[ConvFromAX25(ADJBUFFER->ORIGIN, fromCall)] = 0; + + + // IF CONNECT TO APPL ADDRESS, SET UP APPL SESSION + + if (APPLMASK == 0) + { + // Not ATTACH TO APPL + + // Send CTEXT if connect to NODE/Port Alias, or NODE/Port Call, and FULL_CTEXT set + // Dont sent to known NODEs, or appl connects + + struct DATAMESSAGE * Msg; + int Totallen = 0; + int Paclen= PORT->PORTPACLEN; + UCHAR * ptr; + + if (LogAllConnects) + { + char toCall[12], fromCall[12]; + toCall[ConvFromAX25(ADJBUFFER->DEST, toCall)] = 0; + fromCall[ConvFromAX25(ADJBUFFER->ORIGIN, fromCall)] = 0; + WriteConnectLog(fromCall, toCall, "AX.25"); + } + + hookL2SessionAccepted(PORT->PORTNUMBER, fromCall, toCall, LINK); + + L2SENDUA(PORT, Buffer, ADJBUFFER); + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + AttachKISSHF(PORT, Buffer); + + if (NO_CTEXT == 1) + return; + + if (FULL_CTEXT == 0 && !ALIASMSG) // Any connect, or call to alias + return; + + // if Port CTEXT defined, use it + + if (PORT->CTEXT) + { + Totallen = strlen(PORT->CTEXT); + ptr = PORT->CTEXT; + } + else if (CTEXTLEN) + { + Totallen = CTEXTLEN; + ptr = CTEXTMSG; + } + else + return; + + if (Paclen == 0) + Paclen = PACLEN; + + while(Totallen) + { + Msg = GetBuff(); + + if (Msg == NULL) + break; // No Buffers + + Msg->PID = 0xf0; + + if (Paclen > Totallen) + Paclen = Totallen; + + memcpy(Msg->L2DATA, ptr, Paclen); + Msg->LENGTH = Paclen + MSGHDDRLEN + 1; + + C_Q_ADD(&LINK->TX_Q, Msg); + + ptr += Paclen; + Totallen -= Paclen; + } + return; + } + + + // Connnect to APPL + + if (LINK->LINKTYPE != 1) + { + L2SENDUA(PORT, Buffer, ADJBUFFER); // RESET OF DOWN/CROSSLINK + return; + } + + if (LINK->CIRCUITPOINTER) + { + L2SENDUA(PORT, Buffer, ADJBUFFER); // ALREADY SET UP - MUST BE REPEAT OF SABM OR LINK RESET + return; + } + + // IF RUNNING ONLY BBS (NODE=0), THIS MAY BE EITHER A USER OR NODE + // TRYING TO SET UP A L4 CIRCUIT - WE DONT WANT TO ATTACH A NODE TO + // THE BBS! + + if (NODE == 0) + { + // NOW THINGS GET DIFICULT - WE MUST EITHER WAIT TO SEE IF A PID CF MSG + // ARRIVES, OR ASSUME ALL NODES ARE IN NEIGHBOURS - I'LL TRY THE LATTER + // AND SEE HOW IT GOES. tHIS MEANS THAT YOU MUST DEFINE ALL ROUTES + // IN CONFIG FILE + + struct ROUTE * ROUTE; + + if (FindNeighbour(Buffer->ORIGIN, PORT->PORTNUMBER, &ROUTE)) + { + // It's a node + + L2SENDUA(PORT, Buffer, ADJBUFFER); // ALREADY SET UP - MUST BE REPEAT OF SABM OR LINK RESET + return; + } + } + + + Session = SetupSessionForL2(LINK); // CREATE INCOMING L4 SESSION + + if (Session == NULL) + { + CLEAROUTLINK(LINK); + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + DetachKISSHF(PORT); + L2SENDDM(PORT, Buffer, ADJBUFFER); + + return; + } + + // NOW TRY A BBS CONNECT + // IF APPL CONNECT, SEE IF APPL HAS AN ALIAS + + if (ALIASPTR[0] > ' ') + { + struct DATAMESSAGE * Msg; + + // ACCEPT THE CONNECT, THEN INVOKE THE ALIAS + + L2SENDUA(PORT, Buffer, ADJBUFFER); + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + { + struct DATAMESSAGE * Msg; + int Totallen = 0; + int Paclen= PORT->PORTPACLEN; + UCHAR * ptr; + + AttachKISSHF(PORT, Buffer); + + // if Port CTEXT defined, use it + + if (PORT->CTEXT) + { + Totallen = strlen(PORT->CTEXT); + ptr = PORT->CTEXT; + } + else if (HFCTEXTLEN) + { + Totallen = HFCTEXTLEN; + ptr = HFCTEXT; + } + + if (Paclen == 0) + Paclen = PACLEN; + + while(Totallen) + { + Msg = GetBuff(); + + if (Msg == NULL) + break; // No Buffers + + Msg->PID = 0xf0; + + if (Paclen > Totallen) + Paclen = Totallen; + + memcpy(Msg->L2DATA, ptr, Paclen); + Msg->LENGTH = Paclen + MSGHDDRLEN + 1; + + C_Q_ADD(&LINK->TX_Q, Msg); + + ptr += Paclen; + Totallen -= Paclen; + } + + } + + if (LogAllConnects) + { + char toCall[12], fromCall[12]; + toCall[ConvFromAX25(ADJBUFFER->DEST, toCall)] = 0; + fromCall[ConvFromAX25(ADJBUFFER->ORIGIN, fromCall)] = 0; + WriteConnectLog(fromCall, toCall, "AX.25"); + } + + Msg = GetBuff(); + + if (Msg) + { + Msg->PID = 0xf0; + + memcpy(Msg->L2DATA, ALIASPTR, 12); + Msg->L2DATA[12] = 13; + + Msg->LENGTH = MSGHDDRLEN + 12 + 2; // 2 for PID and CR + + C_Q_ADD(&LINK->RX_Q, Msg); + } + + return; + } + + if (cATTACHTOBBS(Session, APPLMASK, PORT->PORTPACLEN, &CONERROR) == 0) + { + // NO BBS AVAILABLE + + CLEARSESSIONENTRY(Session); + CLEAROUTLINK(LINK); + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + DetachKISSHF(PORT); + + L2SENDDM(PORT, Buffer, ADJBUFFER); + + return; + } + + if (LogAllConnects) + { + char toCall[12], fromCall[12]; + toCall[ConvFromAX25(ADJBUFFER->DEST, toCall)] = 0; + fromCall[ConvFromAX25(ADJBUFFER->ORIGIN, fromCall)] = 0; + WriteConnectLog(fromCall, toCall, "AX.25"); + } + + L2SENDUA(PORT, Buffer, ADJBUFFER); + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + { + struct DATAMESSAGE * Msg; + int Totallen = 0; + int Paclen= PORT->PORTPACLEN; + UCHAR * ptr; + + AttachKISSHF(PORT, Buffer); + + // if Port CTEXT defined, use it + + if (PORT->CTEXT) + { + Totallen = strlen(PORT->CTEXT); + ptr = PORT->CTEXT; + } + else if (HFCTEXTLEN) + { + Totallen = HFCTEXTLEN; + ptr = HFCTEXT; + } + else + return; + + if (Paclen == 0) + Paclen = PACLEN; + + while(Totallen) + { + Msg = GetBuff(); + + if (Msg == NULL) + break; // No Buffers + + Msg->PID = 0xf0; + + if (Paclen > Totallen) + Paclen = Totallen; + + memcpy(Msg->L2DATA, ptr, Paclen); + Msg->LENGTH = Paclen + MSGHDDRLEN + 1; + + C_Q_ADD(&LINK->TX_Q, Msg); + + ptr += Paclen; + Totallen -= Paclen; + } + return; + } +} + +VOID SETUPNEWL2SESSION(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR MSGFLAG) +{ + // COPY ADDRESS INFO TO LINK TABLE + + UCHAR * ptr1, * ptr2; + UCHAR TEMPDIGI[57]; + int n; + + memcpy(LINK->LINKCALL, Buffer->ORIGIN, 7); + LINK->LINKCALL[6] &= 0x1e; // Mask SSID + + memcpy(LINK->OURCALL, Buffer->DEST, 7); + LINK->OURCALL[6] &= 0x1e; // Mask SSID + + memset(LINK->DIGIS, 0, 56); // CLEAR DIGI FIELD IN CASE RECONNECT + + LINK->L2TIME = PORT->PORTT1; // Set tomeoiut for no digis + + if ((Buffer->ORIGIN[6] & 1) == 0) // End of Address + { + // THERE ARE DIGIS TO PROCESS - COPY TO WORK AREA reversed, THEN COPY BACK + + memset(TEMPDIGI, 0, 57); // CLEAR DIGI FIELD IN CASE RECONNECT + + ptr1 = &Buffer->ORIGIN[6]; // End of add + ptr2 = &TEMPDIGI[7 * 7]; // Last Temp Digi + + while((*ptr1 & 1) == 0) // End of address bit + { + ptr1++; + memcpy(ptr2, ptr1, 7); + ptr2[6] &= 0x1e; // Mask Repeated and Last bits + ptr2 -= 7; + ptr1 += 6; + } + + // LIST OF DIGI CALLS COMPLETE - COPY TO LINK CONTROL ENTRY + + n = PORT->PORTMAXDIGIS; + + ptr1 = ptr2 + 7; // First in TEMPDIGIS + ptr2 = &LINK->DIGIS[0]; + + while (*ptr1) + { + if (n == 0) + { + // Too many for us + + CLEAROUTLINK(LINK); + return; + } + + memcpy(ptr2, ptr1, 7); + ptr1 += 7; + ptr2 += 7; + n--; + + LINK->L2TIME += PORT->PORTT1; // Adjust timeout for digis + } + } + + // THIS MAY BE RESETTING A LINK - BEWARE OF CONVERTING A CROSSLINK TO + // AN UPLINK AND CONFUSING EVERYTHING + + LINK->LINKPORT = PORT; + + if (LINK->LINKTYPE == 0) + { + if (ISNETROMMSG && NODE == 0) // Only allow crosslink if node = 0 + LINK->LINKTYPE = 3; // Crosslink + else + LINK->LINKTYPE = 1; // Uplink + } + LINK->L2TIMER = 0; // CANCEL TIMER + + LINK->L2SLOTIM = T3; // SET FRAME SENT RECENTLY + + LINK->LINKWINDOW = PORT->PORTWINDOW; + + RESET2(LINK); // RESET ALL FLAGS + + LINK->L2STATE = 5; + + // IF VERSION 1 MSG, SET FLAG + + if (MSGFLAG & VER1) + LINK->VER1FLAG |= 1; + +} + +VOID L2SENDUA(struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER) +{ + L2SENDRESP(PORT, Buffer, ADJBUFFER, UA); +} + +VOID L2SENDDM(struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER) +{ + if (CheckExcludeList(Buffer->ORIGIN) == 0) // if in exclude, don't send DM + { + ReleaseBuffer(Buffer); // not sure that this is the right place for releasing? + return; + } + + L2SENDRESP(PORT, Buffer, ADJBUFFER, DM); +} + +VOID L2SENDRESP(struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL) +{ + // QUEUE RESPONSE TO PORT CONTROL - MAY NOT HAVE A LINK ENTRY + + // SET APPROPRIATE P/F BIT + + ADJBUFFER->CTL = CTL | PFBIT; + + Buffer->LENGTH = (int)((UCHAR *)ADJBUFFER - (UCHAR *)Buffer) + MSGHDDRLEN + 15; // SET UP BYTE COUNT + + L2SWAPADDRESSES(Buffer); // SWAP ADDRESSES AND SET RESP BITS + + PUT_ON_PORT_Q(PORT, Buffer); + + return; +} + + +VOID L2SENDINVALIDCTRL(struct PORTCONTROL * PORT, MESSAGE * Buffer, MESSAGE * ADJBUFFER, UCHAR CTL) +{ + // Send FRMR Invalid Control field + + // QUEUE RESPONSE TO PORT CONTROL - MAY NOT HAVE A LINK ENTRY + + // SET APPROPRIATE P/F BIT + + UCHAR * ptr; + + ADJBUFFER->CTL = FRMR | PFBIT; + + ptr = &ADJBUFFER->PID; + + *(ptr++) = CTL; // MOVE REJECT C-BYTE + *(ptr++) = 0; + *(ptr++) = SDINVC; // MOVE REJECT FLAGS + + Buffer->LENGTH = (int)((UCHAR *)ADJBUFFER - (UCHAR *)Buffer) + MSGHDDRLEN + 18; // SET UP BYTE COUNT + + L2SWAPADDRESSES(Buffer); // SWAP ADDRESSES AND SET RESP BITS + + PUT_ON_PORT_Q(PORT, Buffer); + + return; +} + +VOID L2SWAPADDRESSES(MESSAGE * Buffer) +{ + // EXCHANGE ORIGIN AND DEST, AND REVERSE DIGIS (IF PRESENT) + + char TEMPFIELD[7]; + UCHAR * ptr1, * ptr2; + UCHAR TEMPDIGI[57]; + + memcpy(TEMPFIELD, Buffer->ORIGIN, 7); + memcpy(Buffer->ORIGIN, Buffer->DEST, 7); + memcpy(Buffer->DEST, TEMPFIELD, 7); + + Buffer->ORIGIN[6] &= 0x1e; // Mask SSID + Buffer->ORIGIN[6] |= 0xe0; // Reserved and Response + + Buffer->DEST[6] &= 0x1e; // Mask SSID + Buffer->DEST[6] |= 0x60; // Reserved + + if ((TEMPFIELD[6] & 1) == 0) + { + // THERE ARE DIGIS TO PROCESS - COPY TO WORK AREA reversed, THEN COPY BACK + + memset(TEMPDIGI, 0, 57); // CLEAR DIGI FIELD IN CASE RECONNECT + + ptr1 = &Buffer->ORIGIN[6]; // End of add + ptr2 = &TEMPDIGI[7 * 7]; // Last Temp Digi + + while((*ptr1 & 1) == 0) // End of address bit + { + ptr1++; + memcpy(ptr2, ptr1, 7); + ptr2[6] &= 0x1e; // Mask Repeated and Last bits + ptr2 -= 7; + ptr1 += 6; + } + + // LIST OF DIGI CALLS COMPLETE - copy back + + ptr1 = ptr2 + 7; // First in TEMPDIGIS + ptr2 = &Buffer->CTL; + + while (*ptr1) + { + memcpy(ptr2, ptr1, 7); + ptr1 += 7; + ptr2 += 7; + } + + *(ptr2 - 1) |= 1; // End of addresses + } + else + { + Buffer->ORIGIN[6] |= 1; // End of address + } +} + +BOOL InternalL2SETUPCROSSLINK(PROUTE ROUTE, int Retries) +{ + // ROUTE POINTS TO A NEIGHBOUR - FIND AN L2 SESSION FROM US TO IT, OR INITIATE A NEW ONE + + struct _LINKTABLE * LINK; + struct PORTCONTROL * PORT; + int FRACK; + + if (FindLink(ROUTE->NEIGHBOUR_CALL, NETROMCALL, ROUTE->NEIGHBOUR_PORT, &LINK)) + { + // SESSION ALREADY EXISTS + + LINK->LINKTYPE = 3; // MAKE SURE IT KNOWS ITS A CROSSLINK + ROUTE->NEIGHBOUR_LINK = LINK; + LINK->NEIGHBOUR = ROUTE; + + return TRUE; + } + + // SET UP NEW SESSION (OR RESET EXISTING ONE) + + if (LINK == NULL) + return FALSE; // No free links + + + ROUTE->NEIGHBOUR_LINK = LINK; + LINK->NEIGHBOUR = ROUTE; + + LINK->LINKPORT = PORT = GetPortTableEntryFromPortNum(ROUTE->NEIGHBOUR_PORT); + + if (PORT == NULL) + return FALSE; // maybe port has been deleted + + // IF ROUTE HAS A FRACK, SET IT + + if (ROUTE->NBOUR_FRACK) + FRACK = ROUTE->NBOUR_FRACK; + else + FRACK = PORT->PORTT1; + + LINK->L2TIME = FRACK; // SET TIMER VALUE + + // IF ROUTE HAS A WINDOW, SET IT + + if (ROUTE->NBOUR_MAXFRAME) + LINK->LINKWINDOW = ROUTE->NBOUR_MAXFRAME; + else + LINK->LINKWINDOW = PORT->PORTWINDOW; + +// if (SUPPORT2point2) +// LINK->L2STATE = 1; // Send XID +// else + LINK->L2STATE = 2; + + memcpy(LINK->LINKCALL, ROUTE->NEIGHBOUR_CALL, 7); + memcpy(LINK->OURCALL, NETROMCALL, 7); + + if (ROUTE->NEIGHBOUR_DIGI1[0]) + { + memcpy(LINK->DIGIS, ROUTE->NEIGHBOUR_DIGI1, 7); + LINK->L2TIME += FRACK; + } + + if (ROUTE->NEIGHBOUR_DIGI2[0]) + { + memcpy(&LINK->DIGIS[7], ROUTE->NEIGHBOUR_DIGI1, 7); + LINK->L2TIME += FRACK; + } + + LINK->LINKTYPE = 3; // CROSSLINK + + if (Retries) + LINK->L2RETRIES = PORT->PORTN2 - Retries; + + if (LINK->L2STATE == 1) + L2SENDXID(LINK); + else + SENDSABM(LINK); + + return TRUE; +} + + + +BOOL L2SETUPCROSSLINKEX(PROUTE ROUTE, int Retries) +{ + // Allows caller to specify number of times SABM should be sent + + return InternalL2SETUPCROSSLINK(ROUTE, Retries); +} + +BOOL L2SETUPCROSSLINK(PROUTE ROUTE) +{ + return InternalL2SETUPCROSSLINK(ROUTE, 0); +} + +VOID L2_PROCESS(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR CTL, UCHAR MSGFLAG) +{ + // PROCESS LEVEL 2 PROTOCOL STUFF + + // SEE IF COMMAND OR RESPONSE + + if ((MSGFLAG & CMDBIT) == 0) + { + + // RESPONSE OR VERSION 1 + + // IF RETRYING, MUST ONLY ACCEPT RESPONSES WITH F SET (UNLESS RUNNING V1) + + if ((CTL & PFBIT) || LINK->VER1FLAG == 1) + { + // F SET or V1 - CAN CANCEL TIMER + + LINK->L2TIMER = 0; // CANCEL LINK TIMER + } + } + + if (LINK->L2STATE == 3) + { + + // FRMR STATE - IF C(P) SEND FRMR, ELSE IGNORE + + if (CTL & PFBIT) + { + if (CTL == (FRMR | PFBIT)) // if both ends in FRMR state, reset link + { + RESET2(LINK); + + LINK->L2STATE = 2; // INITIALISING + LINK->L2ACKREQ = 0; // DONT SEND ANYTHING ELSE + LINK->L2RETRIES = 0; // ALLOW FULL RETRY COUNT FOR SABM + + L2SENDCOMMAND(LINK, SABM | PFBIT); + } + } + + if (MSGFLAG & CMDBIT) + { + // SEND FRMR AGAIN + + SENDFRMR(LINK); + } + + ReleaseBuffer(Buffer); + return; + } + + if (LINK->L2STATE >= 5) + { + // LINK IN STATE 5 OR ABOVE - LINK RUNNING + + if ((CTL & 1) == 0) // I frame + { + SDIFRM(LINK, PORT, Buffer, CTL, MSGFLAG); // consumes buffer + return; + } + + if ((CTL & 2)) // U frame + { + SDUFRM(LINK, PORT, Buffer, CTL); //consumes buffer + return; + } + + // ELSE SUPERVISORY, MASK OFF N(R) AND P-BIT + + switch (CTL & 0x0f) + { + // is there any harm in accepting SREJ even if we don't + // otherwise support 2.2? + + case REJ: + case SREJ: + + PORT->L2REJCOUNT++; + + case RR: + case RNR: + + SFRAME(LINK, PORT, CTL, MSGFLAG); + break; + + default: + + // UNRECOGNISABLE COMMAND + + LINK->SDRBYTE = CTL; // SAVE FOR FRMR RESPONSE + LINK->SDREJF |= SDINVC; // SET INVALID COMMAND REJECT + SDFRMR(LINK, PORT); // PROCESS FRAME REJECT CONDITION + } + + ReleaseBuffer(Buffer); + return; + } + + // NORMAL DISCONNECT MODE + + // COULD BE UA, DM - SABM AND DISC HANDLED ABOVE + + switch (CTL & ~PFBIT) + { + case UA: + + // UA RECEIVED + + if (LINK->L2STATE == 2) + { + // RESPONSE TO SABM - SET LINK UP + + RESET2X(LINK); // LEAVE QUEUED STUFF + + LINK->L2STATE = 5; + LINK->L2TIMER = 0; // CANCEL TIMER + LINK->L2RETRIES = 0; + LINK->L2SLOTIM, T3; // SET FRAME SENT RECENTLY + + // IF VERSION 1 MSG, SET FLAG + + if (MSGFLAG & VER1) + LINK->VER1FLAG |= 1; + + // TELL PARTNER CONNECTION IS ESTABLISHED + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + KISSHFConnected(PORT, LINK); + + SENDCONNECTREPLY(LINK); + ReleaseBuffer(Buffer); + return; + } + + if (LINK->L2STATE == 4) // DISCONNECTING? + { + InformPartner(LINK, NORMALCLOSE); // SEND DISC TO OTHER END + CLEAROUTLINK(LINK); + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + DetachKISSHF(PORT); + } + + // UA, BUT NOT IN STATE 2 OR 4 - IGNORE + + ReleaseBuffer(Buffer); + return; + + case DM: + + // DM RESPONSE - IF TO SABM, SEND BUSY MSG + + if (LINK->L2STATE == 2) + { + CONNECTREFUSED(LINK); // SEND MESSAGE IF DOWNLINK + return; + } + + // DM RESP TO DISC RECEIVED - OTHER END HAS LOST SESSION + + // CLEAR OUT TABLE ENTRY - IF INTERNAL TNC, SHOULD SEND *** DISCONNECTED + + InformPartner(LINK, LINKLOST); // SEND DISC TO OTHER END + CLEAROUTLINK(LINK); + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + DetachKISSHF(PORT); + + ReleaseBuffer(Buffer); + return; + + case FRMR: + + // FRAME REJECT RECEIVED - LOG IT AND RESET LINK + + RESET2(LINK); + + LINK->L2STATE = 2; // INITIALISING + LINK->L2ACKREQ = 0; // DONT SEND ANYTHING ELSE + LINK->L2RETRIES = 0; // ALLOW FULL RETRY COUNT FOR SABM + + PORT->L2FRMRRX++; + + L2SENDCOMMAND(LINK, SABM | PFBIT); + return; + + default: + + // ANY OTHER - IGNORE + + ReleaseBuffer(Buffer); + } +} + +VOID SDUFRM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR CTL) +{ + // PROCESS AN UNSEQUENCED COMMAND (IN LINK UP STATES) + + switch (CTL & ~PFBIT) + { + case UA: + + // DISCARD - PROBABLY REPEAT OF ACK OF SABM + + break; + + case FRMR: + + // FRAME REJECT RECEIVED - LOG IT AND RESET LINK + + RESET2(LINK); + + LINK->L2STATE = 2; // INITIALISING + LINK->L2ACKREQ = 0; // DONT SEND ANYTHING ELSE + LINK->L2RETRIES = 0; // ALLOW FULL RETRY COUNT FOR SABM + + PORT->L2FRMRRX++; + + L2SENDCOMMAND(LINK, SABM | PFBIT); + break; + + case DM: + + // DM RESPONSE - SESSION MUST HAVE GONE + + // SEE IF CROSSLINK ACTIVE + + InformPartner(LINK, LINKLOST); // SEND DISC TO OTHER END + CLEAROUTLINK(LINK); + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + DetachKISSHF(PORT); + + break; + + default: + + // UNDEFINED COMMAND + + LINK->SDRBYTE = CTL; // SAVE FOR FRMR RESPONSE + LINK->SDREJF |= SDINVC; + SDFRMR(LINK, PORT); // PROCESS FRAME REJECT CONDITION + + } + + ReleaseBuffer(Buffer); +} + + +VOID SFRAME(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, UCHAR CTL, UCHAR MSGFLAG) +{ + // CHECK COUNTS, AND IF RNR INDICATE _BUFFER SHORTAGE AT OTHER END + + if (LINK->SDREJF) // ARE ANY REJECT FLAGS SET? + { + SDFRMR(LINK, PORT); // PROCESS FRAME REJECT CONDITION + return; + } + + SDNRCHK(LINK, CTL); // CHECK RECEIVED N(R) + + if (LINK->SDREJF) // ARE ANY REJECT FLAGS SET NOW? + { + SDFRMR(LINK, PORT); // PROCESS FRAME REJECT CONDITION + return; + } + + if ((CTL & 0xf) == SREJ) + { + // Probably safer to handle SREJ completely separately + + // Can we get SREJ Command with P??(Yes) + + // Can we just resend missing frame ?? (Think so!) + + // We support MultiSREJ (can gave additional missing frame + // numbers in the Info field + + // I don't see the point of Multi unless we wait fot an F bit, + // bur maybe not safe to assume others do the same + + // So if I get SREJ(F) I can send missing frame(s) + + if (MSGFLAG & RESP) + { + // SREJ Response + + if (CTL & PFBIT) + { + // SREJ(F). Send Frames() + + UCHAR NS = (CTL >> 5) & 7; // Frame to resend + + struct PORTCONTROL * PORT; + UCHAR * ptr1, * ptr2; + UCHAR CTL; + int count; + MESSAGE * Msg; + MESSAGE * Buffer; + + Msg = LINK->FRAMES[NS]; // is frame available? + + if (Msg == NULL) + return; // Wot!! + + // send the frame + + // GET BUFFER FOR COPY OF MESSAGE - HAVE TO KEEP ORIGINAL FOR RETRIES + + Buffer = GetBuff(); + + if (Buffer == NULL) + return; + + ptr2 = SETUPADDRESSES(LINK, Buffer); // copy addresses + + // ptr2 NOW POINTS TO COMMAND BYTE + + // GOING TO SEND I FRAME - WILL ACK ANY RECEIVED FRAMES + + LINK->L2ACKREQ = 0; // CLEAR ACK NEEDED + LINK->L2SLOTIM = T3 + rand() % 15; // SET FRAME SENT RECENTLY + LINK->KILLTIMER = 0; // RESET IDLE CIRCUIT TIMER + + CTL = LINK->LINKNR << 5; // GET CURRENT N(R), SHIFT IT TO TOP 3 BITS + CTL |= NS << 1; // BITS 1-3 OF CONTROL BYTE + + // SET P BIT IF NO MORE TO SEND (only more if Multi SREJ) + + if (LINK->VER1FLAG == 0) // NO POLL BIT IF V1 + { + CTL |= PFBIT; + LINK->L2FLAGS |= POLLSENT; + LINK->L2TIMER = ONEMINUTE; // (RE)SET TIMER + + // FLAG BUFFER TO CAUSE TIMER TO BE RESET AFTER SEND (or ACK if ACKMODE) + + Buffer->Linkptr = LINK; + } + + *(ptr2++) = CTL; // TO DATA (STARTING WITH PID) + + count = Msg->LENGTH - MSGHDDRLEN; + + if (count > 0) // SHOULD ALWAYS BE A PID, BUT BETTER SAFE THAN SORRY + { + ptr1 = (UCHAR *)Msg; + ptr1 += MSGHDDRLEN; + memcpy(ptr2, ptr1, count); + } + + Buffer->DEST[6] |= 0x80; // SET COMMAND + + Buffer->LENGTH = (int)(ptr2 - (UCHAR *)Buffer) + count; // SET NEW LENGTH + + LINK->L2TIMER = ONEMINUTE; // (RE)SET TIMER + + PORT = LINK->LINKPORT; + + if (PORT) + { + Buffer->PORT = PORT->PORTNUMBER; + PUT_ON_PORT_Q(PORT, Buffer); + } + else + { + Buffer->Linkptr = 0; + ReleaseBuffer(Buffer); + } + } + } + + return; + } + + // VALID RR/RNR RECEIVED + + LINK->L2FLAGS &= ~RNRSET; //CLEAR RNR + + if ((CTL & 0xf) == RNR) + LINK->L2FLAGS |= RNRSET; //Set RNR + + if (MSGFLAG & CMDBIT) + { + // ALWAYS REPLY TO RR/RNR/REJ COMMAND (even if no P bit ??) + + // FIRST PROCESS RESEQ QUEUE + + //; CALL PROCESS_RESEQ + + // IGNORE IF AN 'F' HAS BEEN SENT RECENTLY + + if (LINK->LAST_F_TIME + 15 > REALTIMETICKS) + return; // DISCARD + + CTL = RR_OR_RNR(LINK); + + CTL |= LINK->LINKNR << 5; // SHIFT N(R) TO TOP 3 BITS + CTL |= PFBIT; + + L2SENDRESPONSE(LINK, CTL); + + LINK->L2SLOTIM = T3 + rand() % 15; // SET FRAME SENT RECENTLY + + LINK->L2ACKREQ = 0; // CANCEL DELAYED ACKL2 + + // SAVE TIME IF 'F' SENT' + + LINK->LAST_F_TIME = REALTIMETICKS; + + return; + } + + // Response + + if ((CTL & PFBIT) == 0 && LINK->VER1FLAG == 0) + { + // RESPONSE WITHOUT P/F DONT RESET N(S) (UNLESS V1) + + return; + + } + + // RESPONSE WITH P/F - MUST BE REPLY TO POLL FOLLOWING TIMEOUT OR I(P) + + // THERE IS A PROBLEM WITH REPEATED RR(F), SAY CAUSED BY DELAY AT L1 + + // AS FAR AS I CAN SEE, WE SHOULD ONLY RESET N(S) IF AN RR(F) FOLLOWS + // AN RR(P) AFTER A TIMEOUT - AN RR(F) FOLLOWING AN I(P) CANT POSSIBLY + // INDICATE A LOST FRAME. ON THE OTHER HAND, A REJ(F) MUST INDICATE + // A LOST FRAME. So dont reset NS if not retrying, unless REJ + + + // someone (probably WLE KISS Driver) is sending REJ followed by RR(F) + // after lost frame and i(p) + +/* +1:Fm W4DHW-10 To W4DHW [17:08:03R] [+++] +úJƒÑZKÀ)x@DÖBÉrNôÝ4XÔ;i‹#CäM³,ïнҼüÕrÞùOË N¿XæâïÀÄ5Ð(È|©¸ì#íÿÈUþïÒcYÞÍl—çûž)Àú璘oÑȼö>©Ï9¨*ÎG²£ëðû(6À5C‹!áL±Ÿîßì÷³ÙQð»pƒËIH”Š;ØÚi¯Ò>â9p¶B¬õ<ÌcŠEPž«<ŸÊ{0aŽ(’­YÕ–´M¢†—N£+<ÇIÐ[–áÛPw–[^]6ƒ2\ù¿9äÆov{‹¥Å¸mm [17:08:03T] +1:Fm W4DHW To W4DHW-10 [17:08:03T] +1:Fm W4DHW To W4DHW-10 [17:08:03T] + + is there a problem with restting on RR(F) following I(P)? + + I think the problem is restting NS twice if you get delayed responses to + I or RR (P). So lets try only resetting NS once for each P sent + +*/ +// if ((CTL & 0xf) == REJ || LINK->L2RETRIES) + if ((LINK->L2FLAGS & POLLSENT)) + { + RESETNS(LINK, (CTL >> 5) & 7); // RESET N(S) AND COUNT RETRIED FRAMES + + LINK->L2RETRIES = 0; + LINK->L2TIMER = 0; // WILL RESTART TIMER WHEN RETRY SENT + } + + LINK->L2FLAGS &= ~POLLSENT; // CLEAR I(P) or RR(P) SET + + if ((CTL & 0xf) == RNR) + { + // Dont Clear timer on receipt of RNR(F), spec says should poll for clearing of busy, + // and loss of subsequent RR will cause hang. Perhaps should set slightly longer time?? + // Timer may have been cleared earlier, so restart it + + LINK->L2TIMER = LINK->L2TIME; + } +} + +//*** PROCESS AN INFORMATION FRAME + +VOID SDIFRM(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR CTL, UCHAR MSGFLAG) +{ + int NS; + + if (LINK->SDREJF) // ARE ANY REJECT FLAGS SET? + { + SDFRMR(LINK, PORT); // PROCESS FRAME REJECT CONDITION + ReleaseBuffer(Buffer); + return; + } + + SDNRCHK(LINK, CTL); // CHECK RECEIVED N(R) + + if (LINK->SDREJF) // ARE ANY REJECT FLAGS SET NOW? + { + SDFRMR(LINK, PORT); // PROCESS FRAME REJECT CONDITION + ReleaseBuffer(Buffer); + return; + } + + LINK->SESSACTIVE = 1; // SESSION IS DEFINITELY SET UP + + NS = (CTL >> 1) & 7; // ISOLATE RECEIVED N(S) + + // IPOLL (sending an I(P) frame following timeout instead of RR(P)) + // is a problem. We need to send REJ(F), but shouldn't add to collector. + // We also need to handle repeated I(P), so shouldn't set REJSENT in + // this state. + + if ((((NS + 1) & 7) == LINK->LINKNR) && (CTL & PFBIT)) + { + // Previous Frame and P set - Assume IPOLL + + PORT->L2OUTOFSEQ++; + LINK->L2STATE = 6; + + LINK->L2ACKREQ = 0; // CANCEL RR NEEDED + + // We need to protect against sending multiple REJ(F) if channel + // delays mean we get two I(P) close together (how close is close ??) + // SM has default IPOLL limit of 30 bytes or about a second at 300 + // ACKMODE should avoid this anyway, and resptime of under 3 secs + // is unlikely so say 2.5 secs ?? + + if (LINK->LAST_F_TIME + 25 > REALTIMETICKS) + { + ReleaseBuffer(Buffer); + return; + } + + SEND_RR_RESP(LINK, PFBIT); + LINK->LAST_F_TIME = REALTIMETICKS; + + ReleaseBuffer(Buffer); + return; + } + +CheckNSLoop: + + if (NS != LINK->LINKNR) // EQUAL TO OUR N(R)? + { + // There is a frame missing. + // if we have just sent a REJ we have at least one out + // of sequence frame in RXFRAMES + + // so if we have frame LINK->LINKNR we can process it + // and remove it from RXFRAMES. If we are then back + // in sequence we just carry on. + + if (LINK->RXFRAMES[LINK->LINKNR]) + { + // We have the first missing frame. Process it. + + MESSAGE * OldBuffer = Q_REM(&LINK->RXFRAMES[LINK->LINKNR]); + + Debugprintf("L2 process saved Frame %d", LINK->LINKNR); + PROC_I_FRAME(LINK, PORT, OldBuffer); // Passes on or releases Buffer + + // NR has been updated. + + goto CheckNSLoop; // See if OK or we have another saved frame + } + + // BAD FRAME, SEND REJ (AFTER RESPTIME - OR WE MAY SEND LOTS!) + + // ALSO SAVE THE FRAME - NEXT TIME WE MAY GET A DIFFERENT SUBSET + // AND SOON WE WILL HANDLE SREJ + + PORT->L2OUTOFSEQ++; + + LINK->L2STATE = 6; + + // IF RUNNING VER1, AND OTHER END MISSES THIS REJ, LINK WILL FAIL + // SO TIME OUT REJ SENT STATE (MUST KEEP IT FOR A WHILE TO AVOID + // 'MULTIPLE REJ' PROBLEM) + + if (LINK->VER1FLAG == 1) + LINK->REJTIMER = TENSECS; + + // SET ACK REQUIRED TIMER - REJ WILL BE SENT WHEN IT EXPIRES + + // if configured RESPTIME is longer than 3 secs use it (may be longer on HF) + + if (PORT->PORTT2 > THREESECS) + LINK->L2ACKREQ = PORT->PORTT2; + else + LINK->L2ACKREQ = THREESECS; // EXTRA LONG RESPTIME, AS SENDING TOO MANY REJ'S IS SERIOUS + + if (LINK->RXFRAMES[NS]) + { + // Already have a copy, so discard old and keep this + + Debugprintf ("Frame %d out of seq but already have copy - release it", NS); + ReleaseBuffer(Q_REM(&LINK->RXFRAMES[NS])); + } + else + { + Debugprintf ("Frame %d out of seq - save", NS); + } + + Buffer->CHAIN = 0; + LINK->RXFRAMES[NS] = Buffer; + goto CheckPF; + } + + // IN SEQUENCE FRAME + + // Remove any stored frame with this seq + + if (LINK->RXFRAMES[NS]) + ReleaseBuffer(Q_REM(&LINK->RXFRAMES[NS])); + + if (LINK->L2STATE == 6) // REJ? + { + // If using REJ we can cancel REJ state. + // If using SREJ we only cancel REJ if we have no stored frames + + if (LINK->Ver2point2) + { + // see if any frames saved. + + int i; + + for (i = 0; i < 8; i++) + { + if (LINK->RXFRAMES[i]) + goto stayinREJ; + } + // Drop through if no stored frames + } + + // CANCEL REJ + + LINK->L2STATE = 5; + LINK->L2FLAGS &= ~REJSENT; + } + +stayinREJ: + + PROC_I_FRAME(LINK, PORT, Buffer); // Passes on or releases Buffer + + +CheckPF: + + if (LINK->Ver2point2 == 0) // Unless using SREJ + { + if (LINK->L2FLAGS & REJSENT) + { + return; // DONT SEND ANOTHER TILL REJ IS CANCELLED + } + } + + if (CTL & PFBIT) + { + if (LINK->L2STATE == 6) + LINK->L2FLAGS |= REJSENT; // Set "REJ Sent" + else + { + // we have all frames. Clear anything in RXFRAMES + + int n = 0; + + while (n < 8) + { + if (LINK->RXFRAMES[n]) + ReleaseBuffer(Q_REM(&LINK->RXFRAMES[n])); + + n++; + } + } + LINK->L2ACKREQ = 0; // CANCEL RR NEEDED + + SEND_RR_RESP(LINK, PFBIT); + + // RECORD TIME + + LINK->LAST_F_TIME = REALTIMETICKS; + } + else + if (LINK->L2ACKREQ == 0) // Resptime is zero so send RR now + SEND_RR_RESP(LINK, 0); + +} + + +VOID PROC_I_FRAME(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT, MESSAGE * Buffer) +{ + int Length; + char * Info; + UCHAR PID; + struct DATAMESSAGE * Msg = (struct DATAMESSAGE *)Buffer; + UCHAR * EOA; + int n = 8; // Max Digis + + LINK->LINKNR++; // INCREMENT OUR N(R) + LINK->LINKNR &= 7; // MODULO 8 + + // ATTACH I FRAMES TO LINK TABLE RX QUEUE - ONLY DATA IS ADDED (NOT ADDRESSES) + + // IF DISC PENDING SET, IGNORE FRAME + + if (LINK->L2FLAGS & DISCPENDING) + { + ReleaseBuffer(Buffer); + return; + } + + // Copy data down the buffer so PID comes after Header (DATAMESSAGE format) + + Length = Buffer->LENGTH - (MSGHDDRLEN + 15); // Buffer Header + addrs + CTL + Info = &Buffer->PID; + + // Adjust for DIGIS + + EOA = &Buffer->ORIGIN[6]; // End of address Bit + + while (((*EOA & 1) == 0) && n--) + { + Length -= 7; + Info += 7; + EOA += 7; + } + + PID = EOA[2]; + + switch(PID) + { + case 0xcc: + case 0xcd: + + // IP Message + + if (n < 8) // If digis, move data back down buffer + { + memmove(&Buffer->PID, &EOA[2], Length); + Buffer->LENGTH -= (int)(&EOA[2] - &Buffer->PID); + } + + Q_IP_MSG( Buffer); + break; + + case 8: + + // NOS FRAGMENTED IP + + if (n < 8) // If digis, move data back down buffer + { + memmove(&Buffer->PID, &EOA[2], Length); + Buffer->LENGTH -= (int)(&EOA[2] - &Buffer->PID); + } + + C_Q_ADD(&LINK->L2FRAG_Q, Buffer); + + if (Buffer->L2DATA[0] == 0) + { + // THERE IS A WHOLE MESSAGE ON FRAG_Q - PASS TO IP + + while(LINK->L2FRAG_Q) + { + Buffer = Q_REM(&LINK->L2FRAG_Q); + Q_IP_MSG( Buffer); + } + } + break; + + default: + + if (Length < 1 || Length > 257) + { + ReleaseBuffer(Buffer); + return; + } + + // Copy Data back over + + memmove(&Msg->PID, Info, Length); + + Buffer->LENGTH = Length + MSGHDDRLEN; + + C_Q_ADD(&LINK->RX_Q, Buffer); + } + + LINK->L2ACKREQ = PORT->PORTT2; // SET RR NEEDED + LINK->KILLTIMER = 0; // RESET IDLE LINK TIMER +} + +//*** CHECK RECEIVED N(R) COUNT + +VOID SDNRCHK(struct _LINKTABLE * LINK, UCHAR CTL) +{ + UCHAR NR = (CTL >> 5) & 7; + + if (NR >= LINK->LINKWS) // N(R) >= WINDOW START? + { + // N(R) ABOVE OR EQUAL TO WINDOW START - OK IF NOT ABOVE N(S), OR N(S) BELOW WS + + if (NR > LINK->LINKNS) // N(R) <= WINDOW END? + { + // N(R) ABOVE N(S) - DOES COUNT WRAP? + + if (LINK->LINKNS >= LINK->LINKWS) // Doesnt wrap + goto BadNR; + } + +GoodNR: + + if ((CTL & 0x0f) == SREJ) + if ((CTL & PFBIT) == 0) + return; // SREJ without F doesn't ACK anything + + LINK->LINKWS = NR; // NEW WINDOW START = RECEIVED N(R) + ACKMSG(LINK); // Remove any acked messages + return; + } + + // N(R) LESS THAN WINDOW START - ONLY OK IF WINDOW WRAPS + + if (NR <= LINK->LINKNS) // N(R) <= WINDOW END? + goto GoodNR; + +BadNR: + + // RECEIVED N(R) IS INVALID + + LINK->SDREJF |= SDNRER; // FLAG A REJECT CONDITION + LINK->SDRBYTE = CTL; // SAVE FOR FRMR RESPONSE +} + +VOID RESETNS(struct _LINKTABLE * LINK, UCHAR NS) +{ + int Resent = (LINK->LINKNS - NS) & 7; // FRAMES TO RESEND + + LINK->LINKNS = NS; // RESET N(S) + + if (LINK->LINKTYPE == 3) // mode-Node + { + if (LINK->NEIGHBOUR) + LINK->NEIGHBOUR->NBOUR_RETRIES += Resent; + } +} + +int COUNT_AT_L2(struct _LINKTABLE * LINK) +{ + // COUNTS FRAMES QUEUED ON AN L2 SESSION (IN BX) + + int count = 0, abovelink = 0; + int n = 0; + + if (LINK == NULL) + return 0; + + abovelink = C_Q_COUNT((UINT *)&LINK->TX_Q); + + // COUNT FRAMES IN TSLOTS + + while (n < 8) + { + if (LINK->FRAMES[n]) + count++; + n++; + } + +// ADD AL,AH ; TOTAL IN AL, NUMBER ABOVE LINK IN AH + + return abovelink + count; +} + +//*** RESET HDLC AND PURGE ALL QUEUES ETC. + +VOID RESET2X(struct _LINKTABLE * LINK) +{ + LINK->SDREJF = 0; // CLEAR FRAME REJECT FLAGS + LINK->LINKWS = 0; // CLEAR WINDOW POINTERS + LINK->LINKOWS = 0; + LINK->LINKNR = 0; // CLEAR N(R) + LINK->LINKNS = 0; // CLEAR N(S) + LINK->SDTSLOT= 0; + LINK->L2STATE = 5; // RESET STATE + LINK->L2FLAGS = 0; +} + + +VOID CLEARL2QUEUES(struct _LINKTABLE * LINK) +{ + // GET RID OF ALL FRAMES THAT ARE QUEUED + + int n = 0; + + while (n < 8) + { + while (LINK->FRAMES[n]) + ReleaseBuffer(Q_REM(&LINK->FRAMES[n])); + while (LINK->RXFRAMES[n]) + ReleaseBuffer(Q_REM(&LINK->RXFRAMES[n])); + n++; + } + + // GET RID OF ALL FRAMES THAT ARE + // QUEUED ON THE TX HOLDING QUEUE, RX QUEUE AND LEVEL 3 QUEUE + + + while (LINK->TX_Q) + ReleaseBuffer(Q_REM(&LINK->TX_Q)); + + while (LINK->RX_Q) + ReleaseBuffer(Q_REM(&LINK->RX_Q)); + +} + +VOID RESET2(struct _LINKTABLE * LINK) +{ + CLEARL2QUEUES(LINK); + RESET2X(LINK); +} + +VOID SENDSABM(struct _LINKTABLE * LINK) +{ + L2SENDCOMMAND(LINK, SABM | PFBIT); +} + + +VOID PUT_ON_PORT_Q(struct PORTCONTROL * PORT, MESSAGE * Buffer) +{ + // TIME STAMP IT + + time(&Buffer->Timestamp); + + if (PORT->TXPORT) + { + Buffer->PORT = PORT->TXPORT; // update port no in header + + PORT = GetPortTableEntryFromPortNum(PORT->TXPORT); + + if (PORT == NULL) + { + ReleaseBuffer(Buffer); + return; + } + } + C_Q_ADD(&PORT->PORTTX_Q, (UINT *)Buffer); +} + + +UCHAR * SETUPADDRESSES(struct _LINKTABLE * LINK, PMESSAGE Msg) +{ + // COPY ADDRESSES FROM LINK TABLE TO MESSAGE _BUFFER + + UCHAR * ptr1 = &LINK->DIGIS[0]; + UCHAR * ptr2 = &Msg->CTL; + int Digis = 8; + + memcpy(&Msg->DEST[0], &LINK->LINKCALL[0], 14); // COPY DEST AND ORIGIN + + Msg->DEST[6] |= 0x60; + Msg->ORIGIN[6] |= 0x60; + + while (Digis) + { + if (*(ptr1)) // any more to copy? + { + memcpy(ptr2, ptr1, 7); + ptr1 += 7; + ptr2 += 7; + Digis--; + } + else + break; + } + + *(ptr2 - 1) |= 1; // SET END OF ADDRESSES + + return ptr2; // Pointer to CTL +} + +VOID SDETX(struct _LINKTABLE * LINK) +{ + // Start sending frsmes if possible + + struct PORTCONTROL * PORT; + int Outstanding; + UCHAR * ptr1, * ptr2; + UCHAR CTL; + int count; + MESSAGE * Msg; + MESSAGE * Buffer; + + // DONT SEND IF RESEQUENCING RECEIVED FRAMES - CAN CAUSE FRMR PROBLEMS + +// if (LINK->L2RESEQ_Q) +// return; + + if (LINK->LINKPORT->PORTNUMBER == 19) + { + int i = 0; + } + + Outstanding = LINK->LINKNS - LINK->LINKOWS; // Was WS not NS + + if (Outstanding < 0) + Outstanding += 8; // allow for wrap + + if (Outstanding >= LINK->LINKWINDOW) // LIMIT + return; + + // See if we can load any more frames into the frame holding q + + while (LINK->TX_Q && LINK->FRAMES[LINK->SDTSLOT] == NULL) + { + Msg = Q_REM(&LINK->TX_Q); + Msg->CHAIN = NULL; + LINK->FRAMES[LINK->SDTSLOT] = Msg; + LINK->SDTSLOT ++; + LINK->SDTSLOT &= 7; + } + + // dont send while poll outstanding + + while ((LINK->L2FLAGS & POLLSENT) == 0) + { + Msg = LINK->FRAMES[LINK->LINKNS]; // is next frame available? + + if (Msg == NULL) + return; + + // send the frame + + // GET BUFFER FOR COPY OF MESSAGE - HAVE TO KEEP ORIGINAL FOR RETRIES + + Buffer = GetBuff(); + + if (Buffer == NULL) + return; + + ptr2 = SETUPADDRESSES(LINK, Buffer); // copy addresses + + // ptr2 NOW POINTS TO COMMAND BYTE + + // GOING TO SEND I FRAME - WILL ACK ANY RECEIVED FRAMES + + LINK->L2ACKREQ = 0; // CLEAR ACK NEEDED + LINK->L2SLOTIM = T3 + rand() % 15; // SET FRAME SENT RECENTLY + LINK->KILLTIMER = 0; // RESET IDLE CIRCUIT TIMER + + CTL = LINK->LINKNR << 5; // GET CURRENT N(R), SHIFT IT TO TOP 3 BITS + CTL |= LINK->LINKNS << 1; // BITS 1-3 OF CONTROL BYTE + + LINK->LINKNS++; // INCREMENT NS + LINK->LINKNS &= 7; // mod 8 + + // SET P BIT IF END OF WINDOW OR NO MORE TO SEND + + if (LINK->VER1FLAG == 0) // NO POLL BIT IF V1 + { + Outstanding = LINK->LINKNS - LINK->LINKOWS; + + if (Outstanding < 0) + Outstanding += 8; // allow for wrap + + // if at limit, or no more to send, set P) + + if (Outstanding >= LINK->LINKWINDOW || LINK->FRAMES[LINK->LINKNS] == NULL) + { + CTL |= PFBIT; + LINK->L2FLAGS |= POLLSENT; + LINK->L2TIMER = ONEMINUTE; // (RE)SET TIMER + + // FLAG BUFFER TO CAUSE TIMER TO BE RESET AFTER SEND (or ACK if ACKMODE) + + Buffer->Linkptr = LINK; + } + } + + *(ptr2++) = CTL; // TO DATA (STARTING WITH PID) + + count = Msg->LENGTH - MSGHDDRLEN; + + if (count > 0) // SHOULD ALWAYS BE A PID, BUT BETTER SAFE THAN SORRY + { + ptr1 = (UCHAR *)Msg; + ptr1 += MSGHDDRLEN; + memcpy(ptr2, ptr1, count); + } + + Buffer->DEST[6] |= 0x80; // SET COMMAND + + Buffer->LENGTH = (int)(ptr2 - (UCHAR *)Buffer) + count; // SET NEW LENGTH + + LINK->L2TIMER = ONEMINUTE; // (RE)SET TIMER + + PORT = LINK->LINKPORT; + + if (PORT) + { + Buffer->PORT = PORT->PORTNUMBER; + PUT_ON_PORT_Q(PORT, Buffer); + } + else + { + Buffer->Linkptr = 0; + ReleaseBuffer(Buffer); + } + + } +} + +VOID L2TimerProc() +{ + int i = MAXLINKS; + struct _LINKTABLE * LINK = LINKS; + struct PORTCONTROL * PORT = PORTTABLE; + + while (i--) + { + if (LINK->LINKCALL[0] == 0) + { + LINK++; + continue; + } + + // CHECK FOR TIMER EXPIRY OR BUSY CLEARED + + PORT = LINK->LINKPORT; + + if (PORT == NULL) + { + LINK++; + continue; // just ion case!! + } + + if (LINK->L2TIMER) + { + LINK->L2TIMER--; + if (LINK->L2TIMER == 0) + { + L2TIMEOUT(LINK, PORT); + LINK++; + continue; + } + } + else + { + // TIMER NOT RUNNING - MAKE SURE STATE NOT BELOW 5 - IF + // IT IS, SOMETHING HAS GONE WRONG, AND LINK WILL HANG FOREVER + + if (LINK->L2STATE < 5 && LINK->L2STATE != 2 && LINK->L2STATE != 1) // 2 = CONNECT - PROBABLY TO CQ + LINK->L2TIMER = 2; // ARBITRARY VALUE + } + + // TEST FOR RNR SENT, AND NOT STILL BUSY + + if (LINK->L2FLAGS & RNRSENT) + { + // Was busy + + if (RR_OR_RNR(LINK) != RNR) // SEE IF STILL BUSY + { + // Not still busy - tell other end + + // Just sending RR will hause a hang of RR is missed, and other end does not poll on Busy + // Try sending RR CP, so we will retry if not acked + + LINK->L2ACKREQ = 0; // CLEAR ANY DELAYED ACK TIMER + + if (LINK->L2RETRIES == 0) // IF RR(P) OUTSTANDING WILl REPORT ANYWAY + { + SendSupervisCmd(LINK); + LINK++; + continue; + } + } + } + else + { + // NOT BUSY + + if (LINK->L2ACKREQ) // DELAYED ACK TIMER + { + if (LINK->L2RETRIES == 0) // DONT SEND RR RESPONSE WHILEST RR(P) OUTSTANDING + { + LINK->L2ACKREQ--; + if (LINK->L2ACKREQ == 0) + { + SEND_RR_RESP(LINK, 0); // NO F BIT + LINK++; + continue; + } + } + } + } + + // CHECK FOR REJ TIMEOUT + + if (LINK->REJTIMER) + { + LINK->REJTIMER--; + if (LINK->REJTIMER == 0) // {REJ HAS TIMED OUT (THIS MUST BE A VERSION 1 SESSION) + { + // CANCEL REJ STATE + + if (LINK->L2STATE == 6) // REJ? + LINK->L2STATE = 5; // CLEAR REJ + } + } + + // See if time for link validation poll + + if (LINK->L2SLOTIM) + { + LINK->L2SLOTIM--; + if (LINK->L2SLOTIM == 0) // Time to poll + { + SendSupervisCmd(LINK); + LINK++; + continue; + } + } + + // See if idle too long + + LINK->KILLTIMER++; + + if (L2KILLTIME && LINK->KILLTIMER > L2KILLTIME) + { + // CIRCUIT HAS BEEN IDLE TOO LONG - SHUT IT DOWN + + LINK->KILLTIMER = 0; + LINK->L2TIMER = 1; // TO FORCE DISC + LINK->L2STATE = 4; // DISCONNECTING + + // TELL OTHER LEVELS + + InformPartner(LINK, NORMALCLOSE); + } + LINK++; + } +} + +VOID SendSupervisCmd(struct _LINKTABLE * LINK) +{ + // Send Super Command RR/RNR/REJ(P) + + UCHAR CTL; + + if (LINK->VER1FLAG == 1) + { + // VERSION 1 TIMEOUT + + // RESET TO RESEND I FRAMES + + LINK->LINKNS = LINK->LINKOWS; + + SDETX(LINK); // PREVENT FRMR (I HOPE) + } + + // SEND RR COMMAND - EITHER AS LINK VALIDATION POLL OR FOLLOWING TIMEOUT + + LINK->L2ACKREQ = 0; // CLEAR ACK NEEDED + + CTL = RR_OR_RNR(LINK); + +// MOV L2STATE[EBX],5 ; CANCEL REJ - ACTUALLY GOING TO 'PENDING ACK' + + CTL |= LINK->LINKNR << 5; // SHIFT N(R) TO TOP 3 BITS + CTL |= PFBIT; + + LINK->L2FLAGS |= POLLSENT; + + L2SENDCOMMAND(LINK, CTL); + + LINK->L2SLOTIM = T3 + rand() % 15; // SET FRAME SENT RECENTLY +} + +void SEND_RR_RESP(struct _LINKTABLE * LINK, UCHAR PF) +{ + UCHAR CTL; + + CTL = RR_OR_RNR(LINK); + +// MOV L2STATE[EBX],5 ; CANCEL REJ - ACTUALLY GOING TO 'PENDING ACK' + + CTL |= LINK->LINKNR << 5; // SHIFT N(R) TO TOP 3 BITS + CTL |= PF; + + L2SENDRESPONSE(LINK, CTL); + + ACKMSG(LINK); // SEE IF STILL WAITING FOR ACK +} + +VOID ACKMSG(struct _LINKTABLE * LINK) +{ + // RELEASE ANY ACKNOWLEDGED FRAMES + + while (LINK->LINKOWS != LINK->LINKWS) // is OLD WINDOW START EQUAL TO NEW WINDOW START? + { + // No, so frames to ack + + if (LINK->FRAMES[LINK->LINKOWS]) + ReleaseBuffer(Q_REM(&LINK->FRAMES[LINK->LINKOWS])); + else + { + char Call1[12], Call2[12]; + + Call1[ConvFromAX25(LINK->LINKCALL, Call1)] = 0; + Call2[ConvFromAX25(LINK->OURCALL, Call2)] = 0; + + Debugprintf("Missing frame to ack Seq %d Calls %s %s", LINK->LINKOWS, Call1, Call2); + } + + LINK->IFrameRetryCounter = 0; + + LINK->LINKOWS++; // INCREMENT OLD WINDOW START + LINK->LINKOWS &= 7; // MODULO 8 + + // SOMETHING HAS BEEN ACKED - RESET RETRY COUNTER + + if (LINK->L2RETRIES) + LINK->L2RETRIES = 1; // MUSTN'T SET TO ZERO - COULD CAUSE PREMATURE RETRANSMIT + + } + + if (LINK->LINKWS != LINK->LINKNS) // IS N(S) = NEW WINDOW START? + { + // NOT ALL I-FRAMES HAVE BEEN ACK'ED - RESTART TIMER + + // Need to kill link if we are getting repeated RR(F) after timeout + // (Indicating other station is seeing our RR(P) but not the resent I frame) + + if (LINK->IFrameRetryCounter++ > LINK->LINKPORT->PORTN2) + { + Debugprintf("Too many repeats of same I frame - closing connection"); + LINK->L2TIMER = 1; // USE TIMER TO SEND DISC + LINK->L2STATE = 4; // DISCONNECTING + return; + } + + + LINK->L2TIMER = LINK->L2TIME; + return; + } + + // ALL FRAMES HAVE BEEN ACKED - CANCEL TIMER UNLESS RETRYING + // IF RETRYING, MUST ONLY CANCEL WHEN RR(F) RECEIVED + + if (LINK->VER1FLAG == 1 || LINK->L2RETRIES == 0) // STOP TIMER IF LEVEL 1 or not retrying + { + LINK->L2TIMER = 0; + LINK->L2FLAGS &= ~POLLSENT; // CLEAR I(P) SET (IN CASE TALKING TO OLD BPQ!) + } + + // IF DISCONNECT REQUEST OUTSTANDING, AND NO FRAMES ON TX QUEUE, SEND DISC + + if ((LINK->L2FLAGS & DISCPENDING) && LINK->TX_Q == 0) + { + LINK->L2FLAGS &= ~DISCPENDING; + + LINK->L2TIMER = 1; // USE TIMER TO SEND DISC + LINK->L2STATE = 4; // DISCONNECTING + } +} + +VOID CONNECTFAILED(); + +VOID L2TIMEOUT(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT) +{ + // TIMER EXPIRED + + // IF LINK UP (STATE 5 OR ABOVE) SEND RR/RNR AS REQUIRED + // IF S2, REPEAT SABM + // IF S3, REPEAT FRMR + // IF S4, REPEAT DISC + + + PORT->L2TIMEOUTS++; // FOR STATS + + if (LINK->L2STATE == 0) + return; + + if (LINK->L2STATE == 1) + { + // XID + + LINK->L2RETRIES++; + if (LINK->L2RETRIES >= PORT->PORTN2) + { + // RETRIED N2 TIMES - Give up + + CONNECTFAILED(LINK); // TELL LEVEL 4 IT FAILED + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + DetachKISSHF(PORT); + + CLEAROUTLINK(LINK); + return; + } + + L2SENDXID(LINK); + return; + } + + + if (LINK->L2STATE == 2) + { + // CONNECTING + + LINK->L2RETRIES++; + if (LINK->L2RETRIES >= PORT->PORTN2) + { + // RETRIED N2 TIMES - Give up + + CONNECTFAILED(LINK); // TELL LEVEL 4 IT FAILED + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + DetachKISSHF(PORT); + + CLEAROUTLINK(LINK); + return; + } + + SENDSABM(LINK); + return; + } + + if (LINK->L2STATE == 4) + { + // DISCONNECTING + + LINK->L2RETRIES++; + + if (LINK->L2RETRIES >= PORT->PORTN2) + { + // RETRIED N2 TIMES - JUST CLEAR OUT LINK + + if (PORT->TNC && PORT->TNC->Hardware == H_KISSHF) + DetachKISSHF(PORT); + + CLEAROUTLINK(LINK); + return; + } + + L2SENDCOMMAND(LINK, DISC | PFBIT); + return; + } + + if (LINK->L2STATE == 3) + { + // FRMR + + LINK->L2RETRIES++; + if (LINK->L2RETRIES >= PORT->PORTN2) + { + // RETRIED N2 TIMES - RESET LINK + + LINK->L2RETRIES = 0; + LINK->L2STATE = 2; + SENDSABM(LINK); + return; + } + } + + // STATE 5 OR ABOVE + + // SEND RR(P) UP TO N2 TIMES + + LINK->L2RETRIES++; + + if (LINK->L2RETRIES >= PORT->PORTN2) + { + // RETRIED N TIMES SEND A COUPLE OF DISCS AND THEN CLOSE + + InformPartner(LINK, RETRIEDOUT); // TELL OTHER END ITS GONE + + LINK->L2RETRIES -= 1; // Just send one DISC + LINK->L2STATE = 4; // CLOSING + + L2SENDCOMMAND(LINK, DISC | PFBIT); + return; + } + + SendSupervisCmd(LINK); +} + +VOID SDFRMR(struct _LINKTABLE * LINK, struct PORTCONTROL * PORT) +{ + PORT->L2FRMRTX++; + + LINK->L2STATE = 3; // ENTER FRMR STATE + + LINK->L2TIMER = LINK->L2TIME; //SET TIMER + + SENDFRMR(LINK); +} + +VOID SENDFRMR(struct _LINKTABLE * LINK) +{ + // RESEND FRMR + + struct PORTCONTROL * PORT; + MESSAGE * Buffer; + UCHAR * ptr; + + Buffer = SETUPL2MESSAGE(LINK, FRMR); + + if (Buffer == NULL) + return; + + Buffer->ORIGIN[6] |= 0x80; // SET RESPONSE + + ptr = &Buffer->PID; + + *(ptr++) = LINK->SDRBYTE; // MOVE REJECT C-BYTE + + *(ptr++) = LINK->LINKNR << 5 | LINK->LINKNS << 1; + + *(ptr++) = LINK->SDREJF; // MOVE REJECT FLAGS + + Buffer->LENGTH += 3; + + PORT = LINK->LINKPORT; + Buffer->PORT = PORT->PORTNUMBER; + + if (PORT) + PUT_ON_PORT_Q(PORT, Buffer); + else + ReleaseBuffer(Buffer); + + return; +} + +VOID CLEAROUTLINK(struct _LINKTABLE * LINK) +{ + char toCall[12], fromCall[12]; + + toCall[ConvFromAX25(LINK->LINKCALL, toCall)] = 0; + fromCall[ConvFromAX25(LINK->OURCALL, fromCall)] = 0; + + hookL2SessionDeleted(LINK->LINKPORT->PORTNUMBER, fromCall, toCall, LINK); + + seeifUnlockneeded(LINK); + + CLEARL2QUEUES(LINK); // TO RELEASE ANY BUFFERS + + memset(LINK, 0, sizeof(struct _LINKTABLE)); +} + +VOID L2SENDXID(struct _LINKTABLE * LINK) +{ + // Set up and send XID + + struct PORTCONTROL * PORT; + UCHAR * ptr; + unsigned int xidval; + MESSAGE * Buffer; + + if (LINK->LINKPORT == 0) + return; //??? has been zapped + + Buffer = SETUPL2MESSAGE(LINK, XID | PFBIT); + + if (Buffer == NULL) + { + // NO BUFFERS - SET TIMER TO FORCE RETRY + + LINK->L2TIMER = 10*3; // SET TIMER + return; + } + + Buffer->DEST[6] |= 0x80; // SET COMMAND + + ptr = &Buffer->PID; + + // Set up default XID Mod 8 + + *ptr++ = 0x82; // FI + *ptr++ = 0x80; // GI + *ptr++ = 0x0; + *ptr++ = 0x10; // Length 16 + + *ptr++ = 0x02; // Classes of Procedures + *ptr++ = 0x02; // Length + *ptr++ = 0x00; // + *ptr++ = 0x21; // ABM Half Duplex + + // We offer REJ, SREJ and SREJ Multiframe + + *ptr++ = 0x03; // Optional Functions + *ptr++ = 0x03; // Len + + // Sync TX, SREJ Multiframe 16 bit FCS, Mod 8, TEST, + // Extended Addressing, REJ, SREJ + + xidval = OPMustHave | OPSREJ | OPSREJMult | OPREJ | OPMod8; + *ptr++ = xidval >> 16; + *ptr++ = xidval >> 8; + *ptr++ = xidval; + + + *ptr++ = 0x06; // RX Packet Len + *ptr++ = 0x02; // Len + *ptr++ = 0x08; // + *ptr++ = 0x00; // 2K bits (256) Bytes + + *ptr++ = 0x08; // RX Window + *ptr++ = 0x01; // Len + *ptr++ = 0x07; // 7 + + Buffer->LENGTH = (int)(ptr - (UCHAR *)Buffer); // SET LENGTH + + LINK->L2TIMER = ONEMINUTE; // (RE)SET TIMER + + // FLAG BUFFER TO CAUSE TIMER TO BE RESET AFTER SEND + + Buffer->Linkptr = LINK; + + PORT = LINK->LINKPORT; + + if (PORT) + { + Buffer->PORT = PORT->PORTNUMBER; + PUT_ON_PORT_Q(PORT, Buffer); + } + else + { + Buffer->Linkptr = 0; + ReleaseBuffer(Buffer); + } +} + + + + + + +VOID L2SENDCOMMAND(struct _LINKTABLE * LINK, int CMD) +{ + // SEND COMMAND IN CMD + + struct PORTCONTROL * PORT; + MESSAGE * Buffer; + + if (LINK->LINKPORT == 0) + return; //??? has been zapped + + Buffer = SETUPL2MESSAGE(LINK, CMD); + + if (Buffer == NULL) + { + // NO BUFFERS - SET TIMER TO FORCE RETRY + + if (CMD & PFBIT) // RESPONSE EXPECTED? + LINK->L2TIMER = 10*3; // SET TIMER + + return; + } + + Buffer->DEST[6] |= 0x80; // SET COMMAND + + if (CMD & PFBIT) // RESPONSE EXPECTED? + { + LINK->L2TIMER = ONEMINUTE; // (RE)SET TIMER + + // FLAG BUFFER TO CAUSE TIMER TO BE RESET AFTER SEND + + Buffer->Linkptr = LINK; + } + + PORT = LINK->LINKPORT; + + if (PORT) + { + Buffer->PORT = PORT->PORTNUMBER; + PUT_ON_PORT_Q(PORT, Buffer); + } + else + { + Buffer->Linkptr = 0; + ReleaseBuffer(Buffer); + } +} + + + + + + +VOID L2SENDRESPONSE(struct _LINKTABLE * LINK, int CMD) +{ + // SEND Response IN CMD + + struct PORTCONTROL * PORT; + MESSAGE * Buffer; + + Buffer = SETUPL2MESSAGE(LINK, CMD); + + if (Buffer == NULL) + { + // NO BUFFERS - SET TIMER TO FORCE RETRY + + if (CMD & PFBIT) // RESPONSE EXPECTED? + LINK->L2TIMER = 10*3; // SET TIMER + + return; + } + + Buffer->ORIGIN[6] |= 0x80; // SET RESPONSE + + LINK->L2SLOTIM = T3 + rand() % 15; // SET FRAME SENT RECENTLY + + PORT = LINK->LINKPORT; + Buffer->PORT = PORT->PORTNUMBER; + + if (PORT) + PUT_ON_PORT_Q(PORT, Buffer); + else + ReleaseBuffer(Buffer); + +} + + +MESSAGE * SETUPL2MESSAGE(struct _LINKTABLE * LINK, UCHAR CMD) +{ + MESSAGE * Buffer; + UCHAR * ptr; + + Buffer = GetBuff(); + + if (Buffer == NULL) + return NULL; + + ptr = SETUPADDRESSES(LINK, Buffer); // copy addresses + + // ptr NOW POINTS TO COMMAND BYTE + + *(ptr)++ = CMD; + + Buffer->LENGTH = (int)(ptr - (UCHAR *)Buffer); // SET LENGTH + + return Buffer; +} + + +VOID L3LINKCLOSED(struct _LINKTABLE * LINK, int Reason); + +VOID InformPartner(struct _LINKTABLE * LINK, int Reason) +{ + // LINK IS DISCONNECTING - IF THERE IS A CROSSLINK, SEND DISC TO IT + + if (LINK->LINKTYPE == 3) + { + L3LINKCLOSED(LINK, Reason); + return; + } + + if (LINK->CIRCUITPOINTER) + { + CloseSessionPartner(LINK->CIRCUITPOINTER); + CLEARSESSIONENTRY(LINK->CIRCUITPOINTER); + } +} + + +UINT RR_OR_RNR(struct _LINKTABLE * LINK) +{ + UCHAR Temp; + TRANSPORTENTRY * Session; + + LINK->L2FLAGS &= ~RNRSENT; + + // SET UP APPROPRIATE SUPER COMMAND + + if (LINK->LINKTYPE == 3) + + // Node to Node - only busy if short of buffers + + goto CHKBUFFS; + +// UP OR DOWN LINK - SEE IF SESSION IS BUSY + + if (LINK->CIRCUITPOINTER == 0) + goto CHKBUFFS; // NOT CONNECTED + + Session = LINK->CIRCUITPOINTER; // TO CIRCUIT ENTRY + + Temp = CHECKIFBUSYL2(Session); //TARGET SESSION BUSY? + + if (Temp & L4BUSY) + goto SENDRNR; // BUSY + +CHKBUFFS: + + if (QCOUNT < 20) + goto SENDRNR; // NOT ENOUGH + + // SEND REJ IF IN REJ STATE + + if (LINK->L2STATE == 6) + { + + // We may have the needed frame in RXFRAMES + +CheckNSLoop2: + + if (LINK->RXFRAMES[LINK->LINKNR]) + { + // We have the first missing frame. Process it. + + struct PORTCONTROL * PORT = LINK->LINKPORT; + MESSAGE * OldBuffer = Q_REM(&LINK->RXFRAMES[LINK->LINKNR]); + + Debugprintf("L2 about to send REJ - process saved Frame %d", LINK->LINKNR); + PROC_I_FRAME(LINK, PORT, OldBuffer); // Passes on or releases Buffer + + // NR has been updated. + + // Clear REJ if we have no more saved + + if (LINK->Ver2point2) // Using SREJ? + { + // see if any frames saved. + + int i; + + for (i = 0; i < 8; i++) + { + if (LINK->RXFRAMES[i]) + goto stayinREJ2; + } + // Drop through if no stored frames + } + + LINK->L2STATE = 5; + LINK->L2FLAGS &= ~REJSENT; +stayinREJ2: + LINK->L2ACKREQ = 0; // Cancel Resptime (Set by PROC_I_FRAME) + + goto CheckNSLoop2; // See if OK or we have another saved frame + } + if (LINK->L2STATE == 6) + + // if we support SREJ send that instesd or REJ + + if (LINK->Ver2point2) // We only allow 2.2 with SREJ Multi + return SREJ; + else + return REJ; + } + return RR; + +SENDRNR: + + LINK->L2FLAGS |= RNRSENT; // REMEMBER + + return RNR; +} + + +VOID ConnectFailedOrRefused(struct _LINKTABLE * LINK, char * Msg); + +VOID CONNECTFAILED(struct _LINKTABLE * LINK) +{ + ConnectFailedOrRefused(LINK, "Failure with"); +} +VOID CONNECTREFUSED(struct _LINKTABLE * LINK) +{ + ConnectFailedOrRefused(LINK, "Busy from"); +} + +VOID L3CONNECTFAILED(); + +VOID ConnectFailedOrRefused(struct _LINKTABLE * LINK, char * Msg) +{ + // IF DOWNLINK, TELL PARTNER + // IF CROSSLINK, TELL ROUTE CONTROL + + struct DATAMESSAGE * Buffer; + UCHAR * ptr1; + char Normcall[10]; + TRANSPORTENTRY * Session; + TRANSPORTENTRY * InSession; + + if (LINK->LINKTYPE == 3) + { + L3CONNECTFAILED(LINK); // REPORT TO LEVEL 3 + return; + } + + if (LINK->CIRCUITPOINTER == 0) // No crosslink?? + return; + + Buffer = GetBuff(); + + if (Buffer == NULL) + return; + + // SET UP HEADER + + Buffer->PID = 0xf0; + + ptr1 = SetupNodeHeader(Buffer); + + Normcall[ConvFromAX25(LINK->LINKCALL, Normcall)] = 0; + + ptr1 += sprintf(ptr1, "%s %s\r", Msg, Normcall); + + Buffer->LENGTH = (int)(ptr1 - (UCHAR *)Buffer); + + Session = LINK->CIRCUITPOINTER; // GET CIRCUIT TABLE ENTRY + InSession = Session->L4CROSSLINK; // TO INCOMMING SESSION + + CLEARSESSIONENTRY(Session); + + if (InSession) + { + InSession->L4CROSSLINK = NULL; // CLEAR REVERSE LINK + C_Q_ADD(&InSession->L4TX_Q, Buffer); + PostDataAvailable(InSession); + } + else + ReleaseBuffer(Buffer); +} + +VOID SENDCONNECTREPLY(struct _LINKTABLE * LINK) +{ + // LINK SETUP COMPLETE + + struct DATAMESSAGE * Buffer; + UCHAR * ptr1; + char Normcall[10]; + TRANSPORTENTRY * Session; + TRANSPORTENTRY * InSession; + + if (LINK->LINKTYPE == 3) + return; + + // UP/DOWN LINK + + if (LINK->CIRCUITPOINTER == 0) // No crosslink?? + return; + + Buffer = GetBuff(); + + if (Buffer == NULL) + return; + + // SET UP HEADER + + Buffer->PID = 0xf0; + + ptr1 = SetupNodeHeader(Buffer); + + Normcall[ConvFromAX25(LINK->LINKCALL, Normcall)] = 0; + + ptr1 += sprintf(ptr1, "Connected to %s\r", Normcall); + + Buffer->LENGTH = (int)(ptr1 - (UCHAR *)Buffer); + + Session = LINK->CIRCUITPOINTER; // GET CIRCUIT TABLE ENTRY + Session->L4STATE = 5; + InSession = Session->L4CROSSLINK; // TO INCOMMONG SESSION + + if (InSession) + { + C_Q_ADD(&InSession->L4TX_Q, Buffer); + PostDataAvailable(InSession); + } +} + + +TRANSPORTENTRY * SetupSessionForL2(struct _LINKTABLE * LINK) +{ + TRANSPORTENTRY * NewSess = L4TABLE; + int Index = 0; + + while (Index < MAXCIRCUITS) + { + if (NewSess->L4USER[0] == 0) + { + // Got One + + LINK->CIRCUITPOINTER = NewSess; // SETUP LINK-CIRCUIT CONNECTION + + memcpy(NewSess->L4USER, LINK->LINKCALL, 7); + memcpy(NewSess->L4MYCALL, MYCALL, 7); // ALWAYS USE _NODE CALL + + NewSess->CIRCUITINDEX = Index; //OUR INDEX + NewSess->CIRCUITID = NEXTID; + + NEXTID++; + if (NEXTID == 0) + NEXTID++; // kEEP nON-ZERO + + NewSess->L4TARGET.LINK = LINK; + + NewSess->L4CIRCUITTYPE = L2LINK | UPLINK; + + NewSess->L4STATE = 5; // SET LINK ACTIVE + + NewSess->SESSPACLEN = LINK->LINKPORT->PORTPACLEN; + + + NewSess->SESSIONT1 = L4T1; // Default + NewSess->L4WINDOW = (UCHAR)L4DEFAULTWINDOW; + + return NewSess; + } + Index++; + NewSess++; + } + + return NULL; +} + + +VOID Digipeat(struct PORTCONTROL * PORT, MESSAGE * Buffer, UCHAR * OurCall, int toPort, int UIOnly) // Digi it (if enabled) +{ + // WE MAY HAVE DISABLED DIGIPEAT ALTOGETHER, (DIGIFLAG=0), + // OR ALLOW ALLOW ONLY UI FRAMES TO BE DIGIED (DIGIFLAG=-1) + + // toPort and UIOnly are used for Cross Port digi feature + + int n; + + if (PORT->DIGIFLAG == 0 && toPort == 0) + { + ReleaseBuffer(Buffer); + return; + } + + OurCall[6] |= 0x80; // SET HAS BEEN REPEATED + + // SEE IF UI FRAME - scan forward for end of address bit + + n = 8; + + while ((OurCall[6] & 1) == 0) + { + OurCall += 7; + + if ((OurCall - &Buffer->CTL) > 56) + { + // Run off end before findin end of address + + ReleaseBuffer(Buffer); + return; + } + } + + if (toPort) // Cross port digi + { + if (((OurCall[7] & ~PFBIT) == 3) || UIOnly == 0) + { + // UI or Digi all + + Buffer->PORT = toPort; // update port no in header + PORT = GetPortTableEntryFromPortNum(toPort); + + if (PORT == NULL) + ReleaseBuffer(Buffer); + else + PUT_ON_PORT_Q(PORT, Buffer); + return; + } + else + { + ReleaseBuffer(Buffer); + return; + } + } + + if ((OurCall[7] & ~PFBIT) == 3) + { + // UI + + // UI FRAME. IF DIGIMASK IS NON-ZERO, SEND TO ALL PORTS SET, OTHERWISE SEND TO DIGIPORT + + PORT->L2DIGIED++; + + if (toPort) + { + // Cross port digi + + PORT = GetPortTableEntryFromPortNum(toPort); + Buffer->PORT = PORT->DIGIPORT; // update port no in header + + if (PORT == NULL) + ReleaseBuffer(Buffer); + else + PUT_ON_PORT_Q(PORT, Buffer); + + return; + } + + if (PORT->DIGIMASK == 0) + { + if (PORT->DIGIPORT) // Cross Band Digi? + { + Buffer->PORT = PORT->DIGIPORT; // update port no in header + + PORT = GetPortTableEntryFromPortNum(PORT->DIGIPORT); + + if (PORT == NULL) + { + ReleaseBuffer(Buffer); + return; + } + } + PUT_ON_PORT_Q(PORT, Buffer); + } + else + { + DigiToMultiplePorts(PORT, Buffer); + ReleaseBuffer(Buffer); + } + return; + } + + // Not UI - Only Digi if Digiflag not -1 + + if (PORT->DIGIFLAG == -1) + { + ReleaseBuffer(Buffer); + return; + } + + PORT->L2DIGIED++; + + if (PORT->DIGIPORT) // Cross Band Digi? + { + Buffer->PORT = PORT->DIGIPORT; // update port no in header + + PORT = GetPortTableEntryFromPortNum(PORT->DIGIPORT); + + if (PORT == NULL) + { + ReleaseBuffer(Buffer); + return; + } + } + PUT_ON_PORT_Q(PORT, Buffer); +} + +BOOL CheckForListeningSession(struct PORTCONTROL * PORT, MESSAGE * Msg) +{ + TRANSPORTENTRY * L4 = L4TABLE; + struct DATAMESSAGE * Buffer; + int i = MAXCIRCUITS; + UCHAR * ptr; + + while (i--) + { + if ((CountBits64(L4->LISTEN) == 1) && ((1 << ((Msg->PORT & 0x7f) - 1) && L4->LISTEN))) + { + // See if he is calling our call + + UCHAR ourcall[7]; // Call we are using (may have SSID bits inverted + memcpy(ourcall, L4->L4USER, 7); + ourcall[6] ^= 0x1e; // Flip SSID + + if (CompareCalls(Msg->DEST, ourcall)) + { + // Get Session Entry for Downlink + + TRANSPORTENTRY * NewSess = L4TABLE; + struct _LINKTABLE * LINK; + char Normcall[10]; + + int Index = 0; + + while (Index < MAXCIRCUITS) + { + if (NewSess->L4USER[0] == 0) + { + // Got One + + L4->L4CROSSLINK = NewSess; + NewSess->L4CROSSLINK = L4; + + memcpy(NewSess->L4USER, L4->L4USER, 7); + memcpy(NewSess->L4MYCALL, L4->L4USER, 7); + + NewSess->CIRCUITINDEX = Index; //OUR INDEX + NewSess->CIRCUITID = NEXTID; + NewSess->L4STATE = 5; + NewSess->L4CIRCUITTYPE = L2LINK+UPLINK; + + NEXTID++; + if (NEXTID == 0) + NEXTID++; // kEEP nON-ZERO + + NewSess->SESSIONT1 = L4->SESSIONT1; + NewSess->L4WINDOW = (UCHAR)L4DEFAULTWINDOW; + + // SET UP NEW SESSION (OR RESET EXISTING ONE) + + FindLink(Msg->ORIGIN, ourcall, PORT->PORTNUMBER, &LINK); + + if (LINK == NULL) + return FALSE; + + memcpy(LINK->LINKCALL, Msg->ORIGIN, 7); + LINK->LINKCALL[6] &= 0xFE; + memcpy(LINK->OURCALL, ourcall, 7); + + LINK->LINKPORT = PORT; + + LINK->L2TIME = PORT->PORTT1; +/* + // Copy Digis + + n = 7; + ptr = &LINK->DIGIS[0]; + + while (axcalls[n]) + { + memcpy(ptr, &axcalls[n], 7); + n += 7; + ptr += 7; + + LINK->L2TIME += 2 * PORT->PORTT1; // ADJUST TIMER VALUE FOR 1 DIGI + } +*/ + LINK->LINKTYPE = 2; // DOWNLINK + LINK->LINKWINDOW = PORT->PORTWINDOW; + + RESET2(LINK); // RESET ALL FLAGS + + LINK->L2STATE = 5; // CONNECTED + + LINK->CIRCUITPOINTER = NewSess; + + NewSess->L4TARGET.LINK = LINK; + + if (PORT->PORTPACLEN) + NewSess->SESSPACLEN = L4->SESSPACLEN = PORT->PORTPACLEN; + + L2SENDUA(PORT, Msg, Msg); // RESET OF DOWN/CROSSLINK + + L4->LISTEN = FALSE; // Take out of listen mode + + // Tell User + + Buffer = GetBuff(); + + if (Buffer == NULL) + return TRUE; + + // SET UP HEADER + + Buffer->PID = 0xf0; + + ptr = &Buffer->L2DATA[0]; + + Normcall[ConvFromAX25(LINK->LINKCALL, Normcall)] = 0; + + ptr += sprintf(ptr, "Incoming call from %s\r", Normcall); + + Buffer->LENGTH = (int)(ptr - (UCHAR *)Buffer); + + C_Q_ADD(&L4->L4TX_Q, Buffer); + PostDataAvailable(L4); + + return TRUE; + + } + Index++; + NewSess++; + } + return FALSE; + } + } + L4++; + } + return FALSE; +} + + +int COUNTLINKS(int Port); +VOID SuspendOtherPorts(struct TNCINFO * ThisTNC); +VOID ReleaseOtherPorts(struct TNCINFO * ThisTNC); + + +int CheckKissInterlock(struct PORTCONTROL * PORT, int Exclusive) +{ + // This checks for interlocked kiss and other ports. Returns 1 if attach/connect not allowed + + // If Exclusive is not set allow connects on specified port up to l2limit, + + // If Exclusive is set also don't allow any connects on specified port. + + // Generally use Exclusive if locking a port that doesn't allow shared access, eg ARDOP, VARAus + + // Maybe only Exclusive is needed, and just check session mode ports. Sharing of KISS ports is controlled by USERS + + int Interlock = PORT->PORTINTERLOCK; + + if (Interlock == 0) + return 0; // No locking + + PORT = PORTTABLE; + + if (Exclusive) + { + while(PORT) + { + if (PORT->TNC) + { + struct TNCINFO * TNC = PORT->TNC; + + if (Interlock == TNC->RXRadio || Interlock == TNC->TXRadio) // Same Group + { + // See if port in use + + int n; + + for (n = 0; n <= 26; n++) + { + if (TNC->PortRecord->ATTACHEDSESSIONS[n]) + { + return TNC->Port; ; // Refuse Connect + } + } + } + } + PORT = PORT->PORTPOINTER; + } + } + return 0; // ok to connect +} + +int seeifInterlockneeded(struct PORTCONTROL * PORT) +{ + // Can we just call SuspendOtherPorts - it won't do any harm if already suspended + // No, at that needs a TNC Record, so duplicate code here + + int i; + int Interlock = PORT->PORTINTERLOCK; + struct TNCINFO * TNC; + + if (Interlock == 0) + return 0; // No locking + + for (i = 1; i <= MAXBPQPORTS; i++) + { + TNC = TNCInfo[i]; + + if (TNC) + if (Interlock == TNC->RXRadio || Interlock == TNC->TXRadio) // Same Group + if (TNC->SuspendPortProc && TNC->PortRecord->PORTCONTROL.PortSuspended == FALSE) + TNC->SuspendPortProc(TNC, TNC); + } + + return 0; +} + +int seeifUnlockneeded(struct _LINKTABLE * LINK) +{ + // We need to see if any other links are active on any interlocked KISS ports. If not, release the lock + + int i; + int links = 0; + + int Interlock; + struct TNCINFO * TNC; + struct PORTCONTROL * PORT = LINK->LINKPORT; + + if (PORT == NULL) + return 0; + + // Should only be called for KISS links, but just in case + + if (PORT->PORTTYPE > 12) // INTERNAL or EXTERNAL? + return 0; // Not KISS Port + + Interlock = PORT->PORTINTERLOCK; + + if (Interlock == 0) + return 0; // No locking + + + // Count all L2 links on interlocked KISS ports + + PORT = PORTTABLE; + + while(PORT) + { + if (PORT->PORTTYPE <= 12) // INTERNAL or EXTERNAL? + if (Interlock == PORT->PORTINTERLOCK) + links += COUNTLINKS(PORT->PORTNUMBER); + + PORT = PORT->PORTPOINTER; + } + + if (links > 1) // must be the one we are closing + return 0; // Keep lock + + + for (i = 1; i <= MAXBPQPORTS; i++) + { + TNC = TNCInfo[i]; + + if (TNC) + if (Interlock == TNC->RXRadio || Interlock == TNC->TXRadio) // Same Group + if (TNC->ReleasePortProc && TNC->PortRecord->PORTCONTROL.PortSuspended == TRUE) + TNC->ReleasePortProc(TNC, TNC); + } + + return 0; +} + + + + diff --git a/L4Code-skigdebian.c b/L4Code-skigdebian.c new file mode 100644 index 0000000..59fc0e2 --- /dev/null +++ b/L4Code-skigdebian.c @@ -0,0 +1,2416 @@ +/* +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 +*/ + +// +// C replacement for L4Code.asm +// +#define Kernel + +#define _CRT_SECURE_NO_DEPRECATE + + +#pragma data_seg("_BPQDATA") + +#include "time.h" +#include "stdio.h" +#include + +#include "CHeaders.h" +#include "tncinfo.h" + +extern BPQVECSTRUC BPQHOSTVECTOR[]; +#define BPQHOSTSTREAMS 64 +#define IPHOSTVECTOR BPQHOSTVECTOR[BPQHOSTSTREAMS + 3] + +VOID CLOSECURRENTSESSION(TRANSPORTENTRY * Session); +VOID SENDL4DISC(TRANSPORTENTRY * Session); +int C_Q_COUNT(VOID * Q); +TRANSPORTENTRY * SetupSessionForL2(struct _LINKTABLE * LINK); +VOID InformPartner(struct _LINKTABLE * LINK, int Reason); +VOID IFRM150(TRANSPORTENTRY * Session, PDATAMESSAGE Buffer); +VOID SendConNAK(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG); +BOOL FINDCIRCUIT(L3MESSAGEBUFFER * L3MSG, TRANSPORTENTRY ** REQL4, int * NewIndex); +int GETBUSYBIT(TRANSPORTENTRY * L4); +BOOL cATTACHTOBBS(TRANSPORTENTRY * Session, UINT Mask, int Paclen, int * AnySessions); +VOID SETUPNEWCIRCUIT(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, + TRANSPORTENTRY * L4, char * BPQPARAMS, int ApplMask, int * BPQNODE); +extern char * ALIASPTR; +VOID SendConACK(struct _LINKTABLE * LINK, TRANSPORTENTRY * L4, L3MESSAGEBUFFER * L3MSG, BOOL BPQNODE, UINT Applmask, UCHAR * ApplCall); +VOID L3SWAPADDRESSES(L3MESSAGEBUFFER * L3MSG); +VOID L4TIMEOUT(TRANSPORTENTRY * L4); +struct DEST_LIST * CHECKL3TABLES(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * Msg); +int CHECKIFBUSYL4(TRANSPORTENTRY * L4); +VOID AUTOTIMER(); +VOID NRRecordRoute(UCHAR * Buff, int Len); +VOID REFRESHROUTE(TRANSPORTENTRY * Session); +VOID ACKFRAMES(L3MESSAGEBUFFER * L3MSG, TRANSPORTENTRY * L4, int NR); +VOID SENDL4IACK(TRANSPORTENTRY * Session); +VOID CHECKNEIGHBOUR(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * Msg); +VOID ProcessINP3RIF(struct ROUTE * Route, UCHAR * ptr1, int msglen, int Port); +VOID ProcessRTTMsg(struct ROUTE * Route, struct _L3MESSAGEBUFFER * Buff, int Len, int Port); +VOID FRAMEFORUS(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, int ApplMask, UCHAR * ApplCall); +void WriteConnectLog(char * fromCall, char * toCall, UCHAR * Mode); +void SendVARANetromMsg(struct TNCINFO * TNC, PL3MESSAGEBUFFER MSG); + +extern UINT APPLMASK; + +extern BOOL LogL4Connects; +extern BOOL LogAllConnects; + +// L4 Flags Values + +#define DISCPENDING 8 // SEND DISC WHEN ALL DATA ACK'ED + +extern APPLCALLS * APPL; + +VOID NETROMMSG(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG) +{ + // MAKE SURE PID IS 0CF - IN CASE SOMEONE IS SENDING L2 STUFF ON WHAT + // WE THINK IS A _NODE-_NODE LINK + + struct DEST_LIST * DEST; + + int n; + + if (L3MSG->L3PID != 0xCF) + { + ReleaseBuffer(L3MSG); + return; + } + + if (LINK->NEIGHBOUR == 0) + { + // NO ROUTE ASSOCIATED WITH THIS CIRCUIT - SET ONE UP + + CHECKNEIGHBOUR(LINK, L3MSG); + + if (LINK->NEIGHBOUR == 0) + { + // COULDNT SET UP NEIGHBOUR - CAN ONLY THROW IT AWAY + + ReleaseBuffer(L3MSG); + return; + } + } + + // See if a INP3 RIF (first Byte 0xFF) + + if (L3MSG->L3SRCE[0] == 0xff) + { + // INP3 + + ProcessINP3RIF(LINK->NEIGHBOUR, &L3MSG->L3SRCE[1], L3MSG->LENGTH - (MSGHDDRLEN + 2), L3MSG->Port); // = 2 = PID + FF Flag + ReleaseBuffer(L3MSG); + return; + } + + APPLMASK = 0; // NOT APPLICATION + + if (NODE) // _NODE SUPPORT INCLUDED? + { + + if (CompareCalls(L3MSG->L3DEST, MYCALL)) + { + FRAMEFORUS(LINK, L3MSG, APPLMASK, MYCALL); + return; + } + } + + // CHECK ALL L4 CALLS + + APPLMASK = 1; + ALIASPTR = &CMDALIAS[0][0]; + + n = NumberofAppls; + + APPL = APPLCALLTABLE; + + while (n--) + { + if (APPL->APPLCALL[0] > 0x40) // Valid ax.25 addr + { + if (CompareCalls(L3MSG->L3DEST, APPL->APPLCALL)) + { + FRAMEFORUS(LINK, L3MSG, APPLMASK, APPL->APPLCALL); + return; + } + } + APPLMASK <<= 1; + ALIASPTR += ALIASLEN; + APPL++; + } + + // IS IT INP3 (L3RTT) + + if (CompareCalls(L3MSG->L3DEST, L3RTT)) + { + ProcessRTTMsg(LINK->NEIGHBOUR, L3MSG, L3MSG->LENGTH, L3MSG->Port); + return; + } + + L3MSG->L3TTL--; + + if (L3MSG->L3TTL == 0) + { + ReleaseBuffer(L3MSG); + return; + } + + // If it is a record route frame we should add our call to the list before sending on + + if (L3MSG->L4FLAGS == 0 && L3MSG->L4ID == 1 && L3MSG->L4INDEX == 0) + { + // Add our call on end, and increase count + + int Len = L3MSG->LENGTH; + int Count; + + UCHAR * ptr = (UCHAR *)L3MSG; + + if (Len < 248) + { + ptr += (Len - 1); + + Count = (*(ptr++)) & 0x7F; // Mask End of Route + + memcpy(ptr, MYCALL, 7); + + ptr += 7; + + Count--; + *(ptr) = Count; + + if (Count) + L3MSG->LENGTH += 8; + } + } + + if (L3MSG->L3TTL > L3LIVES) + L3MSG->L3TTL = L3LIVES; // ENFORCE LIMIT ON ALL FRAMES SENT + + if (FindDestination(L3MSG->L3DEST, &DEST) == 0) + { + ReleaseBuffer(L3MSG); // CANT FIND DESTINATION + return; + } + + // IF MESSAGE ORIGINTED HERE, THERE MUST BE A ROUTING LOOP - + // THERE IS LITTLE POINT SENDING IT OVER THE SAME ROUTE AGAIN, + // SO SET ANOTHER ROUTE ACTIVE IF POSSIBLE + + if (CompareCalls(L3MSG->L3SRCE, MYCALL) || CompareCalls(L3MSG->L3SRCE, APPLCALLTABLE->APPLCALL)) + { + // MESSAGE HAS COME BACK TO ITS STARTING POINT - ACTIVATE ANOTHER ROUTE, + // UNLESS THERE IS ONLY ONE, IN WHICH CASE DISCARD IT + + if (DEST->NRROUTE[1].ROUT_NEIGHBOUR == 0) // No more routes + { + ReleaseBuffer(L3MSG); + return; + } + + DEST->DEST_ROUTE++; + + if (DEST->DEST_ROUTE == 4) // TO NEXT + DEST->DEST_ROUTE = 1; // TRY TO ACTIVATE FIRST + } + + // IF CURRENT ROUTE IS BACK THE WAY WE CAME, THEN ACTIVATE + //ANOTHER (IF POSSIBLE). + + if (DEST->DEST_ROUTE) + { + if (DEST->NRROUTE[DEST->DEST_ROUTE -1].ROUT_NEIGHBOUR == LINK->NEIGHBOUR) + { + // Current ROUTE IS BACK THE WAY WE CAME - ACTIVATE ANOTHER IF POSSIBLE + + DEST->DEST_ROUTE++; + if (DEST->DEST_ROUTE == 4) + DEST->DEST_ROUTE =1; + } + goto NO_PROBLEM; + } + else + { + // DONT HAVE AN ACTIVE ROUTE + + if (DEST->NRROUTE[0].ROUT_NEIGHBOUR == LINK->NEIGHBOUR) + { + // FIRST ROUTE IS BACK THE WAY WE CAME - ACTIVATE ANOTHER IF POSSIBLE + + DEST->DEST_ROUTE = 2; // WILL BE RESET BY L3 CODE IF THERE IS NOT OTHER ROUTE + } + } + +NO_PROBLEM: + + CHECKL3TABLES(LINK, L3MSG); + +// EVEN IF WE CANT PUT ORIGINATING NODE INTO OUR TABLES, PASS MSG ON +// ANYWAY - THE FINAL TARGET MAY HAVE ANOTHER WAY BACK + + + C_Q_ADD(&DEST->DEST_Q, L3MSG); + + L3FRAMES++; +} + +VOID SENDL4MESSAGE(TRANSPORTENTRY * L4, struct DATAMESSAGE * Msg) +{ + L3MESSAGEBUFFER * L3MSG; + struct DEST_LIST * DEST; + struct DATAMESSAGE * Copy; + int FRAGFLAG = 0; + int Len; + + // These make it simpler to understand code + +#define NullPKTLen 4 + sizeof(void *) // 4 is Port, Len, PID +#define MaxL4Len 236 + 4 + sizeof(void *) // Max NETROM Size + + + if (Msg->LENGTH == NullPKTLen) + { + // NO DATA - DISCARD IT + + ReleaseBuffer(Msg); + return; + } + + L3MSG = GetBuff(); + + if (L3MSG == 0) + { + // DONT THINK WE SHOULD GET HERE, UNLESS _QCOUNT IS CORRUPT, + // BUT IF WE DO, SHOULD RETURN MSG TO FREE Q - START TIMER, AND + // DROP THROUGH TO RELBUFF + + L4->L4TIMER = L4->SESSIONT1; + + ReleaseBuffer(Msg); + return; + } + + L3MSG->L3PID = 0xCF; // NET MESSAGE + + memcpy(L3MSG->L3SRCE, L4->L4MYCALL, 7); + + DEST = L4->L4TARGET.DEST; + memcpy(L3MSG->L3DEST, DEST->DEST_CALL, 7); + + L3MSG->L3TTL = L3LIVES; + + L3MSG->L4ID = L4->FARID; + L3MSG->L4INDEX = L4->FARINDEX; + + L3MSG->L4TXNO = L4->TXSEQNO; + + // SET UP RTT TIMER + + if (L4->RTT_TIMER == 0) + { + L4->RTT_SEQ = L4->TXSEQNO; + + L4->RTT_TIMER = GetTickCount(); + } + + L4->TXSEQNO++; + + + L4->L4LASTACKED = L3MSG->L4RXNO = L4->RXSEQNO; // SAVE LAST NUMBER ACKED + + // SEE IF CROSSSESSION IS BUSY + + GETBUSYBIT(L4); // Sets BUSY in NAKBITS if Busy + + L3MSG->L4FLAGS = L4INFO | L4->NAKBITS; + + L4->L4TIMER = L4->SESSIONT1; // SET TIMER + L4->L4ACKREQ = 0; // CANCEL ACK NEEDED + + Len = Msg->LENGTH; + + if (Len > MaxL4Len) // 236 DATA + 8 HEADER + { + // MUST FRAGMENT MESSAGE + + L3MSG->L4FLAGS |= L4MORE; + FRAGFLAG = 1; + + Len = MaxL4Len; + } + + Len += 20; // L3/4 Header + + L3MSG->LENGTH = Len; + + Len -= (20 + NullPKTLen); // Actual Data + + memcpy(L3MSG->L4DATA, Msg->L2DATA, Len); + + // CREATE COPY FOR POSSIBLE RETRY + + Copy = GetBuff(); + + if (Copy == 0) + { + // SHOULD NEVER HAPPEN + + ReleaseBuffer(Msg); + return; + } + + memcpy(Copy, L3MSG, L3MSG->LENGTH); + + // If we have fragmented, we should adjust length, or retry will send too much + // (bug in .asm code) + + if (FRAGFLAG) + Copy->LENGTH = MaxL4Len; + + C_Q_ADD(&L4->L4HOLD_Q, Copy); + + C_Q_ADD(&DEST->DEST_Q, L3MSG); + + DEST->DEST_COUNT++; // COUNT THEM + + L4FRAMESTX++; + + if (FRAGFLAG) + { + // MESSAGE WAS TOO BIG - ADJUST IT AND LOOP BACK + + Msg->LENGTH -= 236; + + memmove(Msg->L2DATA, &Msg->L2DATA[236], Msg->LENGTH - NullPKTLen); + + SENDL4MESSAGE(L4, Msg); + } +} + + +int GETBUSYBIT(TRANSPORTENTRY * L4) +{ + // SEE IF CROSSSESSION IS BUSY + + L4->NAKBITS &= ~L4BUSY; // Clear busy + + L4->NAKBITS |= CHECKIFBUSYL4(L4); // RETURNS AL WITH BUSY BIT SET IF CROSSSESSION IS BUSY + + return L4->NAKBITS; +} + +VOID Q_IP_MSG(MESSAGE * Buffer) +{ + if (IPHOSTVECTOR.HOSTAPPLFLAGS & 0x80) + { + // CHECK WE ARENT USING TOO MANY BUFFERS + + if (C_Q_COUNT(&IPHOSTVECTOR.HOSTTRACEQ) > 20) + ReleaseBuffer(Q_REM((void *)&IPHOSTVECTOR.HOSTTRACEQ)); + + C_Q_ADD(&IPHOSTVECTOR.HOSTTRACEQ, Buffer); + return; + } + + ReleaseBuffer(Buffer); +} + +VOID SENDL4CONNECT(TRANSPORTENTRY * Session) +{ + PL3MESSAGEBUFFER MSG = (PL3MESSAGEBUFFER)GetBuff(); + struct DEST_LIST * DEST = Session->L4TARGET.DEST; + + if (MSG == NULL) + return; + + if (DEST->DEST_CALL[0] == 0) + { + Debugprintf("Trying to send L4CREQ to NULL Destination"); + ReleaseBuffer(MSG); + return; + } + + MSG->L3PID = 0xCF; // NET MESSAGE + + memcpy(MSG->L3SRCE, Session->L4MYCALL, 7); + memcpy(MSG->L3DEST, DEST->DEST_CALL, 7); + + MSG->L3TTL = L3LIVES; + + MSG->L4INDEX = Session->CIRCUITINDEX; + MSG->L4ID = Session->CIRCUITID; + MSG->L4TXNO = 0; + MSG->L4RXNO = 0; + MSG->L4FLAGS = L4CREQ; + + MSG->L4DATA[0] = L4DEFAULTWINDOW; // PROPOSED WINDOW + + memcpy(&MSG->L4DATA[1], Session->L4USER, 7); // ORIG CALL + memcpy(&MSG->L4DATA[8], Session->L4MYCALL, 7); + + Session->L4TIMER = Session->SESSIONT1; // START TIMER + memcpy(&MSG->L4DATA[15], &Session->SESSIONT1, 2); // AND PUT IN MSG + + MSG->LENGTH = (int)(&MSG->L4DATA[17] - (UCHAR *)MSG); + + if (Session->SPYFLAG) + { + MSG->L4DATA[17] = 'Z'; // ADD SPY ON BBS FLAG + MSG->LENGTH++; + } + C_Q_ADD(&DEST->DEST_Q, (UINT *)MSG); +} + +void RETURNEDTONODE(TRANSPORTENTRY * Session) +{ + // SEND RETURNED TO ALIAS:CALL + + struct DATAMESSAGE * Msg = (struct DATAMESSAGE *)GetBuff(); + char Nodename[20]; + + if (Msg) + { + Msg->PID = 0xf0; + + Nodename[DecodeNodeName(MYCALLWITHALIAS, Nodename)] = 0; // null terminate + + Msg->LENGTH = (USHORT)sprintf(&Msg->L2DATA[0], "Returned to Node %s\r", Nodename) + 4 + sizeof(void *); + C_Q_ADD(&Session->L4TX_Q, (UINT *)Msg); + PostDataAvailable(Session); + } +} + + +extern void * BUFFER; + +VOID L4BG() +{ + // PROCESS DATA QUEUED ON SESSIONS + + int n = MAXCIRCUITS; + TRANSPORTENTRY * L4 = L4TABLE; + int MaxLinks = MAXLINKS; + UCHAR Outstanding; + struct DATAMESSAGE * Msg; + struct PORTCONTROL * PORT; + struct _LINKTABLE * LINK; + int Msglen, Paclen; + + while (n--) + { + if (L4->L4USER[0] == 0) + { + L4++; + continue; + } + while (L4->L4TX_Q) + { + if (L4->L4CIRCUITTYPE & BPQHOST) + break; // Leave on TXQ + + // SEE IF BUSY - NEED DIFFERENT TESTS FOR EACH SESSION TYPE + + if (L4->L4CIRCUITTYPE & SESSION) + { + // L4 SESSION - WILL NEED BUFFERS FOR SAVING COPY, + // AND POSSIBLY FRAGMENTATION + + if (QCOUNT < 15) + break; + + if (L4->FLAGS & L4BUSY) + { + // CHOKED - MAKE SURE TIMER IS RUNNING + + if (L4->L4TIMER == 0) + L4->L4TIMER = L4->SESSIONT1; + + break; + } + + // CHECK WINDOW + + Outstanding = L4->TXSEQNO - L4->L4WS; // LAST FRAME ACKED - GIVES NUMBER OUTSTANING + + // MOD 256, SO SHOULD HANDLE WRAP?? + + if (Outstanding > L4->L4WINDOW) + break; + + } + else if (L4->L4CIRCUITTYPE & L2LINK) + { + // L2 LINK + + LINK = L4->L4TARGET.LINK; + + if (COUNT_AT_L2(LINK) > 8) + break; + } + + // Not busy, so continue + + L4->L4KILLTIMER = 0; //RESET SESSION TIMEOUTS + + if(L4->L4CROSSLINK) + L4->L4CROSSLINK->L4KILLTIMER = 0; + + Msg = Q_REM((void *)&L4->L4TX_Q); + + if (L4->L4CIRCUITTYPE & PACTOR) + { + // PACTOR-like - queue to Port + + // Stream Number is in KAMSESSION + + Msg->PORT = L4->KAMSESSION; + PORT = L4->L4TARGET.PORT; + + C_Q_ADD(&PORT->PORTTX_Q, Msg); + + continue; + } + // non-pactor + + // IF CROSSLINK, QUEUE TO NEIGHBOUR, ELSE QUEUE ON LINK ENTRY + + if (L4->L4CIRCUITTYPE & SESSION) + { + SENDL4MESSAGE(L4, Msg); + ReleaseBuffer(Msg); + continue; + } + + LINK = L4->L4TARGET.LINK; + + // If we want to enforce PACLEN this may be a good place to do it + + Msglen = Msg->LENGTH - (MSGHDDRLEN + 1); //Dont include PID + Paclen = L4->SESSPACLEN; + + if (Paclen == 0) + Paclen = 256; + + if (Msglen > Paclen) + { + // Fragment it. + // Is it best to send Paclen packets then short or equal length? + // I think equal length; + + int Fragments = (Msglen + Paclen - 1) / Paclen; + int Fraglen = Msglen / Fragments; + + if ((Msglen & 1)) // Odd + Fraglen ++; + + while (Msglen > Fraglen) + { + PDATAMESSAGE Fragment = GetBuff(); + + if (Fragment == NULL) + break; // Cant do much else + + Fragment->PORT = Msg->PORT; + Fragment->PID = Msg->PID; + Fragment->LENGTH = Fraglen + (MSGHDDRLEN + 1); + + memcpy(Fragment->L2DATA, Msg->L2DATA, Fraglen); + + C_Q_ADD(&LINK->TX_Q, Fragment); + + memcpy(Msg->L2DATA, &Msg->L2DATA[Fraglen], Msglen - Fraglen); + Msglen -= Fraglen; + Msg->LENGTH -= Fraglen; + } + + // Drop through to send last bit + + } + + C_Q_ADD(&LINK->TX_Q, Msg); + } + + // if nothing on TX Queue If there is stuff on hold queue, timer must be running + +// if (L4->L4TX_Q == 0 && L4->L4HOLD_Q) + if (L4->L4HOLD_Q) + { + if (L4->L4TIMER == 0) + { + L4->L4TIMER = L4->SESSIONT1; + } + } + + // now check for rxed frames + + while(L4->L4RX_Q) + { + Msg = Q_REM((void *)&L4->L4RX_Q); + + IFRM150(L4, Msg); + + if (L4->L4USER[0] == 0) // HAVE JUST CLOSED SESSION! + goto NextSess; + } + + // IF ACK IS PENDING, AND WE ARE AT RX WINDOW, SEND ACK NOW + + Outstanding = L4->RXSEQNO - L4->L4LASTACKED; + if (Outstanding >= L4->L4WINDOW) + SENDL4IACK(L4); +NextSess: + L4++; + } +} + +VOID CLEARSESSIONENTRY(TRANSPORTENTRY * Session) +{ + + // RETURN ANY QUEUED BUFFERS TO FREE QUEUE + + while (Session->L4TX_Q) + ReleaseBuffer(Q_REM((void *)&Session->L4TX_Q)); + + while (Session->L4RX_Q) + ReleaseBuffer(Q_REM((void *)&Session->L4RX_Q)); + + while (Session->L4HOLD_Q) + ReleaseBuffer(Q_REM((void *)&Session->L4HOLD_Q)); + + if (C_Q_COUNT(&Session->L4RESEQ_Q) > Session->L4WINDOW) + { + Debugprintf("Corrupt RESEQ_Q Q Len %d Free Buffs %d", C_Q_COUNT(&Session->L4RESEQ_Q), QCOUNT); + Session->L4RESEQ_Q = 0; + } + + while (Session->L4RESEQ_Q) + ReleaseBuffer(Q_REM((void *)&Session->L4RESEQ_Q)); + + if (Session->PARTCMDBUFFER) + ReleaseBuffer(Session->PARTCMDBUFFER); + + memset(Session, 0, sizeof(TRANSPORTENTRY)); +} + +VOID CloseSessionPartner(TRANSPORTENTRY * Session) +{ + // SEND CLOSE TO CROSSLINKED SESSION AND CLEAR LOCAL SESSION + + if (Session == NULL) + return; + + if (Session->L4CROSSLINK) + CLOSECURRENTSESSION(Session->L4CROSSLINK); + + CLEARSESSIONENTRY(Session); +} + + +VOID CLOSECURRENTSESSION(TRANSPORTENTRY * Session) +{ + MESSAGE * Buffer; + struct _LINKTABLE * LINK; + +// SHUT DOWN SESSION AND UNLINK IF CROSSLINKED + + if (Session == NULL) + return; + + Session->L4CROSSLINK = NULL; + + // IF STAY FLAG SET, KEEP SESSION, AND SEND MESSAGE + + if (Session->STAYFLAG) + { + RETURNEDTONODE(Session); + Session->STAYFLAG = 0; // Only do it once + return; + } + + if (Session->L4CIRCUITTYPE & BPQHOST) + { + // BPQ HOST MODE SESSION - INDICATE STATUS CHANGE + + PBPQVECSTRUC HOST = Session->L4TARGET.HOST; + HOST->HOSTSESSION = 0; + HOST->HOSTFLAGS |= 3; /// State Change + + PostStateChange(Session); + CLEARSESSIONENTRY(Session); + return; + } + + if (Session->L4CIRCUITTYPE & PACTOR) + { + // PACTOR-type (Session linked to Port) + + struct _EXTPORTDATA * EXTPORT = Session->L4TARGET.EXTPORT; + + // If any data is queued, move it to the port entry, so it can be sent before the disconnect + + while (Session->L4TX_Q) + { + Buffer = Q_REM((void *)&Session->L4TX_Q); + EXTPORT->PORTCONTROL.PORTTXROUTINE(EXTPORT, Buffer); + } + + EXTPORT->ATTACHEDSESSIONS[Session->KAMSESSION] = NULL; + + CLEARSESSIONENTRY(Session); + return; + } + + if (Session->L4CIRCUITTYPE & SESSION) + { + // L4 SESSION TO CLOSE + + if (Session->L4HOLD_Q || Session->L4TX_Q) // WAITING FOR ACK or MORE TO SEND - SEND DISC LATER + { + Session->FLAGS |= DISCPENDING; // SEND DISC WHEN ALL DATA ACKED + return; + } + + SENDL4DISC(Session); + return; + } + + // Must be LEVEL 2 SESSION TO CLOSE + + // COPY ANY PENDING DATA TO L2 TX Q, THEN GET RID OF SESSION + + LINK = Session->L4TARGET.LINK; + + if (LINK == NULL) // just in case! + { + CLEARSESSIONENTRY(Session); + return; + } + + while (Session->L4TX_Q) + { + Buffer = Q_REM((void *)&Session->L4TX_Q); + C_Q_ADD(&LINK->TX_Q, Buffer); + } + + // NOTHING LEFT AT SESSION LEVEL + + LINK->CIRCUITPOINTER = NULL; // CLEAR REVERSE LINK + + if ((LINK->LINKWS != LINK->LINKNS) || LINK->TX_Q) + { + // STILL MORE TO SEND - SEND DISC LATER + + LINK->L2FLAGS |= DISCPENDING; // SEND DISC WHEN ALL DATA ACKED + } + else + { + // NOTHING QUEUED - CAN SEND DISC NOW + + LINK->L2STATE = 4; // DISCONNECTING + LINK->L2TIMER = 1; // USE TIMER TO KICK OFF DISC + } + + CLEARSESSIONENTRY(Session); + +} + +VOID L4TimerProc() +{ + // CHECK FOR TIMER EXPIRY + + int n = MAXCIRCUITS; + TRANSPORTENTRY * L4 = L4TABLE; + TRANSPORTENTRY * Partner; + int MaxLinks = MAXLINKS; + + while (n--) + { + if (L4->L4USER[0] == 0) + { + L4++; + continue; + } + + // CHECK FOR L4BUSY SET AND NO LONGER BUSY + + if (L4->NAKBITS & L4BUSY) + { + if ((CHECKIFBUSYL4(L4) & L4BUSY) == 0) + { + // no longer busy + + L4->NAKBITS &= ~L4BUSY; // Clear busy + L4->L4ACKREQ = 1; // SEND ACK + } + } + + if (L4->L4TIMER) // Timer Running? + { + L4->L4TIMER--; + if (L4->L4TIMER == 0) // Expired + L4TIMEOUT(L4); + } + + if (L4->L4ACKREQ) // DELAYED ACK Timer Running? + { + L4->L4ACKREQ--; + if (L4->L4ACKREQ == 0) // Expired + SENDL4IACK(L4); + } + + L4->L4KILLTIMER++; + + // IF BIT 6 OF APPLFLAGS SET, SEND MSG EVERY 11 MINS TO KEEP SESSION OPEN + + if (L4->L4CROSSLINK) // CONNECTED? + if (L4->SESS_APPLFLAGS & 0x40) + if (L4->L4KILLTIMER > 11 * 60) + AUTOTIMER(L4); + + if (L4->L4LIMIT == 0) + L4->L4LIMIT = L4LIMIT; + else + { + if (L4->L4KILLTIMER > L4->L4LIMIT) + { + L4->L4KILLTIMER = 0; + + // CLOSE THIS SESSION, AND ITS PARTNER (IF ANY) + + L4->STAYFLAG = 0; + + Partner = L4->L4CROSSLINK; + CLOSECURRENTSESSION(L4); + + if (Partner) + { + Partner->L4KILLTIMER = 0; //ITS TIMES IS ALSO ABOUT TO EXPIRE + CLOSECURRENTSESSION(Partner); // CLOSE THIS ONE + } + } + } + L4++; + } +} + +VOID L4TIMEOUT(TRANSPORTENTRY * L4) +{ + // TIMER EXPIRED + + // IF LINK UP REPEAT TEXT + // IF S2, REPEAT CONNECT REQUEST + // IF S4, REPEAT DISCONNECT REQUEST + + struct DATAMESSAGE * Msg; + struct DATAMESSAGE * Copy; + struct DEST_LIST * DEST; + + if (L4->L4STATE < 2) + return; + + if (L4->L4STATE == 2) + { + // RETRY CONNECT + + L4->L4RETRIES++; + + if (L4->L4RETRIES > L4N2) + { + // RETRIED N2 TIMES - FAIL LINK + + L4CONNECTFAILED(L4); // TELL OTHER PARTNER IT FAILED + CLEARSESSIONENTRY(L4); + return; + } + + Debugprintf("Retrying L4 Connect Request"); + + SENDL4CONNECT(L4); // Resend connect + return; + } + + if (L4->L4STATE == 4) + { + // RETRY DISCONNECT + + L4->L4RETRIES++; + + if (L4->L4RETRIES > L4N2) + { + // RETRIED N2 TIMES - FAIL LINK + + + CLEARSESSIONENTRY(L4); + return; + } + + SENDL4DISC(L4); // Resend connect + return; + } + // STATE 5 OR ABOVE - RETRY INFO + + + L4->FLAGS &= ~L4BUSY; // CANCEL CHOKE + + L4->L4RETRIES++; + + if (L4->L4RETRIES > L4N2) + { + // RETRIED N2 TIMES - FAIL LINK + + CloseSessionPartner(L4); // SEND CLOSE TO PARTNER (IF PRESENT) + return; + } + + // RESEND ALL OUTSTANDING FRAMES + + L4->FLAGS &= 0x7F; // CLEAR CHOKED + + Msg = L4->L4HOLD_Q; + + while (Msg) + { + Copy = GetBuff(); + + if (Copy == 0) + return; + + memcpy(Copy, Msg, Msg->LENGTH); + + DEST = L4->L4TARGET.DEST; + + C_Q_ADD(&DEST->DEST_Q, Copy); + + L4FRAMESRETRIED++; + + Msg = Msg->CHAIN; + } +} + +VOID AUTOTIMER(TRANSPORTENTRY * L4) +{ + // SEND MESSAGE TO USER TO KEEP CIRCUIT OPEN + + struct DATAMESSAGE * Msg = GetBuff(); + + if (Msg == 0) + return; + + Msg->PID = 0xf0; + Msg->L2DATA[0] = 0; + Msg->L2DATA[1] = 0; + + Msg->LENGTH = MSGHDDRLEN + 3; + + C_Q_ADD(&L4->L4TX_Q, Msg); + + PostDataAvailable(L4); + + L4->L4KILLTIMER = 0; + + if (L4->L4CROSSLINK) + L4->L4CROSSLINK->L4KILLTIMER = 0; +} + +VOID L4CONNECTFAILED(TRANSPORTENTRY * L4) +{ + // CONNECT HAS TIMED OUT - SEND MESSAGE TO OTHER END + + struct DATAMESSAGE * Msg; + TRANSPORTENTRY * Partner; + UCHAR * ptr1; + char Nodename[20]; + struct DEST_LIST * DEST; + + Partner = L4->L4CROSSLINK; + + if (Partner == 0) + return; + + Msg = GetBuff(); + + if (Msg == 0) + return; + + Msg->PID = 0xf0; + + ptr1 = SetupNodeHeader(Msg); + + DEST = L4->L4TARGET.DEST; + Nodename[DecodeNodeName(DEST->DEST_CALL, Nodename)] = 0; // null terminate + + ptr1 += sprintf(ptr1, "Failure with %s\r", Nodename); + + Msg->LENGTH = (int)(ptr1 - (UCHAR *)Msg); + + C_Q_ADD(&Partner->L4TX_Q, Msg); + + PostDataAvailable(Partner); + + Partner->L4CROSSLINK = 0; // Back to command lewel +} + + +VOID ProcessIframe(struct _LINKTABLE * LINK, PDATAMESSAGE Buffer) +{ + // IF UP/DOWN LINK, AND CIRCUIT ESTABLISHED, ADD LEVEL 3/4 HEADERS + // (FRAGMENTING IF NECESSARY), AND PASS TO TRANSPORT CONTROL + // FOR ESTABLISHED ROUTE + + // IF INTERNODE MESSAGE, PASS TO ROUTE CONTROL + + // IF UP/DOWN, AND NO CIRCUIT, PASS TO COMMAND HANDLER + + TRANSPORTENTRY * Session; + + // IT IS POSSIBLE TO MULTIPLEX NETROM AND IP STUFF ON THE SAME LINK + + if (Buffer->PID == 0xCC || Buffer->PID == 0xCD) + { + Q_IP_MSG((MESSAGE *)Buffer); + return; + } + + if (Buffer->PID == 0xCF) + { + // INTERNODE frame + + // IF LINKTYPE IS NOT 3, MUST CHECK IF WE HAVE ACCIDENTALLY ATTACHED A BBS PORT TO THE NODE + + if (LINK->LINKTYPE != 3) + { + if (LINK->CIRCUITPOINTER) + { + // MUST KILL SESSION + + InformPartner(LINK, NORMALCLOSE); // CLOSE IT + LINK->CIRCUITPOINTER = NULL; // AND UNHOOK + } + LINK->LINKTYPE = 3; // NOW WE KNOW ITS A CROSSLINK + } + + NETROMMSG(LINK, (L3MESSAGEBUFFER *)Buffer); + return; + } + + if (LINK->LINKTYPE == 3) + { + // Weve receved a non- netrom frame on an inernode link + + ReleaseBuffer(Buffer); + return; + } + + if (LINK->CIRCUITPOINTER) + { + // Pass to Session + + IFRM150(LINK->CIRCUITPOINTER, Buffer); + return; + } + + // UPLINK MESSAGE WITHOUT LEVEL 4 ENTRY - CREATE ONE + + Session = SetupSessionForL2(LINK); + + if (Session == NULL) + return; + + CommandHandler(Session, Buffer); + return; +} + + +VOID IFRM100(struct _LINKTABLE * LINK, PDATAMESSAGE Buffer) +{ + TRANSPORTENTRY * Session; + + if (LINK->CIRCUITPOINTER) + { + // Pass to Session + + IFRM150(LINK->CIRCUITPOINTER, Buffer); + return; + } + + // UPLINK MESSAGE WITHOUT LEVEL 4 ENTRY - CREATE ONE + + Session = SetupSessionForL2(LINK); + + if (Session == NULL) + return; + + CommandHandler(Session, Buffer); + return; +} + + +VOID IFRM150(TRANSPORTENTRY * Session, PDATAMESSAGE Buffer) +{ + TRANSPORTENTRY * Partner; + struct _LINKTABLE * LINK; + + Session->L4KILLTIMER = 0; // RESET SESSION TIMEOUT + + if (Session->L4CROSSLINK == NULL) // CONNECTED? + { + // NO, SO PASS TO COMMAND HANDLER + + CommandHandler(Session, Buffer); + return; + } + + Partner = Session->L4CROSSLINK; // TO SESSION PARTNER + + if (Partner->L4STATE == 5) + { + C_Q_ADD(&Partner->L4TX_Q, Buffer); + PostDataAvailable(Partner); + return; + } + + + + // MESSAGE RECEIVED BEFORE SESSION IS UP - CANCEL SESSION + // AND PASS MESSAGE TO COMMAND HANDLER + + if (Partner->L4CIRCUITTYPE & L2LINK) // L2 SESSION? + { + // MUST CANCEL L2 SESSION + + LINK = Partner->L4TARGET.LINK; + LINK->CIRCUITPOINTER = NULL; // CLEAR REVERSE LINK + + LINK->L2STATE = 4; // DISCONNECTING + LINK->L2TIMER = 1; // USE TIMER TO KICK OFF DISC + + LINK->L2RETRIES = LINK->LINKPORT->PORTN2 - 2; //ONLY SEND DISC ONCE + } + + CLEARSESSIONENTRY(Partner); + + Session->L4CROSSLINK = 0; // CLEAR CROSS LINK + CommandHandler(Session, Buffer); + return; +} + + +VOID SENDL4DISC(TRANSPORTENTRY * Session) +{ + L3MESSAGEBUFFER * MSG; + struct DEST_LIST * DEST = Session->L4TARGET.DEST; + + if (Session->L4STATE < 4) + { + // CIRCUIT NOT UP OR CLOSING - PROBABLY NOT YET SET UP - JUST ZAP IT + + CLEARSESSIONENTRY(Session); + return; + } + + Session->L4TIMER = Session->SESSIONT1; // START TIMER + Session->L4STATE = 4; // SET DISCONNECTING + Session->L4ACKREQ = 0; // CANCEL ACK NEEDED + + MSG = GetBuff(); + + if (MSG == NULL) + return; + + MSG->L3PID = 0xCF; // NET MESSAGE + + memcpy(MSG->L3SRCE, Session->L4MYCALL, 7); + memcpy(MSG->L3DEST, DEST->DEST_CALL, 7); + MSG->L3TTL = L3LIVES; + MSG->L4INDEX = Session->FARINDEX; + MSG->L4ID = Session->FARID; + MSG->L4TXNO = 0; + MSG->L4FLAGS = L4DREQ; + + MSG->LENGTH = (int)(&MSG->L4DATA[0] - (UCHAR *)MSG); + + C_Q_ADD(&DEST->DEST_Q, (UINT *)MSG); +} + + +void WriteL4LogLine(UCHAR * mycall, UCHAR * call, UCHAR * node) +{ + UCHAR FN[MAX_PATH]; + FILE * L4LogHandle; + time_t T; + struct tm * tm; + + char Call1[12], Call2[12], Call3[12]; + + char LogMsg[256]; + int MsgLen; + + Call1[ConvFromAX25(mycall, Call1)] = 0; + Call2[ConvFromAX25(call, Call2)] = 0; + Call3[ConvFromAX25(node, Call3)] = 0; + + + T = time(NULL); + tm = gmtime(&T); + + sprintf(FN,"%s/L4Log_%02d%02d.txt", BPQDirectory, tm->tm_mon + 1, tm->tm_mday); + + L4LogHandle = fopen(FN, "ab"); + + if (L4LogHandle == NULL) + return; + + MsgLen = sprintf(LogMsg, "%02d:%02d:%02d Call to %s from %s at Node %s\r\n", tm->tm_hour, tm->tm_min, tm->tm_sec, Call1, Call2, Call3); + + fwrite(LogMsg , 1, MsgLen, L4LogHandle); + + fclose(L4LogHandle); +} + +VOID CONNECTREQUEST(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, UINT ApplMask, UCHAR * ApplCall) +{ + // CONNECT REQUEST - SEE IF EXISTING SESSION + // IF NOT, GET AND FORMAT SESSION TABLE ENTRY + // SEND CONNECT ACK + + // EDI = _BUFFER, EBX = LINK + + TRANSPORTENTRY * L4; + int BPQNODE = 0; // NOT ONE OF MINE + char BPQPARAMS[10]; // Extended Connect Params from BPQ Node + int CONERROR; + int Index; + + memcpy(BPQPARAMS, &L4T1, 2); // SET DEFAULT T1 IN CASE NOT FROM ANOTHER BPQ NODE + + BPQPARAMS[2] = 0; // 'SPY' NOT SET + + if (CheckExcludeList(&L3MSG->L4DATA[1]) == 0) + { + SendConNAK(LINK, L3MSG); + return; + } + + if (FINDCIRCUIT(L3MSG, &L4, &Index)) + { + // SESSION EXISTS - ASSUME RETRY AND SEND ACK + + SendConACK(LINK, L4, L3MSG, BPQNODE, ApplMask, ApplCall); + return; + } + + + if (L4 == 0) + { + SendConNAK(LINK, L3MSG); + return; + } + + L4->CIRCUITINDEX = Index; + + SETUPNEWCIRCUIT(LINK, L3MSG, L4, BPQPARAMS, ApplMask, &BPQNODE); + + if (L4->L4TARGET.DEST == 0) + { + // NODE NOT IN TABLE, AND TABLE FULL - CANCEL IT + + memset(L4, 0, sizeof (TRANSPORTENTRY)); + SendConNAK(LINK, L3MSG); + return; + } + // IF CONNECT TO APPL, ALLOCATE BBS PORT + + if (ApplMask == 0 || BPQPARAMS[2] == 'Z') // Z is "Spy" Connect + { + SendConACK(LINK, L4, L3MSG, BPQNODE, ApplMask, ApplCall); + + return; + } + + // IF APPL CONNECT, SEE IF APPL HAS AN ALIAS + + + if (ALIASPTR[0] > ' ') + { + struct DATAMESSAGE * Msg; + + // ACCEPT THE CONNECT, THEN INVOKE THE ALIAS + + SendConACK(LINK, L4, L3MSG, BPQNODE, ApplMask, ApplCall); + + Msg = GetBuff(); + + if (Msg) + { + Msg->PID = 0xf0; + memcpy(Msg->L2DATA, APPL->APPLCMD, 12); + Msg->L2DATA[12] = 13; + Msg->LENGTH = MSGHDDRLEN + 12 + 2; // 2 for PID and CR + + C_Q_ADD(&L4->L4RX_Q, Msg); + return; + } + } + + if (cATTACHTOBBS(L4, ApplMask, PACLEN, &CONERROR)) + { + SendConACK(LINK, L4, L3MSG, BPQNODE, ApplMask, ApplCall); + return; + } + + // NO BBS AVAILABLE + + CLEARSESSIONENTRY(L4); + SendConNAK(LINK, L3MSG); + return; +} + +VOID SendConACK(struct _LINKTABLE * LINK, TRANSPORTENTRY * L4, L3MESSAGEBUFFER * L3MSG, BOOL BPQNODE, UINT Applmask, UCHAR * ApplCall) +{ + // SEND CONNECT ACK + + struct TNCINFO * TNC; + + L4CONNECTSIN++; + + L3MSG->L4TXNO = L4->CIRCUITINDEX; + L3MSG->L4RXNO = L4->CIRCUITID; + + L3MSG->L4DATA[0] = L4->L4WINDOW; //WINDOW + + L3MSG->L4FLAGS = L4CACK; + + if (LogL4Connects) + WriteL4LogLine(ApplCall, L4->L4USER, L3MSG->L3SRCE); + + if (LogAllConnects) + { + char From[64]; + char toCall[12], fromCall[12], atCall[12]; + + toCall[ConvFromAX25(ApplCall, toCall)] = 0; + fromCall[ConvFromAX25(L4->L4USER, fromCall)] = 0; + atCall[ConvFromAX25(L3MSG->L3SRCE, atCall)] = 0; + + sprintf(From, "%s at Node %s", fromCall, atCall); + WriteConnectLog(From, toCall, "NETROM"); + } + + + if (CTEXTLEN && Applmask == 0) // Connects to Node (not application) + { + struct DATAMESSAGE * Msg; + int Totallen = CTEXTLEN; + int Paclen= PACLEN; + UCHAR * ptr = CTEXTMSG; + + if (Paclen == 0) + Paclen = PACLEN; + + while(Totallen) + { + Msg = GetBuff(); + + if (Msg == NULL) + break; // No Buffers + + Msg->PID = 0xf0; + + if (Paclen > Totallen) + Paclen = Totallen; + + memcpy(Msg->L2DATA, ptr, Paclen); + Msg->LENGTH = Paclen + MSGHDDRLEN + 1; + + C_Q_ADD(&L4->L4TX_Q, Msg); // SEND MESSAGE TO CALLER + PostDataAvailable(L4); + ptr += Paclen; + Totallen -= Paclen; + } + } + + L3SWAPADDRESSES(L3MSG); + + L3MSG->L3TTL = L3LIVES; + + L3MSG->LENGTH = MSGHDDRLEN + 22; // CTL 20 BYTE Header Window + + if (BPQNODE) + { + L3MSG->L4DATA[1] = L3LIVES; // Our TTL + L3MSG->LENGTH++; + } + + TNC = LINK->LINKPORT->TNC; + + if (TNC && TNC->NetRomMode) + SendVARANetromMsg(TNC, L3MSG); + else + C_Q_ADD(&LINK->TX_Q, L3MSG); +} + +int FINDCIRCUIT(L3MESSAGEBUFFER * L3MSG, TRANSPORTENTRY ** REQL4, int * NewIndex) +{ + // FIND CIRCUIT FOR AN INCOMING MESSAGE + + TRANSPORTENTRY * L4 = L4TABLE; + TRANSPORTENTRY * FIRSTSPARE = NULL; + struct DEST_LIST * DEST; + + int Index = 0; + + while (Index < MAXCIRCUITS) + { + if (L4->L4USER[0] == 0) // Spare + { + if (FIRSTSPARE == NULL) + { + FIRSTSPARE = L4; + *NewIndex = Index; + } + + L4++; + Index++; + continue; + } + + DEST = L4->L4TARGET.DEST; + + if (DEST == NULL) + { + // L4 entry without a Dest shouldn't happen. (I don't think!) + + char Call1[12], Call2[12]; + + Call1[ConvFromAX25(L4->L4USER, Call1)] = 0; + Call2[ConvFromAX25(L4->L4MYCALL, Call2)] = 0; + + Debugprintf("L4 entry without Target. Type = %02x Calls %s %s", + L4->L4CIRCUITTYPE, Call1, Call2); + + L4++; + Index++; + continue; + } + + if (CompareCalls(L3MSG->L3SRCE, DEST->DEST_CALL)) + { + if (L4->FARID == L3MSG->L4ID && L4->FARINDEX == L3MSG->L4INDEX) + { + // Found it + + *REQL4 = L4; + return TRUE; + } + } + L4++; + Index++; + } + + // ENTRY NOT FOUND - FIRSTSPARE HAS FIRST FREE ENTRY, OR ZERO IF TABLE FULL + + *REQL4 = FIRSTSPARE; + return FALSE; +} + +VOID L3SWAPADDRESSES(L3MESSAGEBUFFER * L3MSG) +{ + // EXCHANGE ORIGIN AND DEST + + char Temp[7]; + + memcpy(Temp, L3MSG->L3SRCE, 7); + memcpy(L3MSG->L3SRCE, L3MSG->L3DEST, 7); + memcpy(L3MSG->L3DEST, Temp, 7); + + L3MSG->L3DEST[6] &= 0x1E; // Mack EOA and CMD + L3MSG->L3SRCE[6] &= 0x1E; + L3MSG->L3SRCE[6] |= 1; // Set Last Call +} + +VOID SendConNAK(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG) +{ + L3MSG->L4FLAGS = L4CACK | L4BUSY; // REJECT + L3MSG->L4DATA[0] = 0; // WINDOW + + L3SWAPADDRESSES(L3MSG); + L3MSG->L3TTL = L3LIVES; + + C_Q_ADD(&LINK->TX_Q, L3MSG); +} + +VOID SETUPNEWCIRCUIT(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, + TRANSPORTENTRY * L4, char * BPQPARAMS, int ApplMask, int * BPQNODE) +{ + struct DEST_LIST * DEST; + int Maxtries = 2; // Just in case + + L4->FARINDEX = L3MSG->L4INDEX; + L4->FARID = L3MSG->L4ID; + + // Index set by caller + + L4->CIRCUITID = NEXTID; + + NEXTID++; + if (NEXTID == 0) + NEXTID++; // kEEP nON-ZERO + + L4->SESSIONT1 = L4T1; + + L4->L4WINDOW = (UCHAR)L4DEFAULTWINDOW; + + if (L3MSG->L4DATA[0] > L4DEFAULTWINDOW) + L4->L4WINDOW = L3MSG->L4DATA[0]; + + memcpy(L4->L4USER, &L3MSG->L4DATA[1], 7); // Originator's call from Call Request + + if (ApplMask) + { + // Should get APPLCALL if set ( maybe ??????????????? + } + +// MOV ESI,APPLCALLTABLEPTR +// LEA ESI,APPLCALL[ESI] + + memcpy(L4->L4MYCALL, MYCALL, 7); + + // GET BPQ EXTENDED CONNECT PARAMS IF PRESENT + + if (L3MSG->LENGTH == MSGHDDRLEN + 38 || L3MSG->LENGTH == MSGHDDRLEN + 39) + { + *BPQNODE = 1; + memcpy(BPQPARAMS, &L3MSG->L4DATA[15],L3MSG->LENGTH - (MSGHDDRLEN + 36)); + } + + L4->L4CIRCUITTYPE = SESSION | UPLINK; + L4->L4STATE = 5; + +TryAgain: + + DEST = CHECKL3TABLES(LINK, L3MSG); + + L4->L4TARGET.DEST = DEST; + + if (DEST == 0) + { + int WorstQual = 256; + struct DEST_LIST * WorstDest = NULL; + int n = MAXDESTS; + + // Node not it table and table full + + // Replace worst quality node with session counts of zero + + // But could have been excluded, so check + + if (CheckExcludeList(L3MSG->L3SRCE) == 0) + return; + + DEST = DESTS; + + while (n--) + { + if (DEST->DEST_COUNT == 0 && DEST->DEST_RTT == 0) // Not used and not INP3 + { + if (DEST->NRROUTE[0].ROUT_QUALITY < WorstQual) + { + WorstQual = DEST->NRROUTE[0].ROUT_QUALITY; + WorstDest = DEST; + } + } + DEST++; + } + + if (WorstDest) + { + REMOVENODE(WorstDest); + if (Maxtries--) + goto TryAgain; // We now have a spare (but protect against loop if something amiss) + } + + // Nothing to delete, so just ignore connect + + return; + } + + if (*BPQNODE) + { + SHORT T1; + + DEST->DEST_STATE |= 0x40; // SET BPQ _NODE BIT + memcpy((char *)&T1, BPQPARAMS, 2); + + if (T1 > 300) + L4->SESSIONT1 = L4T1; + else + L4->SESSIONT1 = T1; + } + else + L4->SESSIONT1 = L4T1; // DEFAULT TIMEOUT + + L4->SESSPACLEN = PACLEN; // DEFAULT +} + + +int CHECKIFBUSYL4(TRANSPORTENTRY * L4) +{ + // RETURN TOP BIT OF AL SET IF SESSION PARTNER IS BUSY + + int Count; + + if (L4->L4CROSSLINK) // CONNECTED? + Count = CountFramesQueuedOnSession(L4->L4CROSSLINK); + else + Count = CountFramesQueuedOnSession(L4); + + if (Count < L4->L4WINDOW) + return 0; + else + return L4BUSY; +} + +VOID FRAMEFORUS(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG, int ApplMask, UCHAR * ApplCall) +{ + // INTERNODE LINK + + TRANSPORTENTRY * L4; + struct DEST_LIST * DEST; + int Opcode; + char Nodename[20]; + char ReplyText[20]; + struct DATAMESSAGE * Msg; + TRANSPORTENTRY * Partner; + UCHAR * ptr1; + int FramesMissing; + L3MESSAGEBUFFER * Saved; + L3MESSAGEBUFFER ** Prev; + char Call[10]; + struct TNCINFO * TNC; + + L4FRAMESRX++; + + Opcode = L3MSG->L4FLAGS & 15; + + switch (Opcode) + { + case 0: + + // OPCODE 0 is used for a variety of functions, using L4INDEX and L4ID as qualifiers + // 0c0c is used for IP + + if (L3MSG->L4ID == 0x0C && L3MSG->L4INDEX == 0x0C) + { + Q_IP_MSG((MESSAGE *)L3MSG); + return; + } + + // 00 01 Seesm to be Netrom Record Route + + if (L3MSG->L4ID == 1 && L3MSG->L4INDEX == 0) + { + NRRecordRoute((UCHAR *)L3MSG, L3MSG->LENGTH); + return; + } + + ReleaseBuffer(L3MSG); + return; + + case L4CREQ: + + CONNECTREQUEST(LINK, L3MSG, ApplMask, ApplCall); + return; + } + + // OTHERS NEED A SESSION + + L4 = &L4TABLE[L3MSG->L4INDEX]; + + if (L4->CIRCUITID!= L3MSG->L4ID) + { + ReleaseBuffer(L3MSG); + return; + } + + if ((L4->L4CIRCUITTYPE & SESSION) == 0) + { + // Not an L4 Session - must be an old connection + + ReleaseBuffer(L3MSG); + return; + } + + // HAVE FOUND CORRECT SESSION ENTRY + + switch (Opcode) + { + case L4CACK: + + // CONNECT ACK + + DEST = L4->L4TARGET.DEST; + + // EXTRACT EXTENDED PARAMS IF PRESENT + + if (L3MSG->LENGTH > MSGHDDRLEN + 22) // Standard Msg + { + DEST->DEST_STATE &= 0x80; + DEST->DEST_STATE |= (L3MSG->L4DATA[1] - L3MSG->L3TTL) + 0x41; // Hops to dest + x40 + } + + Partner = L4->L4CROSSLINK; + + if (L3MSG->L4FLAGS & L4BUSY) + { + // Refused + + CLEARSESSIONENTRY(L4); + if (Partner) + Partner->L4CROSSLINK = NULL; // CLEAR CROSSLINK + + strcpy(ReplyText, "Busy from"); + } + else + { + // Connect OK + + if (L4->L4STATE == 5) + { + // MUST BE REPEAT MSG - DISCARD + + ReleaseBuffer(L3MSG); + return; + } + + L4->FARINDEX = L3MSG->L4TXNO; + L4->FARID = L3MSG->L4RXNO; + + L4->L4STATE = 5; // ACTIVE + L4->L4TIMER = 0; + L4->L4RETRIES = 0; + + L4->L4WINDOW = L3MSG->L4DATA[0]; + + strcpy(ReplyText, "Connected to"); + } + + if (Partner == 0) + { + ReleaseBuffer(L3MSG); + return; + } + + Msg = (PDATAMESSAGE)L3MSG; // reuse input buffer + + Msg->PID = 0xf0; + ptr1 = SetupNodeHeader(Msg); + + Nodename[DecodeNodeName(DEST->DEST_CALL, Nodename)] = 0; // null terminate + + ptr1 += sprintf(ptr1, "%s %s\r", ReplyText, Nodename); + + Msg->LENGTH = (int)(ptr1 - (UCHAR *)Msg); + + C_Q_ADD(&Partner->L4TX_Q, Msg); + + PostDataAvailable(Partner); + return; + + case L4DREQ: + + // DISCONNECT REQUEST + + L3MSG->L4INDEX = L4->FARINDEX; + L3MSG->L4ID = L4->FARID; + + L3MSG->L4FLAGS = L4DACK; + + L3SWAPADDRESSES(L3MSG); // EXCHANGE SOURCE AND DEST + L3MSG->L3TTL = L3LIVES; + + TNC = LINK->LINKPORT->TNC; + + if (TNC && TNC->NetRomMode) + SendVARANetromMsg(TNC, L3MSG); + else + C_Q_ADD(&LINK->TX_Q, L3MSG); + + CloseSessionPartner(L4); // SEND CLOSE TO PARTNER (IF PRESENT) + return; + + case L4DACK: + + CLEARSESSIONENTRY(L4); + ReleaseBuffer(L3MSG); + return; + + case L4INFO: + + //MAKE SURE SESSION IS UP - FIRST I FRAME COULD ARRIVE BEFORE CONNECT ACK + + if (L4->L4STATE == 2) + { + ReleaseBuffer(L3MSG); // SHOULD SAVE - WILL AVOID NEED TO RETRANSMIT + return; + } + + ACKFRAMES(L3MSG, L4, L3MSG->L4RXNO); + + // If DISCPENDING or STATE IS 4, THEN SESSION IS CLOSING - IGNORE ANY I FRAMES + + if ((L4->FLAGS & DISCPENDING) || L4->L4STATE == 4) + { + ReleaseBuffer(L3MSG); + return; + } + + // CHECK RECEIVED SEQUENCE + + FramesMissing = L3MSG->L4TXNO - L4->RXSEQNO; // WHAT WE GOT - WHAT WE WANT + + if (FramesMissing > 128) + FramesMissing -= 256; + + // if NUMBER OF FRAMES MISSING is -VE, THEN IN FACT IT INDICATES A REPEAT + + if (FramesMissing < 0) + { + // FRAME IS A REPEAT + + Call[ConvFromAX25(L3MSG->L3SRCE, Call)] = 0; + Debugprintf("Discarding repeated frame seq %d from %s", L3MSG->L4TXNO, Call); + + L4->L4ACKREQ = 1; + ReleaseBuffer(L3MSG); + return; + } + + if (FramesMissing > 0) + { + // EXPECTED FRAME HAS BEEN MISSED - ASK FOR IT AGAIN, + // AND KEEP THIS FRAME UNTIL MISSING ONE ARRIVES + + L4->NAKBITS |= L4NAK; // SET NAK REQUIRED + + SENDL4IACK(L4); // SEND DATA ACK COMMAND TO ACK OUTSTANDING FRAMES + + // SEE IF WE ALREADY HAVE A COPY OF THIS ONE +/* + Saved = L4->L4RESEQ_Q; + + Call[ConvFromAX25(L3MSG->L3SRCE, Call)] = 0; + Debugprintf("saving seq %d from %s", L3MSG->L4TXNO, Call); + + while (Saved) + { + if (Saved->L4TXNO == L3MSG->L4TXNO) + { + // ALREADY HAVE A COPY - DISCARD IT + + Debugprintf("Already have seq %d - discarding", L3MSG->L4TXNO); + ReleaseBuffer(L3MSG); + return; + } + + Saved = Saved->Next; + } + + C_Q_ADD(&L4->L4RESEQ_Q, L3MSG); // ADD TO CHAIN + return; +*/ + } + + // Frame is OK + +L4INFO_OK: + + if (L3MSG == 0) + { + Debugprintf("Trying to Process NULL L3 Message"); + return; + } + + L4->NAKBITS &= ~L4NAK; // CLEAR MESSAGE LOST STATE + + L4->RXSEQNO++; + + // REMOVE HEADERS, AND QUEUE INFO + + L3MSG->LENGTH -= 20; // L3/L4 Header + + if (L3MSG->LENGTH < (4 + sizeof(void *))) // No PID + { + ReleaseBuffer(L3MSG); + return; + } + + L3MSG->L3PID = 0xF0; // Normal Data PID + + memmove(L3MSG->L3SRCE, L3MSG->L4DATA, L3MSG->LENGTH - (4 + sizeof(void *))); + + REFRESHROUTE(L4); + + L4->L4ACKREQ = L4DELAY; // SEND INFO ACK AFTER L4DELAY (UNLESS I FRAME SENT) + + IFRM150(L4, (PDATAMESSAGE)L3MSG); // CHECK IF SETTING UP AND PASS ON + + // See if anything on reseq Q to process + + if (L4->L4RESEQ_Q == 0) + return; + + Prev = &L4->L4RESEQ_Q; + Saved = L4->L4RESEQ_Q; + + while (Saved) + { + if (Saved->L4TXNO == L4->RXSEQNO) // The one we want + { + // REMOVE IT FROM QUEUE,AND PROCESS IT + + *Prev = Saved->Next; // CHAIN NEXT IN CHAIN TO PREVIOUS + + OLDFRAMES++; // COUNT FOR STATS + + L3MSG = Saved; + Debugprintf("Processing Saved Message %d Address %x", L4->RXSEQNO, L3MSG); + goto L4INFO_OK; + } + + Debugprintf("Message %d %x still on Reseq Queue", Saved->L4TXNO, Saved); + + Prev = &Saved; + Saved = Saved->Next; + } + + return; + + case L4IACK: + + ACKFRAMES(L3MSG, L4, L3MSG->L4RXNO); + REFRESHROUTE(L4); + + // Drop Through + } + + // Unrecognised - Ignore + + ReleaseBuffer(L3MSG); + return; +} + + +VOID ACKFRAMES(L3MESSAGEBUFFER * L3MSG, TRANSPORTENTRY * L4, int NR) +{ + // SEE HOW MANY FRAMES ARE ACKED - IF NEGATIVE, THAN THIS MUST BE A + // DELAYED REPEAT OF AN ACK ALREADY PROCESSED + + int Count = NR - L4->L4WS; + L3MESSAGEBUFFER * Saved; + struct DEST_LIST * DEST; + struct DATAMESSAGE * Msg; + struct DATAMESSAGE * Copy; + int RTT; + + + if (Count < -128) + Count += 256; + + if (Count < 0) + { + // THIS MAY BE A DELAYED REPEAT OF AN ACK ALREADY PROCESSED + + return; // IGNORE COMPLETELY + } + + while (Count > 0) + { + // new ACK + + // FRAME L4WS HAS BEED ACKED - IT SHOULD BE FIRST ON HOLD QUEUE + + Saved = Q_REM((void *)&L4->L4HOLD_Q); + + if (Saved) + ReleaseBuffer(Saved); + + // CHECK RTT SEQUENCE + + if (L4->L4WS == L4->RTT_SEQ) + { + if (L4->RTT_TIMER) + { + // FRAME BEING TIMED HAS BEEN ACKED - UPDATE DEST RTT TIMER + + DEST = L4->L4TARGET.DEST; + + RTT = GetTickCount() - L4->RTT_TIMER; + + if (DEST->DEST_RTT == 0) + DEST->DEST_RTT = RTT; + else + DEST->DEST_RTT = ((DEST->DEST_RTT * 9) + RTT) /10; // 90% Old + New + } + } + + L4->L4WS++; + Count--; + } + + L4->L4TIMER = 0; + L4->L4RETRIES = 0; + + if (NR != L4->TXSEQNO) + { + // Not all Acked + + L4->L4TIMER = L4->SESSIONT1; // RESTART TIMER + } + else + { + if ((L4->FLAGS & DISCPENDING) && L4->L4TX_Q == 0) + { + // All Acked and DISC Pending, so send it + + SENDL4DISC(L4); + return; + } + } + + // SEE IF CHOKE SET + + L4->FLAGS &= ~L4BUSY; + + if (L3MSG->L4FLAGS & L4BUSY) + { + L4->FLAGS |= L3MSG->L4FLAGS & L4BUSY; // Get Busy flag from message + + if ((L3MSG->L4FLAGS & L4NAK) == 0) + return; // Dont send while biust unless NAC received + } + + if (L3MSG->L4FLAGS & L4NAK) + { + // RETRANSMIT REQUESTED MESSAGE - WILL BE FIRST ON HOLD QUEUE + + Msg = L4->L4HOLD_Q; + + if (Msg == 0) + return; + + Copy = GetBuff(); + + if (Copy == 0) + return; + + memcpy(Copy, Msg, Msg->LENGTH); + + DEST = L4->L4TARGET.DEST; + + C_Q_ADD(&DEST->DEST_Q, Copy); + } +} + + + + + + + + + + + + + + + +VOID SENDL4IACK(TRANSPORTENTRY * Session) +{ + // SEND INFO ACK + + PL3MESSAGEBUFFER MSG = (PL3MESSAGEBUFFER)GetBuff(); + struct DEST_LIST * DEST = Session->L4TARGET.DEST; + + if (MSG == NULL) + return; + + MSG->L3PID = 0xCF; // NET MESSAGE + + memcpy(MSG->L3SRCE, Session->L4MYCALL, 7); + memcpy(MSG->L3DEST, DEST->DEST_CALL, 7); + + MSG->L3TTL = L3LIVES; + + MSG->L4INDEX = Session->FARINDEX; + MSG->L4ID = Session->FARID; + + MSG->L4TXNO = 0; + + + MSG->L4RXNO = Session->RXSEQNO; + Session->L4LASTACKED = Session->RXSEQNO; // SAVE LAST NUMBER ACKED + + MSG->L4FLAGS = L4IACK | GETBUSYBIT(Session) | Session->NAKBITS; + + MSG->LENGTH = MSGHDDRLEN + 22; + + C_Q_ADD(&DEST->DEST_Q, (UINT *)MSG); +} + + + + +/* + PUBLIC KILLSESSION +KILLSESSION: + + pushad + push ebx + CALL _CLEARSESSIONENTRY + pop ebx + popad + + JMP L4CONN90 ; REJECT + + PUBLIC CONNECTACK +CONNECTACK: +; +; EXTRACT EXTENDED PARAMS IF PRESENT +; + + CMP BYTE PTR MSGLENGTH[EDI],L4DATA+1 + JE SHORT NOTBPQ + + MOV AL,L4DATA+1[EDI] + SUB AL,L3MONR[EDI] + ADD AL,41H ; HOPS TO DEST + 40H + + MOV ESI,L4TARGET[EBX] + AND DEST_STATE[ESI],80H + OR DEST_STATE[ESI],AL ; SAVE + + PUBLIC NOTBPQ +NOTBPQ: +; +; SEE IF SUCCESS OR FAIL +; + PUSH EDI + + MOV ESI,L4TARGET[EBX] ; ADDR OF LINK/DEST ENTRY + LEA ESI,DEST_CALL[ESI] + + CALL DECODENODENAME ; CONVERT TO ALIAS:CALL + + MOV EDI,OFFSET32 CONACKCALL + MOV ECX,17 + REP MOVSB + + + POP EDI + + TEST L4FLAGS[EDI],L4BUSY + JNZ SHORT L4CONNFAILED + + CMP L4STATE[EBX],5 + JE SHORT CONNACK05 ; MUST BE REPEAT MSG - DISCARD + + MOV AX,WORD PTR L4TXNO[EDI] ; HIS INDEX + MOV WORD PTR FARINDEX[EBX],AX + + MOV L4STATE[EBX],5 ; ACTIVE + MOV L4TIMER[EBX],0 ; CANCEL TIMER + MOV L4RETRIES[EBX],0 ; CLEAR RETRY COUNT + + MOV AL,L4DATA[EDI] ; WINDOW + MOV L4WINDOW[EBX],AL ; SET WINDOW + + MOV EDX,L4CROSSLINK[EBX] ; POINT TO PARTNER +; + MOV ESI,OFFSET32 CONNECTEDMSG + MOV ECX,LCONNECTEDMSG + + JMP SHORT L4CONNCOMM + + PUBLIC L4CONNFAILED +L4CONNFAILED: +; + MOV EDX,L4CROSSLINK[EBX] ; SAVE PARTNER + pushad + push ebx + CALL _CLEARSESSIONENTRY + pop ebx + popad + + PUSH EBX + + MOV EBX,EDX + MOV L4CROSSLINK[EBX],0 ; CLEAR CROSSLINK + POP EBX + + MOV ESI,OFFSET32 BUSYMSG ; ?? BUSY + MOV ECX,LBUSYMSG + + PUBLIC L4CONNCOMM +L4CONNCOMM: + + OR EDX,EDX + JNZ SHORT L4CONNOK10 +; +; CROSSLINK HAS GONE?? - JUST CHUCK MESSAGE +; + PUBLIC CONNACK05 +CONNACK05: + + JMP L4DISCARD + + PUBLIC L4CONNOK10 +L4CONNOK10: + + PUSH EBX + PUSH ESI + PUSH ECX + + MOV EDI,_BUFFER + + ADD EDI,7 + MOV AL,0F0H + STOSB ; PID + + CALL _SETUPNODEHEADER ; PUT IN _NODE ID + + + POP ECX + POP ESI + REP MOVSB + + MOV ESI,OFFSET32 CONACKCALL + MOV ECX,17 ; MAX LENGTH ALIAS:CALL + REP MOVSB + + MOV AL,0DH + STOSB + + MOV ECX,EDI + MOV EDI,_BUFFER + SUB ECX,EDI + + MOV MSGLENGTH[EDI],CX + + MOV EBX,EDX ; CALLER'S SESSION + + LEA ESI,L4TX_Q[EBX] + CALL _Q_ADD ; SEND MESSAGE TO CALLER + + CALL _POSTDATAAVAIL + + POP EBX ; ORIGINAL CIRCUIT TABLE + RET + + + PUBLIC SENDCONNECTREPLY +SENDCONNECTREPLY: +; +; LINK SETUP COMPLETE - EBX = LINK, EDI = _BUFFER +; + CMP LINKTYPE[EBX],3 + JNE SHORT CONNECTED00 +; +; _NODE - _NODE SESSION SET UP - DONT NEED TO DO ANYTHING (I THINK!) +; + CALL RELBUFF + RET + +; +; UP/DOWN LINK +; + PUBLIC CONNECTED00 +CONNECTED00: + CMP CIRCUITPOINTER[EBX],0 + JNE SHORT CONNECTED01 + + CALL RELBUFF ; UP/DOWN WITH NO SESSION - NOONE TO TELL + RET ; NO CROSS LINK + PUBLIC CONNECTED01 +CONNECTED01: + MOV _BUFFER,EDI + PUSH EBX + PUSH ESI + PUSH ECX + + ADD EDI,7 + MOV AL,0F0H + STOSB ; PID + + CALL _SETUPNODEHEADER ; PUT IN _NODE ID + + LEA ESI,LINKCALL[EBX] + + PUSH EDI + CALL CONVFROMAX25 ; ADDR OF CALLED STATION + POP EDI + + MOV EBX,CIRCUITPOINTER[EBX] + + MOV L4STATE[EBX],5 ; SET LINK UP + + MOV EBX,L4CROSSLINK[EBX] ; TO INCOMING LINK + cmp ebx,0 + jne xxx +; +; NO LINK ??? +; + MOV EDI,_BUFFER + CALL RELBUFF + + POP ECX + POP ESI + POP EBX + + RET + + PUBLIC xxx +xxx: + + POP ECX + POP ESI + REP MOVSB + + MOV ESI,OFFSET32 _NORMCALL + MOVZX ECX,_NORMLEN + REP MOVSB + + MOV AL,0DH + STOSB + + MOV ECX,EDI + MOV EDI,_BUFFER + SUB ECX,EDI + + MOV MSGLENGTH[EDI],CX + + LEA ESI,L4TX_Q[EBX] + CALL _Q_ADD ; SEND MESSAGE TO CALLER + + CALL _POSTDATAAVAIL + + POP EBX + RET +*/ \ No newline at end of file diff --git a/LinBPQ.c b/LinBPQ.c index eba6b31..d25dfb7 100644 --- a/LinBPQ.c +++ b/LinBPQ.c @@ -76,6 +76,7 @@ void SaveAIS(); void initAIS(); void DRATSPoll(); VOID GetPGConfig(); +void SendBBSDataToPktMap(); extern uint64_t timeLoadedMS; @@ -1281,6 +1282,10 @@ int main(int argc, char * argv[]) printf("Mail Started\n"); Logprintf(LOG_BBS, NULL, '!', "Mail Starting"); + APIClock = 0; + + SendBBSDataToPktMap(); + } } @@ -1579,6 +1584,13 @@ int main(int argc, char * argv[]) DoHouseKeeping(FALSE); } + if (APIClock < NOW) + { + SendBBSDataToPktMap(); + APIClock = NOW + 7200; // Every 2 hours + } + + tm = gmtime(&NOW); if (tm->tm_wday == 0) // Sunday diff --git a/MailDataDefs.c b/MailDataDefs.c index 883963a..abfbac2 100644 --- a/MailDataDefs.c +++ b/MailDataDefs.c @@ -205,6 +205,7 @@ int MailForInterval = 0; char zeros[NBMASK]; // For forward bitmask tests time_t MaintClock; // Time to run housekeeping +time_t APIClock; // Time to sent to MOLTE's Database struct MsgInfo * MsgnotoMsg[100000]; // Message Number to Message Slot List. diff --git a/MailNode.vcproj.LAPTOP-Q6S4RP5Q.johnw.user b/MailNode.vcproj.LAPTOP-Q6S4RP5Q.johnw.user new file mode 100644 index 0000000..87b9e21 --- /dev/null +++ b/MailNode.vcproj.LAPTOP-Q6S4RP5Q.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/MailNode.vcproj.SKIGACER.johnw.user b/MailNode.vcproj.SKIGACER.johnw.user new file mode 100644 index 0000000..bbece07 --- /dev/null +++ b/MailNode.vcproj.SKIGACER.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/MailTCP.c b/MailTCP.c index 49478e1..91ddc3f 100644 --- a/MailTCP.c +++ b/MailTCP.c @@ -2897,6 +2897,8 @@ SocketConn * SMTPConnect(char * Host, int Port, BOOL AMPR, struct MsgInfo * Msg, sinx.sin_addr.s_addr = INADDR_ANY; sinx.sin_port = 0; + sockptr->Timeout = 0; + if (bind(sockptr->socket, (LPSOCKADDR) &sinx, addrlen) != 0 ) { // @@ -3590,7 +3592,6 @@ VOID ProcessPOP3ClientMessage(SocketConn * sockptr, char * Buffer, int Len) if (sockptr->POP3MsgCount > sockptr->POP3MsgNum++) { sockprintf(sockptr, "RETR %d", sockptr->POP3MsgNum); - sockptr->State = WaitingForRETRResponse; } else diff --git a/Versions.h b/Versions.h index 5f0f730..df74f46 100644 --- a/Versions.h +++ b/Versions.h @@ -10,14 +10,14 @@ #endif -#define KVers 6,0,24,50 -#define KVerstring "6.0.24.50\0" +#define KVers 6,0,24,51 +#define KVerstring "6.0.24.51\0" #ifdef CKernel #define Vers KVers #define Verstring KVerstring -#define Datestring "October 2024" +#define Datestring "November 2024" #define VerComments "G8BPQ Packet Switch (C Version)" KVerstring #define VerCopyright "Copyright © 2001-2024 John Wiseman G8BPQ\0" #define VerDesc "BPQ32 Switch\0" diff --git a/WinmorControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user b/WinmorControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user new file mode 100644 index 0000000..bed4096 --- /dev/null +++ b/WinmorControl.vcproj.LAPTOP-Q6S4RP5Q.johnw.user @@ -0,0 +1,65 @@ + + + + + + + + + + + diff --git a/adif.c b/adif.c index 4e221e6..040942e 100644 --- a/adif.c +++ b/adif.c @@ -609,7 +609,6 @@ VOID ADIFWriteFreqList() fprintf(Handle, "[Channels]\r\n"); - for (i = 0; i < freqCount; i++) fprintf(Handle, "Frequency %d=%lld\r\n" , i + 1, Freqs[i]); diff --git a/bpqmail.h b/bpqmail.h index 4ee6628..78a1831 100644 --- a/bpqmail.h +++ b/bpqmail.h @@ -1191,6 +1191,7 @@ BOOL FBBDoForward(CIRCUIT * conn); BOOL FindMessagestoForward(CIRCUIT * conn); BOOL SeeifMessagestoForward(int BBSNumber, CIRCUIT * Conn); int CountMessagestoForward(struct UserInfo * user); +int CountBytestoForward(struct UserInfo * user); VOID * GetMultiLineDialogParam(HWND hDialog, int DLGItem); @@ -1633,6 +1634,8 @@ extern char ** SendWPAddrs; // Replacers WP To and VIA extern BOOL DontCheckFromCall; +extern time_t APIClock;; + // YAPP stuff #define SOH 1 diff --git a/cMain.c b/cMain.c index b63a69c..4f9dc9b 100644 --- a/cMain.c +++ b/cMain.c @@ -50,6 +50,7 @@ VOID SendSmartID(struct PORTCONTROL * PORT); int CanPortDigi(int Port); int KissEncode(UCHAR * inbuff, UCHAR * outbuff, int len); void MQTTTimer(); +void SaveMH(); #include "configstructs.h" @@ -1507,7 +1508,7 @@ BOOL Start() upnpInit(); - CurrentSecs = lastSlowSecs = time(NULL); + lastSaveSecs = CurrentSecs = lastSlowSecs = time(NULL); return 0; } @@ -2105,7 +2106,19 @@ VOID TIMERINTERRUPT() } */ } - + + // Check Autosave Nodes and MH timer + + if (CurrentSecs - lastSaveSecs >= 3600) // 1 per hour + { + lastSaveSecs = CurrentSecs; + + if (AUTOSAVE == 1) + SaveNodes(); + if (AUTOSAVEMH == 1) + SaveMH(); + } + if (L4TIMERFLAG >= 10) // 1 PER SEC { L4TIMERFLAG -= 10; diff --git a/config.c b/config.c index 6408e25..818e24d 100644 --- a/config.c +++ b/config.c @@ -174,11 +174,16 @@ extern BOOL Loopflag; extern char NodeMapServer[80]; extern char ChatMapServer[80]; +double LatFromLOC; +double LonFromLOC; + + VOID * zalloc(int len); int WritetoConsoleLocal(char * buff); char * stristr (char *ch1, char *ch2); +int FromLOC(char * Locator, double * pLat, double * pLon); VOID Consoleprintf(const char * format, ...) { @@ -342,7 +347,7 @@ static int routine[] = 14, 14, 14, 14, 14, 14 ,14, 14, 15, 0, 2, 9, 9, -2, 2, 2, 2, 2, 2, +2, 2, 1, 2, 2, 2, 2, 2, 0, 1, 20, 20} ; // Routine to process param int PARAMLIM = sizeof(routine)/sizeof(int); @@ -924,11 +929,21 @@ NextAPRS: strcat(LOCATOR, ":"); strcat(LOCATOR, ptr2); ToLOC(atof(ptr1), atof(ptr2), LOC); + LatFromLOC = atof(ptr1); + LonFromLOC = atof(ptr2); + } else { if (strlen(ptr1) == 6) + { strcpy(LOC, ptr1); + FromLOC(LOC, &LatFromLOC, &LonFromLOC); + // Randomise in square + LatFromLOC += ((rand() / 24.0) / RAND_MAX); + LonFromLOC += ((rand() / 12.0) / RAND_MAX); + + } } } return 0; diff --git a/datadefs.c b/datadefs.c index 44d1fd0..f68c1c4 100644 --- a/datadefs.c +++ b/datadefs.c @@ -49,6 +49,9 @@ char MAPCOMMENT[250] = ""; char LOC[7] = ""; // Must be in shared mem// Maidenhead Locator for Reporting char ReportDest[7]; +double LatFromLOC = 0; +double LonFromLOC = 0; + UCHAR BPQDirectory[260] = "."; UCHAR ConfigDirectory[260] = "."; UCHAR LogDirectory[260] = ""; @@ -62,6 +65,7 @@ UCHAR L3KEEP[7] = {'K'+'K','E'+'E','E'+'E','P'+'P','L'+'L','I'+'I', 0xe0}; // K time_t CurrentSecs; time_t lastSlowSecs; +time_t lastSaveSecs; char WL2KCall[10] = ""; char WL2KLoc[7] = ""; diff --git a/lzhuf32.c b/lzhuf32.c index 3482b30..a6828ca 100644 --- a/lzhuf32.c +++ b/lzhuf32.c @@ -768,6 +768,9 @@ BOOL CheckifPacket(char * Via) if (FindContinent(ptr1)) return TRUE; // Packet + if (FindCountry(ptr1)) + return TRUE; // Packet + if ((_stricmp(ptr1, "MARS") == 0) || (_stricmp(ptr1, "USA") == 0)) // MARS used both return TRUE; // Packet diff --git a/mailapi.c b/mailapi.c index f447e7b..fad0897 100644 --- a/mailapi.c +++ b/mailapi.c @@ -705,4 +705,391 @@ packetmail_queue_length{partner="GB7NOT"} 0 1729090716916 packetmail_queue_length{partner="GB7NWL"} 0 1729090716916 packetmail_queue_length{partner="GM8BPQ"} 0 1729090716916 -*/ \ No newline at end of file +*/ + + +// Stuff send to packetnodes.spots.radio/api/bbsdata/{bbsCall} +//https://nodes.ukpacketradio.network/swagger/index.html + + +/* +BbsData{ +callsign* [...] +time* [...] +hroute* [...] +peers [...] +software* [...] +version* [...] +mailQueues [...] +messages [...] +latitude [...] +longitude [...] +locator [...] +location [...] +unroutable [...] +} + +[ + +{ + "callsign": "GE8PZT", + "time": "2024-11-25T10:07:41+00:00", + "hroute": ".#24.GBR.EU", + "peers": [ + "GB7BBS", + "VE2PKT", + "GB7NXT", + "VA2OM" + ], + "software": "XrLin", + "version": "504a", + "mailQueues": [], + "messages": [ + { + "to": "TECH@WW", + "mid": "20539_GB7CIP", + "rcvd": "2024-11-24T09:27:59+00:00", + "routing": [ + "R:241124/0927Z @:GE8PZT.#24.GBR.EU [Lamanva] #:2315 XrLin504a", + + + { + "to": "TNC@WW", + "mid": "37_PA2SNK", + "rcvd": "2024-11-18T21:56:55+00:00", + "routing": [ + "R:241118/2156Z @:GE8PZT.#24.GBR.EU [] #:2215 XrLin504a", + "R:241118/2156Z 12456@VE2PKT.#TRV.QC.CAN.NOAM BPQ6.0.24", + "R:241118/2130Z 51539@VE3KPG.#ECON.ON.CAN.NOAM BPQK6.0.23", + "R:241118/2130Z 26087@VE3CGR.#SCON.ON.CAN.NOAM LinBPQ6.0.24", + "R:241118/2130Z 37521@PA8F.#ZH1.NLD.EURO LinBPQ6.0.24", + "R:241118/2129Z 48377@PI8LAP.#ZLD.NLD.EURO LinBPQ6.0.24", + "R:241118/2129Z @:PD0LPM.FRL.EURO.NLD #:33044 [Joure] $:37_PA2SNK" + ] + } + ], + "latitude": 50.145832, + "longitude": -5.125, + "locator": "IO70KD", + "location": "Lamanva", + "unroutable": [ + { + "type": "P", + "at": "WW" + }, + { + "type": "P", + "at": "G8PZT-2" + }, + { + "type": "P", + "at": "g8pzt._24.gbr.eu" + }, + { + "type": "P", + "at": "G8PZT.#24.GBR.EU" + }, + { + "type": "P", + "at": "GE8PZT.#24.GBR.EU" + }, + { + "type": "P", + "at": "G8PZT.#24.GBR.EURO" + } + ] + }, + +*/ + + +// https://packetnodes.spots.radio/swagger/index.html + +// "unroutable": [{"type": "P","at": "WW"}, {"type": "P", "at": "G8PZT.#24.GBR.EURO"}] + +char * ViaList[100000]; // Pointers to the Message Header field +char TypeList[100000]; + +int unroutableCount = 0; + + +void CheckifRoutable(struct MsgInfo * Msg) +{ + char NextBBS[64]; + int n; + + if (Msg->status == 'K') + return; + + if (Msg->via[0] == 0) // No routing + return; + + strcpy(NextBBS, Msg->via); + strlop(NextBBS, '.'); + + if (strcmp(NextBBS, BBSName) == 0) // via this BBS + return; + + if ((memcmp(Msg->fbbs, zeros, NBMASK) != 0) || (memcmp(Msg->forw, zeros, NBMASK) != 0)) // Has Forwarding Info + return; + + // See if we already have it + + for (n = 0; n < unroutableCount; n++) + { + if ((TypeList[n] == Msg->type) && strcmp(ViaList[n], Msg->via) == 0) + return; + + } + + // Add to list + + TypeList[unroutableCount] = Msg->type; + ViaList[unroutableCount] = Msg->via; + + unroutableCount++; +} + + +extern char LOC[7]; + + +DllExport VOID WINAPI SendWebRequest(char * Host, char * Request, char * Params, char * Return); + +#ifdef LINBPQ +extern double LatFromLOC; +extern double LonFromLOC; +#else +typedef int (WINAPI FAR *FARPROCX)(); +extern FARPROCX pSendWebRequest; +extern FARPROCX pGetLatLon; +double LatFromLOC = 0; +double LonFromLOC = 0; +#endif + +void SendBBSDataToPktMap() +{ + char Return[4096]; + char Request[64]; + char Params[50000]; + char * ptr = Params; + struct MsgInfo * Msg; + + struct UserInfo * ourBBSRec = LookupCall(BBSName); + struct UserInfo * USER; + char Time[64]; + struct tm * tm; + time_t Date = time(NULL); + char Peers[2048] = "[]"; + char MsgQueues[16000] = "[]"; + char * Messages = malloc(1000000); + char * Unroutables; + int m; + char * MsgBytes; + char * Rlineptr; + char * Rlineend; + char * RLines; + char * ptr1, * ptr2; + int n; + +#ifndef LINBPQ + if (pSendWebRequest == 0) + return; // Old Version of bpq32.dll + + pGetLatLon(&LatFromLOC, &LonFromLOC); + +#endif + if (ourBBSRec == 0) + return; // Wot!! + + // Get peers and Mail Queues + + ptr = &Peers[1]; + ptr1 = &MsgQueues[1]; + + for (USER = BBSChain; USER; USER = USER->BBSNext) + { + if (strcmp(USER->Call, BBSName) != 0) + { + int Bytes; + + int Count = CountMessagestoForward(USER); + + ptr += sprintf(ptr, "\"%s\",", USER->Call); + + if (Count) + { + Bytes = CountBytestoForward(USER); + + ptr1 += sprintf(ptr1, "{\"peerCall\": \"%s\", \"numQueued\": %d, \"bytesQueued\": %d},", + USER->Call, Count, Bytes); + } + } + } + + if ((*ptr) != ']') // Have some entries + { + ptr--; // over trailing comms + *(ptr++) = ']'; + *(ptr) = 0; + } + + if ((*ptr1) != ']') // Have some entries + { + ptr1--; // over trailing comms + *(ptr1++) = ']'; + *(ptr1) = 0; + } + + // Get Messages + + strcpy(Messages, "[]"); + ptr = &Messages[1]; + + for (m = LatestMsg; m >= 1; m--) + { + if (ptr > &Messages[999000]) + break; // protect buffer + + Msg = GetMsgFromNumber(m); + + if (Msg == 0 || Msg->type == 0 || Msg->status == 0) + continue; // Protect against corrupt messages + + // Paula suggests including H and K but limit it to the last 30 days or the last 100 messages, whichever is the smaller. + +// if (Msg->status == 'K' || Msg->status == 'H') +// continue; + + if ((Date - Msg->datereceived) > 30 * 86400) // Too old + continue; + + CheckifRoutable(Msg); + + tm = gmtime(&Msg->datereceived); + + sprintf(Time, "%04d-%02d-%02dT%02d:%02d:%02d+00:00", + tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + // Get Routing + + MsgBytes = ReadMessageFile(Msg->number); + RLines = malloc(Msg->length * 2); // Very unlikely to need so much but better safe.. + + strcpy(RLines, "[]"); + + ptr2 = &RLines[1]; + + // Need to skip B2 header if B2 Message + + Rlineptr = MsgBytes; + + // If it is a B2 Message, Must Skip B2 Header + + if (Msg->B2Flags & B2Msg) + { + Rlineptr = strstr(Rlineptr, "\r\n\r\n"); + if (Rlineptr) + Rlineptr += 4; + else + Rlineptr = MsgBytes; + } + + // We have to process R: lines one at a time as we need to send each one as a separate string + + while (memcmp(Rlineptr, "R:", 2) == 0) + { + // Have R Lines + + Rlineend = strstr(Rlineptr, "\r\n"); + Rlineend[0] = 0; + ptr2 += sprintf(ptr2, "\"%s\",", Rlineptr); + + Rlineptr = Rlineend + 2; // over crlf + } + + if ((*ptr2) == ']') // no entries + continue; + + ptr2--; // over trailing comms + *(ptr2++) = ']'; + *(ptr2) = 0; + + ptr += sprintf(ptr, "{\"to\": \"%s\", \"mid\": \"%s\", \"rcvd\": \"%s\", \"routing\": %s},", + Msg->to, Msg->bid, Time, RLines); + + free(MsgBytes); + free(RLines); + + } + + if ((*ptr) != ']') // Have some entries? + { + ptr--; // over trailing comms + *(ptr++) = ']'; + *(ptr) = 0; + } + + // Get unroutables + + Unroutables = malloc((unroutableCount + 1) * 100); + + strcpy(Unroutables, "[]"); + ptr = &Unroutables[1]; + + + for (n = 0; n < unroutableCount; n++) + { + ptr += sprintf(ptr, "{\"type\": \"%c\",\"at\": \"%s\"},", TypeList[n], ViaList[n]); + } + + if ((*ptr) != ']') // Have some entries? + { + ptr--; // over trailing comms + *(ptr++) = ']'; + *(ptr) = 0; + } + + + + /* +char * ViaList[100000]; // Pointers to the Message Header field +char TypeList[100000]; + +int unroutableCount = 0; + "unroutable": [{"type": "P","at": "WW"}, {"type": "P", "at": "G8PZT.#24.GBR.EURO"}] + */ + + + tm = gmtime(&Date); + + sprintf(Time, "%04d-%02d-%02dT%02d:%02d:%02d+00:00", + tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + + ptr = Params; + + sprintf(Request, "/api/bbsdata/%s", BBSName); + + ptr += sprintf(ptr, "{\"callsign\": \"%s\",\r\n", BBSName); + ptr += sprintf(ptr, "\"time\": \"%s\",\r\n", Time); + ptr += sprintf(ptr, "\"hroute\": \"%s\",\r\n", HRoute); + ptr += sprintf(ptr, "\"peers\": %s,\r\n", Peers); +#ifdef LINBPQ + ptr += sprintf(ptr, "\"software\": \"%s\",\r\n", "linbpq"); +#else + ptr += sprintf(ptr, "\"software\": \"%s\",\r\n", "BPQMail"); +#endif + ptr += sprintf(ptr, "\"version\": \"%s\",\r\n", VersionString); + ptr += sprintf(ptr, "\"mailQueues\": %s,\r\n", MsgQueues); + ptr += sprintf(ptr, "\"messages\": %s,\r\n", Messages); + ptr += sprintf(ptr, "\"latitude\": %1.6f,\r\n", LatFromLOC); + ptr += sprintf(ptr, "\"longitude\": %.6f,\r\n", LonFromLOC); + ptr += sprintf(ptr, "\"locator\": \"%s\",\r\n", LOC); + ptr += sprintf(ptr, "\"location\": \"%s\",\r\n", ourBBSRec->Address); + ptr += sprintf(ptr, "\"unroutable\": %s\r\n}\r\n", Unroutables); + + SendWebRequest("packetnodes.spots.radio", Request, Params, Return); + free(Messages); + free(Unroutables); +} diff --git a/xpaho-mqtt3a.dll b/xpaho-mqtt3a.dll deleted file mode 100644 index e978f04063d94c5b8a2a5bf5c1c06b0efc18ac98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101376 zcmeFaePC3@)jz(QY?1{R?jj4W5_PSs#1b`_sEG-6L&7F%fDIupDg;`oT_b9Q-4JUG z!Mh7AmsQ%@`tqpNNBcask59!`DQeY(APb0!SYPT(#kSfT11(yML96%o`OMtAyNR@Y zzQ5=D=NHZ1d*{xaIdkUBnKS2{nLGEgO}1Q{&E~+LVc2Y2apk{&`280@cAIVD$xlwS zJvQ!zQ?}Y0UN~i8_^Oq@&K1{vXT>+K@m=xFYp=a7;``ROeJi5Z`mVaxS3AGScg=On zzJ1n&3FG}H=$9Kle*K4M-W{|4?rv6M>+qavyC;6TxZWARS6uIi|5#jq9KQ|MAD($n z>;qhbOYRZx7qs0GPvUt&+uh>*g0?GS%(tNJUip006=Bvfwnd>iHd})|&$e&;ncueF z?YHHea*TcAiMF%Qz;DUa@1KKvH|_(t@{ad8Hd{U}c2JO4@vk56Nlg695t)&ZdHEx* z^0N0@yG=)-tx>xKxyf$ZFF-SI*1!Agwqlg~V~5>Vn3epM+p7U5J27Cl{qbn<%sgjB zzTF+ceXtmrh!@*x;%$NP*=$SCTCwb#k#E{;hb}}aV2Vmei>!YEq@E?S*&cJ@MIYXi zAS=F-um7wSD_2~BSNr)2Z3dBSw(hUw3!Js$+sm&*Mz-B{AKo8}>vzABZ#Gi@-@pF_ z1+-efQ`K#eV^!TAZP33^z>=i$HkVE9@w;r1^7|X&c6W<*?!tiG9q*6ihK$$lFWi{9 zuf=OCJ&?I?x4`Tf^-n$EZfb7PB7T4ATk*ru@$HL~HJ@FsZg&s1YO@}!w!1qVVSf!O zG+tM?yW;&y{FnlpP3?6!m34O9X+aMvTaH}C$Q4TGdLR6z>s04!jJ|w9( zcyd}xVD%xF< zasvJ+oo6J!=k$v9wy39~Jsedk+LuMgSF|sR=2f&WjRYUpmTnY zOA9Vb)HP_qaH1}x1w+*kDfjRZB1ERX!d4CxanD0d9^R|XN!_3B1 zku6tdi@z0_Xx4ZCRiZv^J`i>$suvkuPW=VQG$eqOTF_Nm?N;~OXI8sK7I&h08QPbN z&gjY0<^_gxN_QpJvsbjBLmhGSoTLS76YE^4O$*j1*0EYG*pOJ~5jV(E0Mf9s<%w$0 z1qsfc^R!?mJ1wODK|}VPthmmVSpTp98{isGGMe=E^#8ynY{j2Wa#i1uck4m565>Yu~nGbwHA+d zu?ze^UHz`Z4i31uFj^?TLb_3@`y?p61o6zaVU?N^)_$jduA?ni^n$TqZ!_)0HNe48OuYf9R^CZw` znMd>-sP18jn>GN3?B9#R2|N1J7MZN}7TF|I|JCAumaccicSR;Qnq+jC?cSq(0)n{k}UIid?B9JTW4RmHI!1)Zc=a?)C>0czWO8`>g7-}Tyox`<2E3fApYVSgb zPr)&vM70wD+09v9gicXnzhO_xS6vH@PM7}bX?BbU$!Y%9MD^0tItTLLf1qIv!@p4| zZg-%foSwN_usq)18%(V85>~K$V1G`?conY`>k5%Veu1`OD}Ae?y}b8iEm&og-JzGJ zTOu+gTzAw|wwL$3r>zT!GLch5`jXF}6#k0OvX_ikFi=w>1F15cZ!+<^T3KXTNG}#e z_-tn8E&l;kNUPt9yG1r7)``0gw~3~#Qi-LI$Upo_ZZtX6?1p5m-)(d)3h7%u1xmNF zRL3O6ZBr)Mk*sh2oJ8SdG__+A6D|ZYf?J%4H4g!(+Pl;isZZBxV%_{NYg`)AYekJL z0TRe6MbEb7_6sqS12NMPM(@4>y2sy=BKsbT9XrAuwkF-~x7!joLYNjOYA|3*5;abW z+8Qu-I8oz14 z+0$lVY90GWMH4XmgM0R1@@+Nc=On<#{8UJU4JGz%%8Et)!uVTC>{8q}>7DCY3PL98 zOm=@hbP+Q=pW%1RnG-7-{64(B;me^JcpLs?w%y&TPdF9B`be4K=$i}goAg^*tPBxsudG2l*c_t_y@9J=>$ zpnl&Pn_IsH%b)P3Ufe}^=e`$rm*EavrSJR5W_!R7So%)h72z(GN7(GFa6NvFDHPAz zA<_)0&9m(Op~-AQt=}VZbl6%FCHj$5?6z!@KnIBCsj2+onPcd})Pmu$#EFhqQ+cVN zll8XhSI0#sCGtVnq<$t)6E!8)euCF6#<>FH^ZpDZW)ZtET4STuxwnu+f-h$nV5^MTcJ4MXkj#N7wtZaMDW7I!_k+sC`DeKPBFd?-iiuW^&=;{?rrskII^82`WwvpW37 z>q2rTZm}Vmf;5TjiaQswKylZ&l}VUFYJ7=mpL(Oi)(c4u{%|MO5FCdGkmDt9Bc4P0 z)NMd}gv@C~gT{GAmrJi1lu1q`m1=86E&UO%+6{?&Y(;lrbOLit_?Dpl@T3-f+hu@^ zq}{ui&<45P}XHVtt4d?LpfhqhwSc3YtuvYzM_Bpy>yHA@%RRjDH z*6MxDMoFT<@6}6HftjZ3Kkb)@H=Zr8Ph122jo6!rC;mbl$fOfPp^iNLj4iSulR-dF zGcdAupIh{Dv_xA==`Ey71k=XfpuiYgm_z{1lz59) zARlEepdr&){2z=T_H>B4rT&lR@ha*K0H?@G+VoZ^O4TA?FCuoWsdvAdd%nL1!})9q#vSGDu51POkpPDe0e4AnC@-1C6wQC$`xp} zmKqh$aL(`bVKIY|hkx2yPWeXJR^ydAP`6UN?-`QGskO|_f3kZpojaDKL5Z8Q zqV!9LSn5|Ci)U)bts4P zwd&4_u&cK&1ONI?eTkJVa(0^Dgb$ET{12$2WoSpx=tnAU>YPl)LW)6Fkj_)r@ z^WB@cB{`p?2)J|%yX;Bjg+R^*aNbtlr1BQxcM%>_d2P7cDDD>Ft_|~RDsL(7ZWC$C zaCe`$3*!!yvZeBtn|E223>||v8Ft*G&Z6Y8DRCX?SoJT$s-Hp!V&}C7_zJ{7!I;v9 zWbIH(cqwYtw*ov{u!G!hW+k_b&f3GQ$+1}hcgj)#dw^Dk(FN{cfkk5r01at<$VTH~ zW}SJIthHZ(+Rv={nXIM#?StVrkO)~}*XKSeMr~=oMWV(gQy7=Nm009=>2IHf0RXv2 z$#pn-oRl5v^M*e8a+Y*xgYD?VR`7w#8VYB8h$20HA4mkIECNSjnc=MHY{Oct+Y`Am zJMR}mf|MY{8FW1)>h%l^u*dCToP+3PEu<*Q83l4 zd-J6dbxf9n(6yzug;^WwYjmp2lkC1nPmIde0Uc>Q#9m@mo4VblxPt*@QUNX@Wm26R z*QL1naP7pkL~*a}Lw&dn{8i~)y9xJKDzDKBVj6I4(SlT66_RZoZLRvris&tE#6(cZ zs@Wuodceaey)aelrx>#d+gDX9mF&~08P*6TY#MG42i%V6Ihx8AqyL4G<_ic6X#Ji+ zwcBZn98{kY#EZIw7V7|dL8zju?F(o@! zi9uJfaShd~KRp#1Y6BN7uEdhIQ~@XT#C#7hm_sX29oAK@L><;u-UL?j!NNox)>ZCA zoi_pFU40X$U(%9T zRVA0Obpd2O$yimcoh#<<*IJF&FyKXY3U*SLia~iu|6~f9Q0uRy)veZF-{EM5d zjlseKjFV`DmC9>2V``?F3?1y5L|Hd~Gwu6+QFi^ssMo5~C+jShwH29l{9<96RGB*N z$s~?#-#PyZzSSBN{(=ms4E$M{#If-AP5&48`z8JsjXrKV3jXh8634<{@vq>QN%$ou z{@>4l%HV%fCUGqMV@JU+LI1na|3z8-Kmwufedublj3_n}R-*`pUgKu{nfBYMEWIaCifgQ<)|F)*{?147d7i2 zj1xIfrU-pMox=XGHJOf*{Cvnv=5t({a9F5E@_O9r=l0%XV9c$2BSHo$#1vRVuLRzm zh^n)v-x&mxo?kIBn6s~bl-YNzVkKIhp7i7-T&{>oxQvtVb!}q$Rbrn|1pmt^c#bs} zDH{h-ATtgB;XRT>npW$IpnU~PsCw1Ho(*Rd8mrv$eHL(BF3j8M)nHwS9n}izsl8RU zXb_5)XKrKD{L)>x^P+@Jdj-&tdLMCfxkHAPpeE$>I(!=1O8o@yg;Gh9;L&>IO@uNmrk^aN$fw%Nb zR3%)SY2S*Vh&Df<&9QOyY89*eYtJ5N5c+Mcw6KMiiIVt1C3XkYK5dQ|4@uk#JvDngxCb#FibQLU-vqphjk3*p z*$TnoV8l~{z)^43KNbSPfmZs#t|yQsO`dlPZ71#Dj2qA<(|iJLay6VhE_JogCRdNq zCf$R0O@3Z2w8_ zKD{73I9?b{wYtJY5bh}$P`zONEKI%0I-yj;8#v|N3^xahk`BQliT3g=Ge)qAjBqU& zx6}Y5d`o5hu+ELwLvpM&iiF9y> zuenwKJ-pZ;7}d;iiQsN6WKmwM_UDrh#W`4)-XaB(%cSx43fZ!X<BzSa6l4?)u3e} z$bS<}J|xDaX+Qe}o-&L}2=yusL@Z*20c|pESQAr$a&#DcWC32-0u*&xz@89AomeF7 zUeZ3+pd{L>3FiRD<^nX=31ld`Y>gqk*DTr}DOXPJVBxwx2$$mJhTywRATSrSN%qA^dxPKTz^MAVg)?0BJ3lyS|zp(Sz2`0iNwi) zyh{A1fR8SYKSVkO+n($J-l^bHq*8xKPt{s*QE2OWyi?|G3x)e|1Je0AjtS|Hh^3ZC z9XV#jaTy&+-+3?Va1UJsNa!F>w#zM9Z8v3zwlldsVZIu198=|qTG6;i~dSPzlcowyovDhjWeRa4C-7Y$!^*4fG00%nB zE{Ke4)ot~Hwyk>Wad34A|MBo_L!dPr&}OlycCz+7R%<6}jZQ5GUCw3h#OOqAHmi@^ zsEtFM!yhPwN?Ka=03;mfC>hM;xVN7YbzBZ+n@Qhib7$pE#0FVM?|r~7XALIU=Yhz;C%YekP5>p?CGTk_0s`OJCS^D z#?F+Qu`@GFQ8gu99frUwnIZ5r*2+NYY4Q|@uE!iXuySbKZHXQzvu{2rS|l4}78MOz zBg5~ZPM9Q*R%2x<`aQ@Rm|i(EiSa*g4DRX9c<$dsySeZ|caNu0r@oC(OWNZHBWIgb zQ~pu$wPtY$FWS@>YXg>LT|KZUBahmeNYmvMu*)$M!tp@2P^8G{@*7<~eQyZ8_J+0_ zN;X*a&dS}&&848=Dre;z%FSosPFo_JinUOwf<-W;1e)}>v9J-VT+Dy(kq3chRBU_T0>X6ZIS7R0l*RQ0Ry8XTtr?o;8ELl7b>WuM}JkJu@?@w zR{h;-(bY@ZF9#{wzF%e8$YM>tb^pJbAn5D?i3p6&8bH zD1a5N(pqRvEYxN@DtAU06gq)XTP?JwFgc8!DSC^8vW z*!4trA_Z|~!;@0gTociCZU!HP%PmyS1}e1sPlsg|d?(!&%nV-Qj7KigFW77GPy8ja zf)M+QO^LM_R6zBRVq*S*xK4|_eLvzW4{_%}`Om-dv2e8Z$atUn%y_A#YaxV)9!;eh zQ+l}cU;_M!^#o?=v(cwozzYS!+bDjFU#>?cV=s;WE-dWg6PV*%y0cNKN9yO5SRW|K z_mHt8C%mZ+Ug)wXXvxNe5LpRoc{*~#w~6GfR`Mb<`Hs=ab40QueD~;NT0=a5{ov^2 z<4hnAjY^JIFj>>sI{37;YEO0}obX=c8zBAgvCZL!QBX{K8-7T_i6&`|CxG@uC``1; zqmf+t)NxHa1(P!;7O3ldMUDXBjMA3ye!`*roeem~%H{fJTy(7L)IY{Gd>>}ZR81Su zu|Zae{=RDu>w_IfiT#WOg)+2`wh+|Z1iHIZK#=*NUZ4CTvqH2)Dn{u~|46?EPyLFZ z^bcMz3&KAwfFpPs^-V;5tO9;PH^BE>xe#T8Qqq2b+ONpC0e*+D0 z6;d9nob&Ylt5}O86y9Vp{5NhByj_Ax!#RAl+Kbuh8UVpe0XQX?VNkwkZex(6xtNW_ zwTxHJ8s&Jv3@Q@q&B_}1EGldIN@XwqKvY(R%HWU?6Gvf-e#r!p>Z!44@ur|R_)&?U zj24&_itHA+i%G93{Fv2%n*fu8N;ucS0JRuda}P1CelT1#Pjq#j-ilm!xfE(amI=qWy2gFxof!+)2(?9guqNJd4yi^wVr z{o!#T{Ws#)f$0{SvefH(!()cyCc!ftj%P#o#efJN2OVYijmaXzYAdOGqIXj`-tdkgn3=wPMb}de2BLp1>LRim#e)! zHti&|YvFt-FI@j_GJ(6Ea1b>S3kSZh9Y_RwR1w)@>s_eL>z2_y&`~JMJMy){^-bCF zmRObz<_~|pqo64}=#r@g!=8@3rtHYeXs$Y#x0o|$wR>Ry)Pa#gF0AAKf%-uU4~Ddd zxP$~@!Ub-~M6NB+y~dP732N5wvX^0D;svO(dpBd+=yE-|5n@(psU4w+Gan3~=gO>A4QST(k3l6T4k9E*zBV2iqa_hE#mB0W=vUz9yRM+c@zLN)Ch4k)dWzg zto=IoqtLn@_u($whY8b~DLbDN6ynU~z`OhXKoJ8CGbYWEAAzqRzO{f!=TP_#L0L@2 zze?g2qyF95Tp2lqJ4L{sxsl_V+2h8eq@?~tyX=TV!>(5SZmn+pN8Sp~UlXOCU>ro__lg#Ju93i18(~;?jOO$0Cju zGc0=0qfDBx6ZC@P9%*@RkaQE3qJe=S$Cb*;-@4TIlq;3qow$Dk3B6Qn8xW>9lGk&N zvL$aXvI7Vg^W|Zd4e2X)qEQY0Vl)Dw5BL8@=J1O8)FTB^g~L5WEE7Q(4XWT*ik_BE zd-Y;)AWIhY+HItp+Ze5~J8OiKAj!Y!eIa2pZqO1-D#f3uO{X zOYxriQ^dS>^QymqWdJoUH_UjV_VPqb2n-?@^YT$Q8cXcjy=rRyE zjcUgRf$B1>r@WNQT%QkLD|EZC#C63|QKMwM5JZn3Rd)gwu&Xz zVLONOn~Wpt$6XWjJtS+ow)=ihA6F-C7@vBkuXa@GD^Afb!n>x{2beQ4g(dZMmk^v& zn)h-_`@vX%@x2g1iwWZcFrq3ZjEt_Cmj0K5Q}9yT=)apZ-$5TD6yVd}2k&m*grw{> zXhz@bVW9HN@G_}H=jz7-43tQak)9FyXkbV43HHjUJHo#R0pMi-9EG770Ed~uMz^D% zOS;5=G0aW)qmEfDoImhsx(8d*uIWRdJa(l<}*cz&&>hla)8%OIPw-7QML01nk> z+PQy#6mS*iclte`Jkt=c3AR0Kn^oe!0SPU?(L+#S(dz6qG-C-Vw#;R1A#%%QZO>(b z^jT}@&mfXfanVuN&_xoC&y=cu!U5c3UMWdS@KA3i2F%K8`7GAZ%2z5&%F4<{ub~SN zI?%#6MQ0rs8^9**pV=tb2hKtzZpYehG4L@;TElNx3+O5UgqpAj0zw*amG{`dfYk?M z=ucC6L+M+V1w@H4?3Y{*6NMAA@`+}3n8E*uRH^KPY0IsjXtu{}u9zA`bA4EB zfdjH=d#@^J>pv=O{Sr>0Nn0Pu2DnAMlD5To5VXX#%B(EFm!h&?wvDZ9rmT#{5NjfX zed52MV3MF|`@{vmX2G<5qTMu5yz+#A2b_$OECa>cHw*fy0)*&a%Lg* zKyhaL6>J*-{Q28$$y3I6 zl&tZj3LGd_x)0O9(@3+W>Ksf+)|6vPNYy#{P~lc?@v`zAGg8_it)?pJx?OW@O4QDT zgQrS!ZGzhsZE#|50Nu0eMjeVb0R z*BthQU5!fDxL+6l#SOx!iZfvU+Lt6f6~J`+A@|PBy<)* z(6?r{YNA`UOUmIi=lZ~E-tMOhfD2n#=S*Hwm4G9n&c&`rJE;yJ-HjF`f}UheeX33v zIiMh<>b%VKB&y?5!(;S~@NPYS*BlAVR%}sMN~9sdR=O*TI?Lg32q$Zx{i}j0b4sLhlq&LT-{4y;bssIAk%Dp%SOO9%GOFxm(X5^V02)d zPhtFE@2hOLR`1f*P#1&*n$p_RY&?1g3+=@_>;-d+*93!*gkrn~85S6iLLoHW4IVgc zVWZz_*$Y0nNg_eWf_jOXMMKwsJD$D=KN1mWZ}}n!0IFUjUuxB0w(_)vHZDv)#o5w} z_7-ZDmPm^E_uwgOOIts-v`Kjs(C>$nSbAqDHWvA*{b#)Iw541q&_qapa8_TB{2Zf} z=O!%?y)v7=`EdZ$(MwgP@_|nE>0jT%YNVF|w#NI(0>GhAiTwq*fq+#ofEcUVnsxVM ztN?kGcsC>!b_Ft~1*R3ov?#GSv*RodbVarMA%_y1gG@i|$Bz=f0Kd{~q+R9HQ&?+C zOpgm03xSRZsS%c%Sq* zdEAdYwgq@7!b7ciFyMUMOjZQdxEdkxvG`Qtx1$)NU@mfL-@6U(Fq5#}q9DxKXHiF`YVQXrC9Iy@*gw4hXJ=k#PK!U@8eFC<4_r&qEs+iL{Joq ztMD6?Al%gZ;0@#xi`=FC+M%I$2nIYk4m_#EH^Ihhjh0UEPi6Ho1OXrsYu3M8wuyE? zL2q?92C#k&SkN4j3!vyV>vunDvkj@N=>p(X0dSx@sa}CJbYHNX8wu2H z4$i*9qR@LObmBrmd9Gg)GVXIcvzteC$Yg9FQeuB)kCGHCa83tyZrOW}OF9Bsg+;XU z3&_Q4gndJ~<(E+JF{*r=?`C}1yx^-{7q(XpDX|}+SaWvQ!8@wN`UuSGx~*fn4wdA- z%FbeQl5n-7rSn-}UO>x9IG!*YD^O;Z72inCb}`e}kqLf(JHn=Oq7&dt3D#<}5lY)I zJV~1u%5Cst%kpb>Cc*oTg6cLG%R^c4x#}u=%ax7ifVM+#uos`p;zxwMX)ny!jXp8? zaR9@|%&uS86oDJ=fRImnHZ&lKvR6e)rTNe0J z1bESh=&Z=atx%R>BJl4=VJN6z9m(Ms)+jcE+)#_M*`v9h+K^+&&Sqf+0$mg8)9~q% zlZybFoIE@=6fU{QWb~)H1at}&IIQz=oDFid=)duiF&*aAln)ILPe+sAH=;ZQJ|%6L z3!t#%hd+mrBb0qOSSR2%=a3VThG`{^?P&-Gqz6x{fY4Z~wUk%xz3EbI4R@BOeN0uG zMxE&aZwL;sC_Cx_7AK84-6INvSsTx=2vFKn#8bZaJ&1dRoxy+v$%G)+U=_rqZ7Y9< zr{aL7=p-aT&e*o{XE+z#6>1GHl7w>T-;WAV#%AHq*&G09ng(G@>4YD^0SYnjp%}G6 z40}+cwcW~v{p(B`>Ooa&z9^f{3AGdVbXh&TR8V$kDW0*GVYX)6!fv9(;a{@pPyIIm ztNo+53x)Vhl;wG;;U#sy9KkA8;NG%V#l z7Xnii>v1N`E5{aoZ{?Amr=`!erG{J@O-1Azx4a6TS zq^+Nj)&V*&4 z7t)tXFT!#FMfos#A=^T~a|LE|SnW|QXyu^N;+2~4&~E`>x)xpn!f+?V{C&6qQQ9&d zg=I)#(sFT?In3J_ulT1&-y#cGeuXe874vV#8|hcrDDJA#xYMCP9SI1hVty_J_>Wc; z&aK(VA0v_Pl*mi>f{8rkhx&yP!O#w{xM^S~5jG#fvzhI0xTV4?jv&BGD!h&EPq$Ii zjZQqg4Vv$Z$T9@xvZ>wmw#YoRwH6O-^XNd6nc`22604VN!?AZ-S7hoWSs}Ijh zaV^2M6W4$;sjm;kG3y3Hm|*+ZQv~ab&itp&kSqf)<`?JCJJhj9pho>1H>R5LecW=q zAI1HGxyLm24*g7&MDMYCB+_|YD=L%V-=yy{Kp!R%_nC;7NW=r&axbtBg#Q1aOvkf8 z`Ey4a<#%!WFEJiLCOUx5<8Y3a2-3d_jMXo`j9jFC@gayHz_h{cZ*2YqZ}bly#Ov*@ zq2IG8yXq%q{^QZ&OtxpuY)>&8r(LYQIkFdmxfafCTv&@i(k8=hm*NficbRz&n^!T0 z#W=&b>tiDR;Wj4kcuVQRf4Jqahk zO2QK3_lYEK^MCC%43O(EL%Fn=pGZ-TOPsj7KjvrUl*g+hSsg?>?uY~yI9YqM zO4MJAcG`PRhs^B9doJ_pL;BU=Q*gYw{U2r7!bBbB;?`6Fojj?!a#Bw}=Y{k@#g3Hd zH>o}a%LA1{Q$mP$Z&hxz0Sc9zmu=BN_%TcNe2QKay#Z}2{eFTdG2R?EMzFlY*`mL& zM^rMD;H1Zs6UtByY*7mgIMvkSzJd2r>h85__#tXQdlrTmsJB!&bH9a>lDEKW!CLYV zA&p`zJa-ZruHEL9sxWJcjH#hi*02mUKyHcXJTK^=#Btc$|3*0VwyDpGvq6;DZ&9=Q zEZNnD@?~fnIJd4ynU9|3OStM1yZf2(xYc`5iCN$E z(z7bhfW>&Q>F=&XHM1DmWP5-|&nodrQ2d&jTdz#V@5!>@XFryY3~w#uRgX><Ho-F(E7QC!DATi*Q@yQ4zm{GKJ4D4ijcQKdOaA$EQDVADav} z56&Ti=|9|nCWyG<@mKMMd+16)ks(e9PwC=zJfFzeCo|^h?}gD+c**s)g%HjqvQnQB zJws&swFU1ouF!=G2-T*tBxBQ?51K znYD(ywBIPLd_kG>TxdayK65|j2u9;Emej94u@NN3Su9PB`l>i!i!BUI#`Qh=`uka> zb9gdz9~_o0n|UhGVo^+f-7u|d1!jDdPl+q&ENzLSwMG4dU4KjJHy9>9SR3N6aVUsm z$gs3L1+j~jJ8wF{I<#zkxWNLy{W}6Yf?7b~a7C~odPV6$wYLG1fjV|ai4<)CYQ5;6 zWe9noglpi>BK>x-8)I8a!pkgxD+RzpGi*}11?vp7(F~o`9P5YHBjIU)#u_y8BW$IS z3NK>|FZwF9(*R921AT#(94lLLjBE+O4d-B2qNOwU;O-7U2Fy*kaMowE&fOxA$Jm<+ z3h|x_*5jpZc$Sv0xUs!hg!}a1vQ*iEUC5ZN&`?;WShxZ9>g8qh(_iWH!*94->;3*@#Ilyj0x+#NXd=bQf_pn)8veEj%9 z5)AC(tf*er>%+;k%k)3jF^glU3%XbMZ9v8Tmp8pk!;<@D^W~*@Ig9O9;^Q%h(1>(Q ze8@bjF#pG9RZ8p?Fwd2l@vI@Yb_-<&8N0?)S?7qHP7t9+eeb<3Yh7Avq53qMWy`}sGRnpZ^gdS7O8jP42Tzw1 z23U;!S$%l!=~QpTxoEMkleufKa6(`S=4If=vz@9;CvyNfIh$Hbhh=(m>a!2dJc0~Tn&rF$7);aZLhf-kek z@X8eeU_g?=DVTDI+9J>Il-eQ$04kY~>6yUrcY42_Ap^GH$uq&S$PfS-W&=^Y0~zkU z+akkRnf=X0qp2ZtMy#A^YM=$!e?*y*Cc?=Q=Y|lDo<;8ooKfF+cq`3aB~AL(Z&Ra# z5rPrWt@^_;fx%EM*l5;fG*5}Y1?mCAuMCQhEat^=5y)cB|Nmq>^jifoo@J z&4izH*V5CIIN2+L) zk7qnDyI5-F5OP$`*^dl4@eIAkZ2w$d|7kZM%%=qvjBZb}I<#p}eG!#vSi;4@AK>hg zul6#Ty$Uphr3SscHSNtR+5~Pd9^Jn(&uvzobpNJDbcWm0jX&?3*<(}~HN0ax`evvU zSsk6L3Q`ZZ&)SdnM_bk25^Q@|;=}+HOtjY`?3+u$1Q76{WF4Y%x}flvkdX>4j`?tn zslNtY;ZIPgsYPAt{#;)PjfN^0Owg9%5;Jh1#*)RCn>tV)&(25PIQWsu67TeDpkx6_ zbyf*e+Ig0~*r4#f@CrM48eM0q#^;fr|}?@V7zp#Vx>OarJRd}p);VxIO2R|-{I z(+~3zN->v+fm3b~5h&UIh8HkgX5hW)ZPL`F`|I7rn+bdZ|!5EOqz z?=2o&fjbEr{1b4A(U-_!hldoXZ2Y;_vKAv9Gj{`$|2H z#^Y3-VfjR~5|8bTzsVfXi;(-47jYlnk4+#Q4th2UjVW(%?1)%b%q7U?uEg<}O7eDO z?8t#?0QC_?7|y~#)XZtik&~I(^1qLIrNqxa*o4=gjVi-mvAz#G&S=RhXKB%xSZx{ipmGc)V!TF1@C)S$~2{?~K(mFcPxS8HIe6zudGiC(7bw)Sp z(p-1Q9kH*YW)G07q0ev$e9Kw+zzNXWTEh-&mu%r>V0BDGB5E8rqoZ;5>}d@zMJVaB zVr_q4H)tyZ(tr3m7Uy}KgdmV`9)J4H1La85cTlQBC6Q}=B@Wfp)*}2%ro*!tV2T5m;)yP385TCj-S;q9ZO-H{Lv}n zVDt2{OZn3MEX@FV-LshLP26G~uq3owQ!ZPC95(&2*DM2rY$nn$53_N+(;3i*x~%zj zNqPW^`1mhlc$@^)PUI#x1jKU(uKZ#Vut}e9V<9`vL^iU@?D)vFLG{aJ zH-$}xjztNUn2$MD=JOW5@PrF31U?iAE^M!R#VqShm;GM){38*p^{gyAvbb#5L&4NH?DfNKcG7bV?js~wdSLq2(2(W15vo2{HdLEkO1ba88<00E0qYf@& z9;au;JgyQ|xVJ)<8SRj{E|?|jL}U-nu7gi-q2I9pP-Tdj6)fhF(*OiY#M2Vq$iO2W z6Ep*`v>o6e%IHlTs0QnStRynvb{F9Ep zyh{Snb0Bvub1l5jQU-jnfh9pKCB{fZ?6T>0>P{Gqv7IzgU6g1n)_!#et>si#j|@#5 zC8ckfXAM9#_UIiT3#ouF8OZMmxL}E@1$NtREx#V!zLLofJGm79>T{Gzy-!R1`8k{rXH6vxt*Z|g|R~+v!Cy- z1!8PtS(}L#7~(laGERW!6p3wzG(lmXiM8#pm~fC(W?qHu5zh^L)?1zezUlCQ5{Xu~ zL~Ef$3rG^JMG~zx7Nc>iJR7$!8wGAS_As;8aEQ8*I)a_ypcjyYlM8kg8i=foA7C6> z?!5#FXz87VnYr^3N<6aJniczB5(6x6 zvE^MH@ZQo*{`lp1Bd7y&ubUkEUn?{`@LO6j4{h`2$0_>U;LYJnSsQzQaOn zch2jK2SE1q^owKETDGI0tN5FD9Zk zt<{lec4+gRiMh_?#d7~d&TM`k6K>@hFRT6Ia<_$=x$E$KRUn~0#W8G)e6P7l|Akly zU`LH0CPsjuCitPSeQ`>D&5h(uYr67=`~vacNLF3 zh+ifCJN#l+MbN{22-*P0Rb1G23t!68hA`*scN{Rrl;v3NAcq5Qp{g6F@mUNd z@k4!Gp8Fw7itm?5V{b=!mXY@z-~oL7&8c8nIqqA95w{4U2cfk-TqYO+?5e^&4rm<~ z;x92X)T%e23&9;oB!gfoAr!ZLUKxZxPDGrJ_)0CzkD<=Ug)Tx=IwUewn8Zn9k2gwPGB4Njzmkb`gsn% z`MY>Wuq3bD{5*%{wShHbu}s_3$X)5&65t3mgB4sr*rvDMET96l)z%mEBf%f1wS_ZJG?zl z8iKIRJQ#)&k`hP&&uI*qW+Jx1V&iw1-&%8}f?*Vd_~8oIG8*v%g6W5~5959I^XAdd z!P?AAe80!yxJFKblDHCy~okiap1dmAiHt9E7 zb(6k}oAhb$j5O)z3MxCZ4)!q{z`bSR{qmgD!crq77UR^bRFtC~)rdNhQ3sX`IOZE> z!q$$7I0Ng0Al528UA2WL%e5h^y1fKT?P=KSUmCsxaY&(#TpWD!4Dvym42jWJ)2U6= zCet|v{^H)S$cdrG1tK>jtOMC^71@Et5IZCDE+6k!;+=6GoBf11*6;kSJjPFH7zj1# z(_s_AFsxO#7lCmhc0E|QITAC6gJ9y>f{CG`lZhw63~9PHvk48yRXRBzRa?wFPm(Q* z*shph*H*T>1aE~|8zBof33i_YcK=YQ7LJS$HS=*q(0nw#4`=8-`6d8c1bVM@B(HXG zOWpaTcT2})(7S7nN$+N3Rj01^SoA*aa4zEbKLf(BY8vNvz#T|L*gTK@YpvZ>R13m$Y zq_>d-a9FFE{c>KqUt0BBVQWC2hz`L=ZDqB#Mkv?Z0(HNDCJu~*fD}Du=h0`!k7p^& zqk^PCko0yTQxLt1#y>?m!4MCrXyn~N4gW@wuLk+9lljss8izFNrUxtNuO0|x^q&TC zn#3YVrloQa$nGfRx#{}NH=<=-aFcnSm#o^FVq2o^GR z4)r~#hjnhWBzM;_J5i`u-9LgG#2;a+iZ-9Zp&V3(p9(kf?A9uIXlt$X)-y<{ECjEd zh{sx}9|PUSSml$G+cyMG9&^{laFtf))9SfF2hcqx)#v-rGBNQ-3&baMhl9w5JZPBs z2KZw?6WF7F-R$4ICjC#4Iq2VED*=tQQvv6KV_a1z|&7X75DxDlsWIK(+^San}` zFLQ+~9v>xNGScO7PzpDfBHe**4>*H8kE$B?ae%j%D z2%2Nj7jh8amliZ{1z5j#=rD1Vf(GLxmx5-xC`8$BN*tClCC-DQSmDsKc-wblNnYf1 zk(rZjrUvAt*y$E|y+c31OCc3{Hw2wkeJfTWr?RErI6oAAh>&^eX#?^iIU|osv*eKh zdxbSm==i#BM&|q^kF9WI$&ypWq~^dmUY&Z~4SZ|Lo8jdp2gNSPF7a0ym6h0M z^2`*UgDF0p*X;S9iOhvVIe^o*x+FgWo2m)T?Cllwa=o!uL$1B_c~h_)y3Qd~$`Wh>0O7 zIPzAJLQW{Q%k?9myx^wWFnfXS4f^y7(X(KbGHqI^F_w3_@4=U~P2MR0mZlbb%1V}U z4No!7<7bFK#i&bnK|(-i!0;_KlDY0iy-i|dY9w+12>Bt-$@=n6mMAmX2>vaY%2EzL z>ak_GH_DNgM~mbcP9Vez}MpRRwp<5A3|5Fy^Cx(*B5sW zxg)E&v$3nZsabbHyyB44-B$${y)DKdj|x*ZK7)L#dDi#MkKqncMMf8xS^phA0(F$! zo6@=0irhZ%Q@vC(9Qp;|hz@&;{Xc>XM80%M_Ko!08Tvav+(!@Bn0ueYgpp( zmdH|UbQPigyiEOiuINFYm{ud|pM_({aA2=)Kc6osPOiZDwmv0}j)T&NI*Jwu>F3br zp$>?R#I;^^p4*pbFC6YpBmeLCP>o5utSZ_>70_Ws75AQJRuKsp=e6pOoXkh+%{UVn z_2^bxKDa=(<;4$0Ti~C?Nxl2uO|)03JKZ={jP^zF1D43~_pnu%MghPfi&?y_AwOlHJ z9=cVEn?gz`PFimL&4X!x z-(m4C9)10(zVa*)$c_dA#)+8rjs*;k&(Fke3jbn$qav_`9@4jOWA{`E3E6lw=~!^R z=rk!EBiB+gdM(KaL;hE!ATt|*Cb$Gp9qS_rp;>6Wb!dcM3xnY9T+7z!a3B~M1!_5k*IsML-Dtw zMe3IyM)|!&H_mr{4MaejZkmUzv7OAR< z-upCN~t|#;Nu@an^xw&2<0+vuROABXK)C_7rniLY}c>;I0EfL4VKf6@bsm#J)> zV{Ca4+4M!QsA?m_mySt37lc z`o{|D=pUPLq|S)09ZY;csW z&kx36PMwdGRfl%uBY?FXvD|~L zXHSF&qV!jymItn=8i0E(LPPivv1l!s(`*9MJ~W$==0EHjMjBxapG%A7Jg$> zHBC6P(hcB^e?I`n`d0o7 z!oCRU?iJF#u%m>h>Ld%#9wlLv*xMjSM+rY1dG=j`)8XUr0+4@welmzSek`Y&LVAwi zRQMz~wkKuF3eGx%Fm837E@E#6XKaH`(euuRi|re7a8w{{CxyzEy#v2@Dq99}_aZJ~ zADkneyhMwuX8}w~DO3-G6i%FJGX1KJrp+Og3Sba`%Y!Q5JH3AOh|vghOPWdj=wmbH7ORif@oxbOL|57|Vor9W?&n)I!TVX3c0iYTSSsVEsgG z;+ppk;YloQ674Q<3N|n^z6kNBkJY~yNWlPYG z?#tbq4CX-E!%Encn5rG0bWG;zX4Qm5yTd+7%fB4MqkZslmEVI9KY*+r@CgZ?Iya-?#EOBw@{C=&?UQ z1Hok5>KVEQ0Pkm8v_k`bcBmgH>X&xq=6a-p?aT6R8mE3aQBliTZQcbbvw(L~43@WudlXL9rUHRafV(lX=;9dm&WmE|SQXTdI-2nrZx?iCj z5&M7DPM@u!8J#00rA8+10ogAHlIx%Yzp}a z-oaW^NDQ}_M-sCRfli5BxqmGHGyCdk8;+|}p4qLww_3hb9(i%#g97_8v3=d|!l`M= z9wNf=CFWf0MmUVQdBe-eUVGo~R}TyoV7@wb!!~8hK9F}8I6DYCmldZEm&1+g0< z0_V(uZwYE=rh}u^kD$1`p2B1h<=+mY!x2bCy9LWX{4dH=A5ce>RZh(jUyrdexV08T zAwyC1Kv8DB?nmz8(p`{~(v6O?NGTuC3vhlPX05|yJG^yg4+obY1U?q>3SHxeQvT&y zwy22uDojl4?O;zQm>@6*60CL8hT(zXg1+}x(;Ktlg}z;V7`$6vR<^vL)V{plb5;<; z^=;(#irwQ&s+LCSwQB$@eEUe*f*CL%=LQUhhQ^M3IVi+u+{La7qg3y}x)b#y19wpJ zj3FCPriNXMs~=Z}De}E=#&EZ83G#^ZKRl3&cft3lI7eKW5jHCzS(ZUHtCOumQp$r$DUVNq_ch?Tk*#>*8aIiQA-pf6$ z03z|2oe+2K{Aj-V-skv`Fqz(~4c@ut_Xf)6ZW|bJuFr2>^9MZj-Qc`r&GVcNYJi<* zeLKHKl+)6P2b@QbH9Es~y(&IjGd{y-7!j94z2!WU(e|-kp~HjX07$vHG;ymR{6$+w z-F(f9`|%4$ma_3?+@N252x}Q%fz82@NNNA@xMsZ<+zHkUkV!264Fs_Ry_$!QP!C^$ zHWzY(+4@}WNdXfGa|KQ%`yirOq7X)iQn60tiWZ;cz+}XDszq3kU;uFUF}66=rq64`ig(6tyl=oTS%@o#4Tleh zO&PAm*r8gy9wP_WGjW}X>sh$s<1%$;<65c>n4F4=>T|cD4(?2`fopM#5h?>k>*v_kyeGN&qBXn7&DHDY+EJg+SOtR?8tre<{~L&6>;y18wKo#;i-%SMs)(vQ zmQj`Z7vC1|yTNnNllx2e0lJ5ci|<0K%g}yhKBi@;Has~Pq`&;+AKoz~Ds6EqsA2>}g|1XNxogd|wrh2Rh&iOCEv*MOlDD#Kw~dfVHd z_f~JE*!$SG`_Nmfpw=b?6T~;Pw@QocrLXs3uuavASSj;-*WTyMOaNQ^`91&qKA&Gc zIdjh0XTPq!_F8MNz4qGFnk*#yDKdi=1voSy?AYQ@Y!I6moJ;N`1>;0Dd8}nfO7J(; z!1Dl_St-b`E%a+Tf&PSSMgH}l2Vz-h5C}6K(LiV@&!k`_Kt(^)6mLq3;?d`^nI*_J zVFQPHeVE1~v=m!Q`mE*$jh@18T2)ssk{HJbGMKJlFt`e(;zdKO`sC8`{9NMr=JAl0 zU_Q+xQMpPqibOA8BZ*3-8fly8jeeO4)7AyBn=evW^x#67wk$XL2`B76D-0?;a=d1$ z@fuP)^G1&M97<}y+aI_t6+QFJeaMn*GHgKwxM z2oNivc)Y(x%VQshzcYC>4hgCyCGN;)9?J^fWHbsv=G1`cF!~g)uWNB7)_dlwMb%Ow zlp^@{z_UT8MzB{M&61C1^f615gb)$FjMd4T8_oU5 z5-dX;^bW8HZn&J;YD(I_XqhM=)sM7CbQ;E}?ao9-zm|Wc^ju@`pP8ShNfSvUtYaJh`9zp3|7WivID zDyRXY;4ky34yHp`B28H=`F!+zit|A%39||}y92aBcvHdntLc0E$g|qXfKF*ijSZpV z!{A6Yhi3fQg}r~gphna@uAi}Hd6hASkwJp0luN6sXg|JH+^#+d+B`RE!Ljjso%ZLK zqx~7KpJ7|z2&2RqDZce(Eh|o|SG#{j+$UUF8*~qu}5vRu& zL=kCb`hiG5B%A9UUV; zOo<5!2F-{%Ha3@)7jhV5!KGW3P0z5B!8y4bloh^Y zoYq0tFeBopg~{40IlVv>Z#NKOWe6aIQcF9zl%1!_mZ`&t*_G(?M8szd$n+_C{Bx3h zIg&ggky2mw{oXZtm=_U`#7E}Vmu0Rc8ow#=h0b`vLf|KQt=wRPdAwfr3c#A2^mixq zV`(lPbZY$yYEOI!d{BJAbyQN4>f3gz$WJ66=uB1A-hDwECYt*{YmEc>JOb}U;aenl z{|T^~xT5nb37V%r5S;PYxd`4&)L+IdRW+27X%h-?C{Ke~WY@ql_{VS`C{)GoyJ+<; zqH=~QpfodxMQidk&P)uND@5p`0#VTO*+e(i9 zu3dKcy0rZdx(7-0b*l$xOreS?;QxixGO<^IeWQRK1%%m*kpa%|A+rMZDQrCus9y%u zpT0B&^^{GxhPqN`2+UI&4aINm9fpdjD&8AN+iEC#V<}0t^C2navuNY4EQTkYw?H8| zTa5R2SSt!IsCBEQn63ttb8&CwAP|J9iUX04T8epE^?=Q2^FL~VL6v2;&O>=sE8DRP ziu)@EJ8#~F%HpS@&S7g;coEP?2)K>9w75}t#^q#S%`{cdsx=Y`=r!-DS) zHrWAVpu2s{--7S9_eQ|i+SKXW`9Fa7V}m-#Hinc;^Vi#Bx`04BsHbT9c>#F;4102c z_o0|i!CUR$f)HK^-a&Kp9D1WRA1MEIcz#THgKi#C%5wU_w%(0bMNePdc^Rr*|5lW` z1mr|d=X8p$cc%nk<8{E{7{Sk&kIDekX9L-M#{4&yk`+Zd=tgmv$UDv+u;}RoCJmuo zpJfx0I@0){hc9PRUxpX)rNFI-l3-|1L^tz_TC%!QW?A-5(rGxX-R#~nIld$}9x0FB z;s{^Kn9dCqE>UYTF4MdOx>IIP&2nc(2i(zqPvvHJ*K0Z_AvvS^_-;?w%!E84ilip&pUJjHz!@rO@72@jN7Zr)Tq-jM(x0hB0A^ z)bhmUDqpKB-)JtDwMLcAd6l29j+5LY`3cBxrQ##=6vcT#DA|Pf#tPo}H6EIc zrDO~VW|Ro^f#ulK^61I(l)iMLQ1dF#w;s`iZB5J|%eYSkGM}cu0GwKT6Id}jovu=T7QtP*Y`SI>SSGM2pRpFB2l zdCzMszt7>%+k~!?F>Lb*c(7}*^)_||rBV2pQ4@=X#ASsuhAOg?z=- zSL&I&dpWrvB;FECydIk%FGHU)a^xO&|5R`*2=C8P=LUK9sL!JkZ>R@Ardur87&(h2 zpXbA+7?be@Zw8m^u(WKh)7hZRQ&o(qG$J-@62}VKrB)SgQYI!tKh;?b{g<5yXS+!q z%s+}jyh<*lMdfBpQq<&BQzVJ;zrt3+NMWv48vs&gT(h`j}gNm3xH zIN>D_*i%(mAjCdk3g-FEAoJ9u0}B!1Rp^fCf_l>bV?(nw%gZwVhsmBaluF3>?>s{DtDv^9<|kj_?ij z=KI%)bO(CEzX_{1pCAAtGIR+!bHt%8{!1!n&+gMi0e^h--sog*L!+Z-6Ok;YNdD1h zMdo$*wmrwXS2U4`Tt+iWW97^0%`d2Y-I%Hg5yzR5>I|9V)SDgp8#w|;=^NeibP%(> z`dF6v2_mp*j+!HRe6j^Mfg*Z5ttS5-GQaVh%!46Q4{xXWZ=+Rvu<&+Q+!AhJ30n;; z>;vk|KaY}c0{^1FI{rqb#-FrjP=*R9DdSKEXx_5=vbW9eU8^!nHc$;VqvVjvnK2!? za#?*UA9Kq&(X9bbV^*E>#imC>h&YZLbt5OXe-Y~r5JJqlSBTAqvmT?u-zst{y)gm5 zd%|@dWMXV{oyf&vXqz=vC5+b&EoJQzs>d(gnZ0zG=(eEmnJjkoqMG zl~6?kN0}~q%r8t<&0U6rSE{9Etp#vjb3GL^#t8zO#w*2#TbS~Aud9PCs>(6@i8)m# z)`xPXg72dsfK4!S$Mi`v#~d(3%WjS_EgbKA} z_=pd9m*ZFH4P`>QE3ES@~)?r9kqQ8J{d#dJr^}4z(mmS15V_B{*nh=Wh({mF&Da zJN7(@I;SOuV z0v1v1sij77z#)(z>y2Cm!BCeidyUPeE-o$MQjLjb)%c{?USQ;ioL^;366U=}#Eib< zF5%2$V7G>1?2MM7(fUy_Pito2>p4v$^$aCPIHZ-(qxiIEC{staCGi&oB5I!Tl*2_j zDOL!wG3h8JD>fJFI&@k`*)6GBJZS+EeRXWXp)*ejzNN)dfr&{{b3L0%yq@HlI#BWw z(Ln_0eAud>M}9E0WwpB-UvGy#{qJUI_HO^M4>z`hziSKFB9n zG6XhP<4S{?UB$ONd}07qe`v2@gb#bWgj)?2kgw-|@xiLcQe6*U&zEIgpv=4zeKIZ6 zL;J)Q0^yMUrp8c_#;i>9NtKUa^pIP=HHH@0pC00qt_1w|#2r>*9dcp{uQsEx?yx!; zBeI0#RugGk8u*FgJzhD?Hg)>E(A%$w6!l&YQq;j1=U4C+cjF5zgVBUDU%+n8V%MAb z11R^NDp0`u7Ecg9iCOmI9+AHWGX+EFO&moEg6Sv07yV?*!GoD1Z|N|R67`eF4)SDv zWw}sKsgFHdWPR1@ps!GUI*6!dj7Np`3@9x)!*sHc3<0u2__S9kFYQ$dO}(0byqV1r ziShqQBW*B7PcFBCfi(@Q^g^KNlP~~Pi+JtEAJX9}B&<+qtg_Q>Rs2qtJ3ieM3TYmf zWgZ2T^q$rqL;e>-aUWYIVaxlWpqSMQBfzqCOK0>$PsdWDe}ommyS5d9od5b!_5)P0 z>u=Z)yyW@=70s=DVYAm&vSV813tgw>9gNw?$(LeA7cQ)3Y?smjSORako;-`#AT-f< z;oWai?LhJ2)w>M$*!X-f>}1fGeumrZn#!TBw;({_v;9ri+;7w8H0 zu!uQVHtEUr_tn2HD-X;2lVkfRz0D%Q9ZgCQ z4(-*zcA583dkA+C<<4#t<`oLU)V>L;Y;ngb@w}#RD3hLN%NjytngtpKs@;}$nQp60 z2^XVgJN%?tM5ONqWVRlHbI_r0Sb;VorH#iuBF|eWE#q^K2<_?bBB7_f8)B`=*(8Xc zE>wO@k&mCX$E>8sMq#QTc6{P<0%j$%y3U@e&cdHYg+fmBfoiD!l4d4nVPNGor>e(f z`NIVxB7LhuQL$x81+lbWWv6({>TL_Mvch>f%@|g{zpi9=5xrki#t5`gz!q*THqf;02(Gk7_e_C*2pFgvdf{dJvgc`#kvHMoZ+366U8>sV* z%^Li?cuFQ)2-zcK(%3Zc`G6p|xf<34g~y4bs_g%|QeNF6f7Y$ZJV;y0iDn&wpnW*) zk-}v{xdei|TcSuWWmobOwRJd_7m{eNMJ{EMD|NH=QHlz&pklmS z{JPj%tE0TDMQxSO6zrBP2C7RFjv+sWpm{y2C2rM{P%hDd+!2A?YvF4-YBp_wKYSS?r1(YW z>wXh?hc1}gx97-K;670&JR}J@3c87oHeQW)u*pWbG^BYwGYESsNIV?VT-^=496RzS zUyeRXOv@KKCx6z`M;*xMIW|HcfG4ojNQ-&>=6`}VjC{w?_4=8i>=^pwV5U$na0@p&`dLWE5c&fN2V{=WAw46iB##hC$BD1UEdt9Ri*CKxItN*XoJ^HNQj~`)}XdXO}<5HBIga-!A!}Y8UZG9-${B#;qEYj zaG8w6af_Q_50)c9FY8r&Li8>cvlz<2C;h{{LQ!u9#AUq|nbHWLujf)imHVk& zNZ&IQ5Db+Fj|Q@k!bHP^jD|N-_GSKk?DxVxLfWr`0)=7oe=qTZl+ zyXBGYs>}@V5GmL_ky~YEp}hE3?=I)Qx-zqfTbNn8;WOV8 z&z!b=_B`k)i?g%(l0#ofz@+!;^nN7*E+(xQj+G|zzM|TcKQg||wLHe>cTzhVaVHw_ z`ot3$UCN;?BJ%4`TQ|CopKIr|cVrd|JWyMlP zM#a+n@Fgt#g~wDZ^@cC1Sjs8!;`RXf*8eo)I1psSK8?U6xjcK;RBI(4&h&XMH7~a){MfTL$@=bR9eka=%Z*wBhy(47< zf?5Tw!CKEFA2{%r=Iu?$&A;nIx#b3(%;0PDcL7lBGOA9?Eig>XWvc#9KTKnEMzI^u z*}Z4&zcap|`s?}drxG{D6}+Xd6Q2pPQY&j0v@4R+Xzuk0{d1>)5B%#e$YKhStMG&k zWwNE!d;>1Yj5q*=0|`EbBIc9Klf8IaF>d!%P7^C7Z?MTX`yKOp#8j~&KbLu6044i% zDu|U$#YLkh+f(y-_U0JhE$jy3Oi`!g+08*Zx=UsMP2fe#3I1*8 zh&Ck{pDuQ+rSa)vDLOGeJ)c{7e7ekZ7f3dG*UdtCBxteRi7|!e2}3xyv75n5o^&&_lEP)_01MrtCG5g#FGz_pTZryi=2u~F&+>U zOU1yq5HA}W>Jm@N_||86pt1{J+!#6{a)O*=A^xZY`;N&rzlVxaRAGRIcUcbhWpz1ZSI)+60|tRv@@~kXQF>} zMox(SiCAJhF$)Y?NaCt};zt(O9uXhwgR?XDD}SQCvKm(jPuRuOC{Z&N}8>b z;vo`Mg0WSQ1w^@`UYz~pC%&(7M9k-8P2&?flJt#^0FE>!zOT`P&M;1=L$5&q=tb{u z0iA&*hMfQ))xU2@|I)D|w8@zOeTh(Y?^6OGWYoTj5uj;ZZt8vW0V?BC&WmF{=?T?RN9_Ur_LBv9PdKV=})h z)AH-w`&V<3u1===9g8Vaj{@r9i-v_msKOi0Dcv<35+EhheQ!j%DSs`UU{x|*WJEgm z1=G1;sh;TBvk{K63DjC7U@?ZLphwEGR5l86M4sg?P5Wa|ENG$#0kEGUl%d!QX@Vp3 z6Tt#~AYO@eu8&P}7@1xSZMXeUV(3*RhAy5{N__Dz6P9+~T@kHGO#J?Z&bzY@>`5Jk z=i8=iSih)9o-DsXnCgodb^9^(}h zPd_I}6)VdE(cVmDsV!JzJ}!%0P^x@!#vmqiAiO%R?)zq-A$8CKG`J`>1Gh?DWvA-I zV6YqrmYU^<0vP!8cg&N85>sp>elC=rzQWomj-?{+|dvAh1?CRqUZx zLWU3*vy_pG8)V0;Xnp8CAz0!X^+x4*FxTEV5KtUJ?{A^>DP?bi9&2S0K-tl1K~(56 zS3Xw}SW_tYauX_sGC-9Dtn%@FTj|0+=S4mPO`=?{5rh~MF4of1q-yr#C=%D8z*;!Th~rdOn8 zO=49kJ-Y1?Y58adOo8$FM|qCT$T#|0ob2;{^+XUa&C&yP7)&hwS9U#8nSrX(EUHqP zXrG93V69m#`gMhfEXY~_=$b@~0(^>z36h-hh*P%!Gc9>UPsJH5fkBEQp^j#G6{;Vc zA^qSmk3S={(#;SnJRzH`skDGSRp~RtYLQ9@MojcMdrP|atI`f75#VBwZCJ7Td@UNx zIaFs6a?ZzABAF5kFs9IC`bQ!jv}A%S0rR`c$)7#Q)YA8fokHD4Ul2S~nVblC^k{6- zh$^jS)*Mz3@czb~Jm83}}5vFv6xx-{} zWlCf>&fs~P<;K`>8k-W^WBJD1c5DjNi{Y@w7E!9))@~8uKw2<>?_5XOC1t0!y|(3Y z&7%OF@F)cxKGjyf)Af6cH)+^fTy5D><2kXvE9qAwW$0T!k}3JnHw^Q{07FMlT&!Q& zhvt!`DKRB&5n`Q77ud>%e*lRY1)SDCHcNF|EQ?0U;*zB=QHI6+_2T_3Xx=!7{>-8& zq>_#ea6Sc1_i$cuz-?sVg0-H!#|TfDCe-`V)R`mp-V^0e%g9nz%(!?rkIvRPK?Pm? zbW^U*(l3}8?RBR@WLbJ-h#VC(GNeGjYaRd13_RW6Me*VIoldFUAy5y8V<=6oqQ(lR z#nt>9Y(9TORT5`Lx{?cU2f=z+h8)$K)SpnKRZljPNW*hwgHSuao&9*CtBoO&561ZWWh_O zR6(;{d1zKM>GabtH>m$Mdj&|R=C~{}Zk|r}phg2Tz zyd6c7I61WUoHJ~S|HSOv7;L$LV}EW_DWOXjgU8~IJtO8sCxG=_@%!CfwL2=nG$Z+^mP{~MpX*19`dz>em@W% zK#8}9N(hN>scwodt*U3wYXqJZ6@lHi?KYZ@iPc0RAQrpL%a4N?_;?oz%8AKAJ3NDM zLOC-M=i5+m`F>?+*VV8|<-Cx?eP4k(DYqO@)vBRSGjj4B`-4AZ{DXF4J1@zO$YnpEp#f`M1h+6?R_TKv3x0>q{dg9Fut*I|F+C;fIgDPd;x z?Tj5+Q>r2l$(U)VAINr?pT34lwL4+Ms!~j)hzK$0jf~qZ0I63S0JP3l>9rfgk=FM= z$!bsXbM6u`mqO>Qe{Bq*@7U_jueiIh(K;YKYEA>QqX!E{kY`0dReV|Mki>&eBC3Y&%X6fP8Dx2JHq+;$eO;#MD2o9p;QuMyTxSlO}ABhU@`Q1*N%lMfOp z-~3l(JVw@33F*2lKI7~n5=fRPr9Y2J^VWk>TEECMv#S&%#K#+@Nd`)+Ig9D#6yDIn z3MW{->l9EAKq-CI{f6w24~UI;6`ug+Zsu)7sOF*BciO{WxK98 zoLbkBG7-bVU*MeT**ivw{NagLIoam_PH6jb2=`~qug$QL-Zen(Qq+K(JOfNQ-`LhG z0g-=FtqaR6(Lc1Ep7ZNwiiF8YKnpNYyi5e9*r*eqf@_QO_{502_d;h9)L{|gP&2;l~07%XqjHdPLE&Q9c&Fu0nGVVmqA6Gg;u5Vs0Rr1`v-X+Rv` zwhcgwFM>Qn*EJ~ZtMyi3y;$UH$os4k(KA)>64}iTlm#vV-tSIILdlY+$`X{E6`y$) zv5ESZCW#HE`$2gr&tOxbdfYGl7A?QlSMvcet@4z-rH1_0lR-*J^_P@s&;{HW0hg;z zwyHu+KB;OVDf*!(>HVcDZjDrkK-n?#onNIo&KLK{pi`i_V3*0nm?QlZ{^tgKJ>TOU ze69M3l}+7OzMdDXXvP~xU-1#8uSvI0Q^TRjj8}~q4Lb`%(#SzA|Nk}1x>M{oJQaP} zx$V?Gs!s*0k7^K#@?TbCYv_B{ycjz)4VN5AEMN^l=nGg7#<#AoXM741mMx)og-8gf zf$7U-(z^<#8VP=B(_*;`8z%?016R)Qg%xRaAixz3~*k%3rTNGy1MPw*SQDwgvej<6@$XjX5sZi8c z^E2CQA=l_%L_HK3piW{xnx!fyGeGx=Z?W8y1d*)bddf~E@&bbPG*~>xq$B=VcEqG@ z`_%j2kyZlA6w;=zhC0%eS)h2@`iZ@Sh6+3~YN@Q>s4dCHOT{nm$cnFrci*V&A4Bax zs==Iai;Z97cIlu2#{~0IJ2WyRomiy$&Mr?mH43EquK0C)!`I5HUy*H5iE0qPZ1HFhsk|u{H6U`1v-`we&e!z=6opdM#86mzu*qx_A?{$lm{jU@KsrsWV|gs%ZM91b z2u7|qCn0fEV0tWa`8nxj^^ti764N<2nPO8)iC{6ut95b~A?+G?Ekgt=kJs5o_QrYR z7rEz#aLJU#;i6Etw8)tBIK9mN81&p8YrG5V+dexZ0Y!gzXu@_EP}I>>60BX4k-G)y zcBuH?45GhRZ$67~uHv2*x}z%ycKpVx-jv$>G5IlvhnTp}<(Fpa;`9yhLOr8rN^UlulBi zAc3a%KO{|H--vI@a{ZZcEpjD24?cpq)CnP=_4$?ZI*^BT&^0^P_J9k zXihz5wWL$b2*vVI)08YtQwXy-s504+eo2Pld{Xs>eX_mo6WOWZp(hwa90qV!?0cl1 zNwJO|@Tc`aL-8B&HCe8=gp&y9G^2}nmZ<%Aoc^E$JS1&hV|FSSKi4&Wu47!&NTC8R zZ8DvtH&g8xNgFeujeNF+0v$hvOY$WCiaarQA$xrEHkQ!#?HEmd6LSvr4&TFH)1)2= ze1u6l6;wEB`xL(r77vL9)^6oRiAwp3YD2nC%y(ooBlz(cwu< zcPBdBp}mX_G$l@R_+{xjoHO*}FK&Ha=!pK1ZN8yCc(GdSR~t#vrd%Ay;_zq{pACU) zlQr-sYtZh&)6WpwourW*!@)RaSJZ)M@I*U|! z7QTGQ+LbQw@+7p-?$bvW^2Mg*i|=wb+(v+haBbaXufLwX&R~$11 zN!>pDi`(5eEcr2gS5pHFiqw%o-m=$d0rOf>^s5t%ct$lHYvoXyEmP}J=#|H}_(dR6 z^0484J$^ragkN{Z7rA3IPm{DTHt&q#-Zeco<4omGyM9SjVN6BK7R6_|>l$?j{0{jT zps@ey9DjEf6d@BHjky-qmL*JaZJ~Q2C)pG2o}tg!Bmu@VMgSah#x}WQoAQSWAQIm2 zxX^p{IQic%$N=)Hvn|w?V)yz9q1+W}cx9Vk;;V$BOGimo7n-6!IL(bJ8ONIR!`*+g z1Ga-e=S|d#<4A<8TF=m{N&K@K$a0nH60?Bbm&VwGPi*%zYo;8!@)~L^m@K5{_4J6c zYObP0eqkZfpU7&r;E0NAkti`T8qAbX>GN}dNv%@&tN~x5v5}H5M;#d5fH2i>P|?am8!m+VS<*tax4xvlV3DQT5x`^h{h4Hjr})bY2{HyxUt(k z)`2VlJvItIPmxO!c)bCG&s{M9|rX~V}mnQ$%8OA2~%kl8C9@Z zK>tkh(C3*IK~K1VgN=Gw25=6!SV<)GB0?i4S$IqDcnc^Zk0=kb%E?47exW4+(#hD0 zjh^sjBB`9hp-03qbZL{A1&E|gjQ=L095ZFCmnr3S#P?Nf@!&y{6?42x@C8?B$=J`N zg83%rI$5QQKkh4qeJC*b6`00-J5EM^HpU4gSr7)n)R=E5 zE5@=oK@i+A1ZBqNxrZ`i9)ehb2{;VS)K+*B&QoY5&UzU;mMFFXM|3{{9QlM*4*05{ z!_Bu+&?M$D62V1jxb-JcJFgS%kK9%J%X%A((c1r1h4;C=pJRv07!IbnqT^3X)C7&z zl|1)2!Q0;6CA{6m7pU1!20+%a-9Chxdyn7a=nvw zNjIR60mapNcG8h3gkCQNTlCkWnQTO$RSw@59!J=yr_VrCsMsd2;mvlPF6&kDi`{>I z-G8e4PRu2NQJ{P9*rriButEa=uXNvA)XErF_BN+~#@+vD^&V$SO!LR5Gv1>g(t*zs zCsFwDD!D?E0vg)XWFl=*hrzMw~y|n=7Z*Q6oP>#Lk82pykAd zfu)d+{#`45PJbs?lR|4dIksn1ZusmL20Q0_Jdz` ze|vO)58Z0@v(X{7~^7^g^ zmo4J^63ARuP$f|`Bx`BEqPEJ>oH>?0Kw3+uHrNN?Z=cdK=1Z!kMF(;#U+H?A0kd!Gn!6KnEMY~ira~4L%cs5{4fDxxD=dzl zc5QeoQMm2oSau!8-7y(LOBOBjJyE^Y-Jc%t{mu+v?whnTH-Eyjnnfe{S(G*y7bD5aL)8-pA`I_KH5N$`?O?s@tWVmmNL-;%B0j| z-u`8d#laRwHWF!i59VlQM=zDF_O%YK_O%Z?K)NrCWoSmvdVHVzjf}5)jBpypBZAon zboh#<4ukf_ABs(1)7!qL-|j0S9yu)V=f=Aq(a$~p<+9=;M|HIB^v~v*hll*->fx@V zz9Ndd>oC4Io>K`A^7b>HX8Rvn?OXp#{q0!)YTtkTVzqCGd+TEVc3+XnL$!Y|pRF87 zbC$G{LdAYlV*84YSH9%iiIEJ2p8cJ#h#2~SkmEkTdUg|joC89RFR6Ta^LS&C7aHF8 z>x|+*S^D3~DVN7*IpgzlakpUfMgQQ8ygfps5_-+((>g{OtocnG3tZgC1WCzAScH7x zS3r@ek3U#v{^d)FgtLZWBB|skT}IRfY`Eb2%59;c$PYU&3oRz;?Z%OPUG(&*NVXbx zLzonJl@yv{mtmpYSq;J|3$~iKKZGLSi9BFP{(ZHk2U4mSsR#!UB_=nA)>==| z11XWU;aH;ZN7ds*k)QaQVvyTsxD`!4@e^V=lS)iHvZKsZi3qk=OiR|+K*9fsqC zkZNJ1xCl41Xtq*&QhvZ$G;|-rS0Nuc=;~Npf8y9vmMRz1Q?TZw%%Eww_KPmzozU_B z98Yt}2U^2PWYI)cWRhe@P+|N&_O+SY$&!yz&HRlh^I{jnwocEXN6RrWS<)Vc6t7Ep1$9<61)v>`urUXHQe>lzkh6u`2KJ;p|e9E}iVy(jk3 zQAV=+eGhz{cTQGC@Ab;RsEU9OFJ8)W{1dfq0-w@>`$WhZx}PoN5}2uQ!n05CVWkwgl&lO+@2r94l?Mz5F_Q7ct40nyD5dp4AT(h|D zw61pUHo;W<>iLS?myT{l>b+=M^aG?`G?IqRP?Ng{|uJd*n{ zKN3ps6@Ru$OQ0u7VZWTo=591Ex<-p6?5VL5CiCAOwaTAhm%oz`nL2qIX`fdaPUaIf)Lz;%s&+sK2(yQ5p={37zLAnc9DI z+}V%(EXufC#1q_lM>iHZnruqE+IIuAf>xUd$;}Azee}*ofVw0dVzR~Tg7ImN;f}9&IcZ4U!-IvN1 zo+7KSMLZSzDe+K3yoqt|4T(~DNR-x@zE7y0AIi5Dmy|FovBlVf91EHEl{mt)y0`cp zk?F?NBPaxj6MAO(#3Y_Qp%l>J=6Gyxp8C$74zF@$Bt2 zPHCc#!&vvrSRQqB`x58S&AO?bm$7&)t_Qp4IJfK<0o?aw@9J1h#?UzL{rhEFM{)$t z7kq~Zj4jDh)%ErKD_^kT!=dZ~^rY@ zQf2Og9lw?2M%IkVqg_u)@%Ba}2F+S5{8>Asa|%a(k^O77u?GZ={1EJgZ1-=Df*; z1{U#h#Udg~mf(VR_vj+&#l{Q8FLS=PUSF5B)^{0}>ToaRRI-4j!4^-0ISRds_*pBZ z`Bk8t@t$#deiK|58+9x!%@&LelRGi$2WwOl#CW~Ps40OVGThV#gqjj*8<~`d&HzJ< z8Z`P*4kosTp1Ma*&Pu_n8lcW5#R56Nqn%aDCLqjV=SEdYS%QWJj2vkvaMOx=AS~9a z(qz6Cwp@m)BRlabl^&AAWbM@wAQYQM)gt+dy-IZ`q?#)I2Xl<*!CA7=x|&{6{yJyj zKLtd^<)Q3Ypho#EmnGUu%-3MV75E3;|1?8jpOh;GvqUPAA9ew;C(T^<6F3B_+khbI z{?d?8PG%%U+PRkU82^YsWgM#AsUoj~f57Y}Jd9fYxh>nNpT6yeoV!Hg$lwg|(O#M$@8nRw3KmDXe&ZJa54{h*r9r9^UBorPI?k%=50z6V?BqG0 z`eI^pTK52=d#>v!kFrCG{s zLhS)$nq0%KZ#sDakP=F_z<80)X+meil%Mya`b4SuoBz#5A=xurs3>|`r~bmHJ5@BX ziOv%3k7I_yMi{H0mxYjzTM^R{a`z+KrO(1S1}kCVY+3BAa;NEe?eBi&>0*>|n+V=4 zGuc{k<(!Zi3Trp5p8%_?8<&MXDSa-{fJ z&SU%v@LvI%sntb%eoG}z{D4p_kDgK%zmFR^5(qBbOiUSTbe@VQN<>vmq6~wC-Ivob zxCdn^MM{!>+9Smb1&v8kXgs&HGEV|JIg`tMBPrTQLHwcO`IdstxI{;tud;UyG{wC) z1$AZ3=_+!r;c^Y^Q6L0p0AM*5Z>ohG`6~9{f(+=OVSh4G+MN2gl zmh>-+UE(^NdQI zdzpOgh0WRaB-a`_f;pl}4!d-!d}|NwT=z{S zz@})TvcL8=Mei$uE!+yF5#4cqBWho~_bA*%pE;dyDaqGUE>GIC`UH7I-R-Y@$G0uO zOSIQh{JKl*KZIEYV*cF^QA6ry+jZ*<0cPC^QJ)!;R144#)8rgQKeEAZkYN%osrw~I zQB3=`^(P~XBI$?n2C=c+MM?r+&m#oJ>pZSHH`FiR7+cK4rIkPJyq=sk**W3XKo^?idsf6c=&~|Pu;eG;E2LW0Pxzp`C{i12$!IYDpeC<= zweKUB>G)A{5cGHtX$A?;pW*wQ2sS&!TK@{)v&h3fj?kkf0W=i82fjsgPKnxbQS`LO zxASYf`JTO(Ge9>vqrbmj3AJapM5arrs%TZ@eY!|;mjdsW0vRXEBqv?qMhf&jDU|RT z*u3w_;i0L~v-d~jtTj6uq@al-vI$t(ihcJQLG22^E>MgH=#r#ns$63BS7M@^~xd!Ak6_JEwZv}yovUDM+3L)rw9x8RP zy5FMsY7Yj9kAn9_)>dSj>s_R%qARx016umyGdPG9+WuwcbYM~ev_|$xEB6TEqMZK6 zokjFufz_g)1E=n;!YjJTIpF(TpDf^=y$Glf9z8n>nN`4pdT`r=(&kBm?TW?AZr0gE za)#;LR{n~#hbd}N zM59WjQG{0UUdX*#YR6r6%c}a8+L5I4<-AOh6jJ|D4ZO+ZPDbP^2{nW4ko?v~QS@U1g(O$y^D9ejyur*ihTopN6pm95fMFI_8gUaGcOJFJK z!7Q?3XbNP>g}vda_$yLbiw3}%W9B4JBU`~~(gf?tgEXC=&fVLM+pV3g|9^XA{3rifFkdXnL~ML5k%;bIc!k;w1U#!9t80 z)qKFI{8eE;45rixR&`+w{xCii+J!Vmw=#TnMV-M6D0uj_coaIa4@mb z6ekO^Q3l^`$S6|R`-I4y_WC~8!2|XL;xD181xJ@uRar`Vf%%jcSA5Gwp@oFm-XFq6 zO`*jW_L*VE4hScM0^yW?7r$2G*C2{(rxtH0oR%4m9l&F1W}P%0auBb<{61&Yk&yX= zIQuHVS*5Q>Ji0Qc5l<$UMHBAzxm&x+&*Y5C85JJY6zY+}6GL0^A9hrzZ55G)f{Kt7 zO^ot=?gmLc;k&d4Vfhm+qe9P+qTcoaQT^_`8AR*+jR3(RQ3r~JstI3c8P#YWX1XB6 z(JRyI!rNfGU>FW_acJ#w+-{%hVfX&k$H@U2+t`V z2)0aaG_O`O8%hpW7N6eXnb-Zn;3LoJIX-0t21A9Eu8O-sNymOgF_K6mj({aGs1XLel(TQ~HY#DV-aS#y z-zz)DfKq0CY6omRwNghyizv8Q*=+~SKK9Yk#D%g4Rkp%$;2G@-YdY`%nQ z7YzM#k_R&Y`|CrGl84&dI~DpOxx?h@AlCIMV*TzTGT&9eHm3H{b2O=1R4s6eQ9Mrx zMi&$YWfVaEO?;}Igx z{u2gdwZS;aIlfWT5iX0e8Xb_IvGS8y)>}2iIk#|@p-g3n%o(0TS0^xXi+D`YAl*6S zk8bumeE-zTm!Rndi|$-2AWDN&qghXPDO`XV4i~UR~X2&e9bP z=O!XvwCmW8ea3j1Lxac;wwd`jY)oL{+p*-oG0Zf-C(+Su7;}bK#l&*SaVKk$&-b@v zo4?@Gi+~{{U|2{4c1z-Iz4tSL9a=MMkIE`BGq(A+0s=FahHbAo9@~6ILn9+{kNJYM zcsm_r{+Gx?;}w)tZ2a2;R_O#);X8d#48Q-&D^6YWek}4^yMdo1jxlg<&5X^zO+=2H z?*T35K9S#=F$$6TPEhsrhIUDP9}}2th8|*HHC1YQe>TffKO;xgUkN963yF+yrm@67 zbR$2U`b1rxmlB7mf{_0`yy)PEky4YvJCnf^e|c61M-v!7K(nu@XJyi377z|ew=n7{ zI_u8*M)}Y}QYdt0G~tTO(pgVWWgVWRgQ`^}h6;^2Ck=eLmWJlvK;H?*GWtB`48R#Z zi2Zc42@y*|>wG{lD6kAZ$`GV#Y9L%fE+B)-$3}^zg6u?%ZuUAOnPv}on=0$ysXL?A zib8+o&4PST3;f0N5W2|i=uPBR^9T=^!fi=ES#3R8NF7@)g;Eg&DadTexbvlJ_w0*_ zD(Z3h&D$%#SAJK1Cp{*=Qy=Ab_0GH_>Xvg*-L8B@-7+6ixA70E+k~%hd+3Xqhq&Qu zbI&vC?%vbO9VeZ8UX=H3Zz84+W^xu5Bhl&DEg$zB1P=x?_wg$gAIyA)CmE-MnTM>W z7Wcw?dL4t^FY+z}PQM$9*u-XMp60#pIL9{(Lww{t^d{gnj{~OXrR7Y+qs{MOc=TSh z`Ls+gO8|gTbGoeNy;#i|w%qV~qdb)Ahj`6NB2840hU0V2l+~Opt2tBF_k6!Dva#vM zbAMm;G@6-20*em!wwEKHGQYy$Hx5<463N~_kB>uR2pyF(cBaG8cicbvP)t?!{a2U8 zR4teGhmJyy*q}lw`q>=C;JCAY-*LkG&81Ki9Oy(nh1d(4|1FuzTLy2^B#crUjagG% zhkQ>?%Z=CNyN>XXbz&Hi!IB(lBSU*A z-wf~`G&}f(QifeO_4b?h@Uqv5sgQYuJbQZYWVrR4>j-60L&aF3pR>({JPf@}WOFW0 z=574&LetC_p&=A}nZkWFIOgQa2)4#X12nWC5y>~-5n6NSl791RDnZyAdXF}k?WJ^T)2N_kG;zoZ{Gt;d1tiwOLCg_tf9u?!l&Z_Z~i z#i<~7{5&t&%{1BW977%B@d@JrlP5J}KoUpm@r=uGS z9bKQWNc>*s(A7rGNe+X2erq||_WHikhb=v%&5Cf-d5lT=O;`7{A<@bkO^82HW{I1^lPmiiw{6TelIIM2l+STow zYt`+0A#M+Saoa=Oj2d@r+gEsq%fll)461U4ei>C~T7t1{yVTdmq$WkwgcMNc!5VQ6 zQZ-m3+9F4+=J7;cA!8jkE(;UyAB)#K7OOd}TX6X?sLa_IFA$O)=+Bme0Z+?M<-47iH5s?Sz(yIbSVQEE=zzQO-HsDtzeL0gy#-#bl0E)@T0ajxRyIHFdXN@!-;bHP}%nPAsv%^3?hq+K<`tM*7b z=}Z|Vn7Z6Uk0m0xn4c@2*%+#Ze}+U{G%poIK3D*X%g`L3D{YLPER5zjIPC|0ip{Zn zJ>LSB(dJ=%J5z_U=i{5-toMDxcz01OJGQ|cn}>g*Sd-VNc@t?QR`cH0_gllwYmFLn z(wf%h+o+dOb9~aw=GF-CM%vOz<4fMe8F0;+Sf_jR;OLsuu{%7mdT(R~xr`saHQ}4u zo8@$bubH|$E5i{knA$G4ys5z~mm@reKyKLtx*UR|{cS`uK)`E5(~ z!q}WM6*cd5-M|S}s>Z5V%A`1B&KXKHmK=Y--vK1TW1_EQSXsD?CBwS~3eiIugP8&t zL8d(t@_rwq*kR33520F-uOv@>b$G&?B$mS;zPIdopYuqp=47m4SVs1o;g+nW*9=q+ zM4GJZJvsx>S7&glKM<~r9t!ASp)IOZ=!)yeHAgFt;v}r*`*x`t zr1klJ0P_F;{eKbyFXHm=8Ln@0g}A!8zRD$krLr={-<$V49Rpn7<@zdD7uRyG*<801 zx19UsT;)8MaJ7=Ii)$y>LtNkGdYtP3*MD*y=Njh9BJMb@vs-jI3kZ|H1@}1}*Ktkc z3UH0%`UCM!b7k=C<&r;?O5Ja6bUJ>|b%ra8^lgNNxjMNn<-&DVH(1-v%(Nx^ zjd@QuSl-RB@6NT#{4;r$bU7W5a>?Ix>;7l%A+8RtH@V!K_4jV#=kvbZs>@EoTeyBt zSO!-w&yR5xa+Px3!WHC_zr%b#!1WZ@Yg~_#$8zqCT(h~J;Qfe|Z#nP(!t)sJV@Ut+ z+=sd3uZ#QTggwZ8rxo@NasQF)Ev`>mVMl%h&HR{ubLti>nAW+eb!B7OvIT3_HFt(r ztZNV4RC-g{gd0mI-1wOVHLag8X9iy zkh~YPwXazjpt$e~K;3$GK;i^ehKNJ)9i4&Diq61Y&CRWWa7Uzd$? z_O;FOjd*j(LIRVmUAdx_h=IEzt5-L7XxP-(&kn5V479d|1D%of_O=cSW_Fv~JKDl+ zE8ErvIsp*fJFi_b?+j2{U<18+x9-#RdKfqyR@bfsqU5BatswJIb4S366KGql(l{KS zZSH8JyIb!LhXNbctX&%jD`3(ttNUof4Be3dij&4#Zx%2DYwm8PfzrwAR&1PZHEWLD zxoZG=NMNFHA<$-3eO;t8EMT~0YM|87(0Wg6+lE$axUFnk)yyCzzZx}OH*_$1Xyf=* zs!KbmCUxy>Z3i#HtBGdC8{ZjN%Yc-qYPhF`M_SMM;&7~-G{G^xb9_Y7ubhyKy9zX1 z*S0>bvjeL;+SY*qNffu^T0v=1O9CA$rRKWc9nh~f!QBA;N96DtaQ(c@q*a;>byBpX zuzHN~E8}xK$aXLFyO;Xvd`Xe47gPrG>;&60QPABDb z0+3Dt5Pu!~c3iORHLW8D^7*Ax_PLQhvh-xz6ofiKsugPmYXlEwtXR8NgIOUFxIeoM z^bzz=gHkHqNFmVz-r2tWr8eou= z5n$$mNJ$*qutJj*fiQf2MSF;0m%<-l^ij>1@nmw z*q0!J5?M3~+I%9(1_K-5lZtmLz}Qg(6H)|6r>8s(OEp+iU|zXt<=W<=YtK)h3$zMv z7P3wj$Pa62Uq>@6xK*Gigw`Tu(>gjB3YvE%dme1lY(?q=tM3$WDe9KQcRh}qg&Wga zi08R@{ZZrG#{0HbOS%aNb~Ow6ajdZMjdE@5Iw#+AtLm6BZ|>Y_Gw!fj2R^K7MsB*R zb7jXG*q((Rs=ty*B+a)!K$B8qqBEK9!f0vNB^B?&t!!vb#jpz!jQQvqq{c)fE$xMK zPsL5q-}AuYn6HW4sx(PL(W+%Dl@JJaEAr^*T-)5-&X^YXgH$V8>-9L9Ov3Z=JcYu@ z8zE%+weZ>zF#PLyrH}~5*p6hO%a?ND+al)%g+(AWGD`p6(j^z@kcG7?E`-!bf?;s2 z_0y~iG_M8eI|6IM*9Syy)XD6jAozIKik0_hx`w>3WWr@D*S2*wTfmXBM7p=>y3h(( zuoCsu8~`Y57ODUX9ll|~?0S$+3uhK~Io3_MWhMVQbeSehQ*D|7Uf0Z609Aq(N!6vm zAk|_bRExOX#=J+w?c}JgpEu)h=FguupZcZKesItdlx=#OhC><_n`T%nbtDsk z5~NtELv;{5{Nv(unfiP8R!zec#}V$KSh)08uwgnwSKa5=F#95Z27YojC{#cg^*Pij zS)~#!$y;*|-a-t=0TA$KeD$+t{GUM|lER~HTc-!2W^K0sZ z7CEshN59|07-A-cOo6D3kby`wLZ}e>ndAs1X+u&EJe_YHtVt#YaA{CMPlW!ST{#QF!zqR^P1+W`K%iI z?P@HR*o~Ap_Mg_=$Zk9RH>d8l&Q(-e(n-j8g>4gV30S2h=?EVb4W10t0!8CjU2Dgk zGC{=^pmwa3uOstFgrC(%Lh{|Hq+BUQfOtz(Ye7XKQ(InVq z(x#7@-$&)7VWaCMpjR|f)gT?7`a6W}Bfjf;j7Wb@9Y>^1ezWtP{a5pa2GEa#*Z z!kGMcZhzU-Q8!8@PPJDL7Q2IzIAY^ddN~)soAc70i#y5mz~+3sNa6eiaZ2Z&tW$CX z+x?z~M+$pNg?FYSkLtm{+2^S-VmHT8uO)a*sM5&onpH6GHLKSkYK-qJN#_9V&4jI3 zwTejtf>>u|z@pi<*q~HeR(pZP1>Nb_;n; z)*nWMPxcS1ONBc_k?<-Mk@7rZ?2K2X3$LdwGLVu&lY_yc zqfSOT3AeVj-eoCv$fLb&?OMrezMA18o+`zrCB<3az^nDZ5UtJ1ki<>BqX$``XvAHc z!p)t5=8f%2nXV8Lxa&~#rb(oBG}Zr~_P#tisw&(21cD3_It&UZNCAmKB;n5UkRpSL zWHKorC|YEwkTjX7st}^6sGuNGK~b0sX%9*7w(2eOLC{_wF;Fv(G;J?6XhBUq&^gbYC+Xg5>VmZ2{Dt6el<00?|2CCc&22xYBxi7Nsn2pXBum-M2!` zYzfVvwm*%YNpy7&s?bU|Bwg0Wt_F2WN2ebQKbOb#W}H&qWhVmjRaBySPGLQm5OqAO z|6+sZ43^7tUJcx%2X}k?|CG+zNTUq!m)~6*0!>hO9Ss{}Ll+I_*vZqe5wrV4^-VaN zIeKO=E0|S~pHmbF21@-|Ii$FVkXD4HMlz^b>s)byh5qtjL4HwA*-wNQeqD}s3W zh0_X&lQT`_e1V>moey%1?tOutUlbqT`vRYr76*#6ifNW!nl4J`K9<(r+tr%?(+Bzc z&VB+owhH-*-AM<52OdZN%uH>dIz(P-VQQI=kc`v}zP80jCZuKvjy@h4Kr)aY&NSRb zh`!^9iwZh#Il;t+Z~(D$73>VadVh|S2Wm%l_sC8nZ<{(VrxC{9bL~alxN+wqu2}_D z2)D(~Mvzm_^N4d7ji$9X#ZvWgc1hrN|4g(N(B8}&y0fu1}r^t?YX+=Ly^3~#a zHx!0K@5&7KGhTv1Q>bad-q}51qj`DOYFE%X=3Q43duB{eKDm7aQ|^LLb2Q51U9d z%J?a|SI^R%+?>*!A_z1muD=ZRD+`ojU*ddc<)8(f63OX8+A4q9xo9yt7QCvs4CMy= z`9(qhbbo$Lu*%JQ$yp>530@|R`@yA4WLHr>cw zV^tPkQdyqA6rt^X22z*3N8k|vbY3i#iH{sD+^N%J_qQJ`gn7myKlQ9 zTmds+wX67|0`cE$k8cj&*I&$sRLSo=SqMicJ-)A?thhU) znBL=~QW>j)XHlr|5%5abR|LNk>IqR&5s9>7OX&0i zv`sV#r~TB$C&oW5jTQ5u)mBv2W3)YS z+eFKn3X`tS@7Q!>N2kc!Jwl9D%%w^`5k5r!8PtCrYB~iK8P>OrI-q0zT{q@^BzA znayC62IRp_0O}L$Y}hp>4VczECyWJK^R7uu|wTJcw_Mn5%xx2vDfy5e3`+;Rx(v^;eI35BSJaUZ1^U+9ndc^B0#B*CX zcWIa_t}Jr|vDa@Tf(~kpb!~bDoWz>xaK)A3NkGccUP+XvyXsKWxeJU{q$@?IJv}y{ z=N#yy0qEuhZH_>p^U*uDn^F;hix};3^m)v@?TzeDf{c97TXRlE9x7d((v=ATWE3 zS24WjPD&sdaLSCPG&uGal9@n`AvmLs8ZJ_tI}m6WNkq~NhN-H-0S&H#y5Cu)ft2zr zKY|GEh{n_48H%4n>_*FX;^C%gT)ccI-sx_%5+~l7?ldPJ!EX#X^>E5{b=^eJ2BNGC zm*&Jv25lS8Ml!;#?FQrHrgOeiA`4tHLBA4)rgiD_o9d$tbRJN?p#0&{ufP8K>PmVp zB{atJiB_fL`<;5?B8rzcrCsfmZ^Y9AZoEv;=y+s=5ij7z!+l<|l+v6Er7XZJIRM&m zY|=AAZY6iNz$ZBY-|l!&b$}e3pFoBIdo-TXPJvB2XXGXN!k+2e6zFBQJ=mm(dY z1JIm?b_19RpaD7nCE&r?fM&pMz!AEOy&PfidZ~$-yZ5cE#fs6s@fI>heU>@KOzy`o?0e=L1 z3OEBu3MY`U00xi?s0FkGHUeG(d<000B#=u0QvlZjS^%p7PXc}icn@$Aa2|e~c^p6l ziz#KnbG!*mmFYKy0I zs_k(^>Qmn1Gp5ORRRzYp5?3DyG(nYewGr`qJh3>zO%-&9#qAEZ9+an!m<%yFX! z(eX5a^4nD?wkj~0#}Qzj@(iwBkzmJ)PeVV?u09O#{u=1JC0)+CrBIgQvPO(4j+1s= z6vv?2Jq~rKqOLaa9e|{s&eN^-U1TUV)GalZwtJ4*J=|G-B39?f^(@0{tmg}K?$nOm z@MnSw9H;*HK8;R0hqq;X#iL>8@Gzbf^F=!stq{)gar8K!v2#;<#HVhVxlLh@bEs-b zD+>+GGb;pPR_IBpYHD_ijEmOE>v1&jZ&6D_C|p-H-SO`4k#mn=zst+r(|ccM6-Q@{ zTgSGu)4>-i7TB}u&0Zq7w={MuuZ`|8<72l4QAPk>D-IDn9cgOJ!f8@48neUGYaYR9 zE!Eb8uHV0VEd~3xt^j0GSKW<5&#KN55odwnXt#*22fY_{M=O_la;RkucCJV$Xl#?h$@$ zhwO`^eE}MN%bBc+qYcFUu63Z*$I&uCi-JbtXhop)11&R-)(jdhI+El#+P$ES0BuAZ zZ6|0x(00VpJ_2nQXy74p*R+

MU1-Af+YP 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