You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
linbpq/L2Code-skigdebian.c

4144 lines
85 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
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 <I C P R1 S4 Pid=F0 Len=236> [17:08:03R] [+++]
<EFBFBD>J<EFBFBD><EFBFBD>ZK<EFBFBD>)x@D<>B<EFBFBD>rN<72><4E>4X<34>;i<>#C<>M<EFBFBD>,<2C>нҼ<D0BD><D2BC>r<EFBFBD><72>O<EFBFBD> N<>X<EFBFBD><58><EFBFBD><EFBFBD><EFBFBD>5<EFBFBD>(<28>|<7C><><EFBFBD>#<23><><EFBFBD>U<EFBFBD><55><EFBFBD>cY<63><59>l<EFBFBD><6C><EFBFBD><EFBFBD>)<29><>璘o<EFA7AF>ȼ<EFBFBD>><3E><>9<EFBFBD>*<2A>G<EFBFBD><47><EFBFBD><EFBFBD><EFBFBD>(6<>5C<35>!<21>L<EFBFBD><4C><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ُQ<D98F><51>p<EFBFBD><70>IH<49><48>;<3B><>i<EFBFBD><69><EFBFBD><EFBFBD>><3E>9p<39>B<EFBFBD><42><<3C>c<EFBFBD>EP<45><50><<3C><>{0a<30>(<28><><59>M<EFBFBD><4D><EFBFBD>N<EFBFBD>+<<3C>I<EFBFBD>[<5B><><EFBFBD><EFBFBD>Pw<50>[^]6<>2\<5C><EFBFBD>9<EFBFBD><39>ov{<7B><><EFBFBD>Ÿmm<d<>^
1:Fm W4DHW To W4DHW-10 <RR C P R1> [17:08:03T]
1:Fm W4DHW To W4DHW-10 <REJ R R1> [17:08:03T]
1:Fm W4DHW To W4DHW-10 <RR R F R1> [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;
}

Powered by TurnKey Linux.