637 lines
12 KiB
C
637 lines
12 KiB
C
|
/*
|
||
|
Copyright 2001-2018 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
|
||
|
*/
|
||
|
|
||
|
// Mail and Chat Server for BPQ32 Packet Switch
|
||
|
//
|
||
|
// UI Handling Routines
|
||
|
|
||
|
#include "bpqmail.h"
|
||
|
|
||
|
|
||
|
char UIDEST[10] = "FBB";
|
||
|
char UIMAIL[10] = "MAIL";
|
||
|
char AXDEST[7];
|
||
|
char AXMAIL[7];
|
||
|
static char MAILMYCALL[7];
|
||
|
|
||
|
#pragma pack(1)
|
||
|
|
||
|
UINT UIPortMask = 0;
|
||
|
BOOL UIEnabled[33];
|
||
|
BOOL UIMF[33];
|
||
|
BOOL UIHDDR[33];
|
||
|
BOOL UINull[33];
|
||
|
char * UIDigi[33];
|
||
|
char * UIDigiAX[33]; // ax.25 version of digistring
|
||
|
int UIDigiLen[33]; // Length of AX string
|
||
|
|
||
|
|
||
|
|
||
|
#pragma pack()
|
||
|
|
||
|
PMESSAGEX DG_Q; // Queue of messages to be sent to node
|
||
|
|
||
|
struct SEM DGSemaphore = {0, 0}; // For locking access to DG_Q;
|
||
|
|
||
|
VOID UnQueueRaw(void * Param);
|
||
|
|
||
|
static VOID Send_AX_Datagram(UCHAR * Msg, DWORD Len, UCHAR Port, UCHAR * HWADDR, BOOL Queue);
|
||
|
DllExport char * APIENTRY GetApplName(int Appl);
|
||
|
|
||
|
int APIENTRY SendRaw(int port, char * msg, int len);
|
||
|
int APIENTRY GetNumberofPorts();
|
||
|
|
||
|
VOID SetupUIInterface()
|
||
|
{
|
||
|
int i, NumPorts = GetNumberofPorts();
|
||
|
#ifndef LINBPQ
|
||
|
struct _EXCEPTION_POINTERS exinfo;
|
||
|
#endif
|
||
|
|
||
|
ConvToAX25(GetApplCall(BBSApplNum), MAILMYCALL);
|
||
|
ConvToAX25(UIDEST, AXDEST);
|
||
|
ConvToAX25(UIMAIL, AXMAIL);
|
||
|
|
||
|
UIPortMask = 0;
|
||
|
|
||
|
for (i = 1; i <= NumPorts; i++)
|
||
|
{
|
||
|
if (UIEnabled[i])
|
||
|
{
|
||
|
char DigiString[100], * DigiLeft;
|
||
|
|
||
|
UIPortMask |= 1 << (i-1);
|
||
|
UIDigiLen[i] = 0;
|
||
|
|
||
|
if (UIDigi[i])
|
||
|
{
|
||
|
UIDigiAX[i] = zalloc(100);
|
||
|
strcpy(DigiString, UIDigi[i]);
|
||
|
DigiLeft = strlop(DigiString,',');
|
||
|
|
||
|
while(DigiString[0])
|
||
|
{
|
||
|
ConvToAX25(DigiString, &UIDigiAX[i][UIDigiLen[i]]);
|
||
|
UIDigiLen[i] += 7;
|
||
|
|
||
|
if (DigiLeft)
|
||
|
{
|
||
|
memmove(DigiString, DigiLeft, strlen(DigiLeft) + 1);
|
||
|
DigiLeft = strlop(DigiString,',');
|
||
|
}
|
||
|
else
|
||
|
DigiString[0] = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_beginthread(UnQueueRaw, 0, NULL);
|
||
|
|
||
|
if (EnableUI)
|
||
|
#ifdef LINBPQ
|
||
|
SendLatestUI(0);
|
||
|
#else
|
||
|
__try
|
||
|
{
|
||
|
SendLatestUI(0);
|
||
|
}
|
||
|
My__except_Routine("SendLatestUI");
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
VOID Free_UI()
|
||
|
{
|
||
|
int i;
|
||
|
PMESSAGEX AXMSG;
|
||
|
|
||
|
for (i = 1; i <= 32; i++)
|
||
|
{
|
||
|
if (UIDigi[i])
|
||
|
{
|
||
|
free(UIDigi[i]);
|
||
|
UIDigi[i] = NULL;
|
||
|
}
|
||
|
|
||
|
if (UIDigiAX[i])
|
||
|
{
|
||
|
free(UIDigiAX[i]);
|
||
|
UIDigiAX[i] = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (DG_Q)
|
||
|
{
|
||
|
AXMSG = DG_Q;
|
||
|
DG_Q = AXMSG->CHAIN;
|
||
|
free(AXMSG);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VOID QueueRaw(int Port, PMESSAGEX AXMSG, int Len)
|
||
|
{
|
||
|
PMESSAGEX AXCopy = zalloc(400);
|
||
|
PMESSAGEX AXNext;
|
||
|
|
||
|
AXMSG->PORT = Port;
|
||
|
AXMSG->LENGTH = Len;
|
||
|
AXMSG->CHAIN = 0; // Clear chain in new buffer
|
||
|
|
||
|
memcpy(AXCopy, AXMSG, Len + 10);
|
||
|
|
||
|
GetSemaphore(&DGSemaphore, 0);
|
||
|
|
||
|
if (DG_Q == 0) // Empty
|
||
|
{
|
||
|
DG_Q = AXCopy;
|
||
|
FreeSemaphore(&DGSemaphore);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
AXNext = DG_Q;
|
||
|
|
||
|
while (AXNext->CHAIN)
|
||
|
AXNext = AXNext->CHAIN; // Chain to end of queue
|
||
|
|
||
|
AXNext->CHAIN = AXCopy; // New one on end
|
||
|
|
||
|
FreeSemaphore(&DGSemaphore);
|
||
|
}
|
||
|
|
||
|
VOID SendMsgUI(struct MsgInfo * Msg)
|
||
|
{
|
||
|
char msg[200];
|
||
|
int len, i;
|
||
|
int Mask = UIPortMask;
|
||
|
int NumPorts = GetNumberofPorts();
|
||
|
|
||
|
//12345 B 2053 TEST@ALL F6FBB 920325 This is the subject
|
||
|
|
||
|
struct tm *tm = gmtime((time_t *)&Msg->datecreated);
|
||
|
|
||
|
len = sprintf_s(msg, sizeof(msg),"%-6d %c %6d %-13s %-6s %02d%02d%02d %s\r",
|
||
|
Msg->number, Msg->type, Msg->length, Msg->to,
|
||
|
Msg->from, tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Msg->title);
|
||
|
|
||
|
for (i=1; i <= NumPorts; i++)
|
||
|
{
|
||
|
if ((Mask & 1) && UIHDDR[i])
|
||
|
Send_AX_Datagram(msg, len, i, AXDEST, TRUE);
|
||
|
|
||
|
Mask>>=1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VOID SendHeaders(int Number, int Port)
|
||
|
{
|
||
|
// Send headers in response to a resync request
|
||
|
|
||
|
char msg[256];
|
||
|
unsigned len=0;
|
||
|
struct tm *tm;
|
||
|
struct MsgInfo * Msg;
|
||
|
|
||
|
//12345 B 2053 TEST@ALL F6FBB 920325 This is the subject
|
||
|
|
||
|
while (Number <= LatestMsg)
|
||
|
{
|
||
|
Msg = FindMessageByNumber(Number);
|
||
|
|
||
|
if (Msg)
|
||
|
{
|
||
|
if (len > (200 - strlen(Msg->title)))
|
||
|
{
|
||
|
Send_AX_Datagram(msg, len, Port, AXDEST, FALSE);
|
||
|
len=0;
|
||
|
}
|
||
|
|
||
|
tm = gmtime((time_t *)&Msg->datecreated);
|
||
|
|
||
|
len += sprintf(&msg[len], "%-6d %c %6d %-13s %-6s %02d%02d%02d %s\r",
|
||
|
Msg->number, Msg->type, Msg->length, Msg->to,
|
||
|
Msg->from, tm->tm_year-100, tm->tm_mon+1, tm->tm_mday, Msg->title);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (len > 230)
|
||
|
{
|
||
|
Send_AX_Datagram(msg, len, Port, AXDEST, FALSE);
|
||
|
len=0;
|
||
|
}
|
||
|
len += sprintf(&msg[len], "%-6d #\r", Number);
|
||
|
}
|
||
|
|
||
|
Number++;
|
||
|
}
|
||
|
|
||
|
Send_AX_Datagram(msg, len, Port, AXDEST, FALSE);
|
||
|
|
||
|
}
|
||
|
VOID SendDummyUI(int num)
|
||
|
{
|
||
|
char msg[100];
|
||
|
int len, i;
|
||
|
int Mask = UIPortMask;
|
||
|
int NumPorts = GetNumberofPorts()
|
||
|
;
|
||
|
len = sprintf_s(msg, sizeof(msg),"%-6d #\r", num);
|
||
|
|
||
|
for (i=1; i <= NumPorts; i++)
|
||
|
{
|
||
|
if (Mask & 1)
|
||
|
Send_AX_Datagram(msg, len, i, AXDEST, TRUE);
|
||
|
|
||
|
Mask>>=1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VOID SendLatestUI(int Port)
|
||
|
{
|
||
|
char msg[20];
|
||
|
int len, i;
|
||
|
int Mask = UIPortMask;
|
||
|
int NumPorts = GetNumberofPorts();
|
||
|
|
||
|
len = sprintf_s(msg, sizeof(msg),"%-6d !!\r", LatestMsg);
|
||
|
|
||
|
if (Port)
|
||
|
{
|
||
|
Send_AX_Datagram(msg, len, Port, AXDEST, FALSE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (i=1; i <= NumPorts; i++)
|
||
|
{
|
||
|
if ((Mask & 1) && UIHDDR[i])
|
||
|
Send_AX_Datagram(msg, len, i, AXDEST, TRUE);
|
||
|
|
||
|
Mask>>=1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static VOID Send_AX_Datagram(UCHAR * Msg, DWORD Len, UCHAR Port, UCHAR * HWADDR, BOOL Queue)
|
||
|
{
|
||
|
MESSAGEX AXMSG;
|
||
|
|
||
|
PMESSAGEX AXPTR = &AXMSG;
|
||
|
|
||
|
// Block includes the Msg Header (7 bytes), Len Does not!
|
||
|
|
||
|
memcpy(AXPTR->DEST, HWADDR, 7);
|
||
|
memcpy(AXPTR->ORIGIN, MAILMYCALL, 7);
|
||
|
AXPTR->DEST[6] &= 0x7e; // Clear End of Call
|
||
|
AXPTR->DEST[6] |= 0x80; // set Command Bit
|
||
|
|
||
|
if (UIDigi[Port] && UIDigiAX[Port])
|
||
|
{
|
||
|
// This port has a digi string
|
||
|
|
||
|
int DigiLen = UIDigiLen[Port];
|
||
|
UCHAR * ptr;
|
||
|
|
||
|
memcpy(&AXPTR->CTL, UIDigiAX[Port], DigiLen);
|
||
|
|
||
|
ptr = (UCHAR *)AXPTR;
|
||
|
ptr += DigiLen;
|
||
|
AXPTR = (PMESSAGEX)ptr;
|
||
|
|
||
|
Len += DigiLen;
|
||
|
|
||
|
}
|
||
|
AXPTR->ORIGIN[6] |= 1; // Set End of Call
|
||
|
AXPTR->CTL = 3; //UI
|
||
|
AXPTR->PID = 0xf0;
|
||
|
memcpy(AXPTR->DATA, Msg, Len);
|
||
|
|
||
|
if (Queue)
|
||
|
QueueRaw(Port, &AXMSG, Len + 16);
|
||
|
else
|
||
|
SendRaw(Port, (char *)&AXMSG.DEST, Len + 16);
|
||
|
|
||
|
return;
|
||
|
|
||
|
}
|
||
|
|
||
|
VOID UnQueueRaw(void * Param)
|
||
|
{
|
||
|
PMESSAGEX AXMSG;
|
||
|
|
||
|
while (TRUE)
|
||
|
{
|
||
|
GetSemaphore(&DGSemaphore, 0);
|
||
|
|
||
|
if (DG_Q)
|
||
|
{
|
||
|
AXMSG = DG_Q;
|
||
|
DG_Q = AXMSG->CHAIN;
|
||
|
|
||
|
SendRaw(AXMSG->PORT, (char *)&AXMSG->DEST, AXMSG->LENGTH);
|
||
|
free(AXMSG);
|
||
|
}
|
||
|
|
||
|
FreeSemaphore(&DGSemaphore);
|
||
|
|
||
|
Sleep(5000);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VOID ProcessUItoMe(char * msg, int len)
|
||
|
{
|
||
|
msg[len] = 0;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
VOID ProcessUItoFBB(char * msg, int len, int Port)
|
||
|
{
|
||
|
// ? 0000006464
|
||
|
// The first 8 digits are the hexadecimal number of the requested start of the list
|
||
|
// (here 00002EE0 -> 12000) and the last two digits are the sum of the four bytes anded with FF (0E).
|
||
|
|
||
|
int Number, Sum, Sent = 0;
|
||
|
char cksum[3];
|
||
|
|
||
|
if (msg[0] == '?')
|
||
|
{
|
||
|
memcpy(cksum, &msg[10], 2);
|
||
|
msg[10]=0;
|
||
|
|
||
|
sscanf(&msg[1], "%X", &Number);
|
||
|
sscanf(cksum, "%X", &Sum);
|
||
|
|
||
|
if (Number >= LatestMsg)
|
||
|
{
|
||
|
SendLatestUI(Port);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SendHeaders(Number+1, Port);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
UCHAR * AdjustForDigis(PMESSAGEX * buff, int * len)
|
||
|
{
|
||
|
PMESSAGEX buff1 = *(buff);
|
||
|
UCHAR * ptr, * ptr1;
|
||
|
|
||
|
if ((buff1->ORIGIN[6] & 1) == 1)
|
||
|
{
|
||
|
// End of Call Set
|
||
|
|
||
|
return 0; // No Digis
|
||
|
}
|
||
|
|
||
|
ptr1 = &buff1->ORIGIN[6]; // End of add
|
||
|
ptr = (UCHAR *)*buff;
|
||
|
|
||
|
while((*ptr1 & 1) == 0) // End of address bit
|
||
|
{
|
||
|
ptr1 += 7;
|
||
|
ptr+= 7;
|
||
|
}
|
||
|
|
||
|
*buff = (PMESSAGEX)ptr;
|
||
|
return (&buff1->CTL); // Start of Digi String
|
||
|
}
|
||
|
VOID SeeifBBSUIFrame(PMESSAGEX buff, int len)
|
||
|
{
|
||
|
UCHAR * Digis;
|
||
|
UCHAR From[7], To[7];
|
||
|
int Port = buff->PORT;
|
||
|
|
||
|
if (Port > 128)
|
||
|
return; // Only look at received frames
|
||
|
|
||
|
memcpy(From, buff->ORIGIN, 7); // Save Origin and Dest before adjucting for Digis
|
||
|
memcpy(To, buff->DEST, 7);
|
||
|
|
||
|
Digis = AdjustForDigis(&buff, &len);
|
||
|
|
||
|
if (Digis)
|
||
|
{
|
||
|
// Make sure all are actioned
|
||
|
|
||
|
DigiLoop:
|
||
|
|
||
|
if ((Digis[6] & 0x80) == 0)
|
||
|
return; // Not repeated
|
||
|
|
||
|
if ((Digis[6] & 1) == 0) // Not end of list
|
||
|
{
|
||
|
Digis +=7;
|
||
|
goto DigiLoop;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (buff->CTL != 3)
|
||
|
return;
|
||
|
|
||
|
if (buff->PID != 0xf0)
|
||
|
return;
|
||
|
|
||
|
// if (memcmp(buff->ORIGIN, MAILMYCALL,6) == 0) // From me?
|
||
|
// if (buff->ORIGIN[6] == (MAILMYCALL[6] | 1)) // Set End of Call
|
||
|
// return;
|
||
|
|
||
|
From[6] &= 0x7e;
|
||
|
To[6] &= 0x7e;
|
||
|
|
||
|
if (memcmp(To, MAILMYCALL, 7) == 0)
|
||
|
{
|
||
|
ProcessUItoFBB(buff->DATA, len-23, Port);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (memcmp(To, AXDEST, 7) == 0)
|
||
|
{
|
||
|
ProcessUItoFBB(buff->DATA, len-23, Port);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
len++;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// ConvToAX25(MYNODECALL, MAILMYCALL);
|
||
|
// len=ConvFromAX25(Routes->NEIGHBOUR_DIGI1,Portcall);
|
||
|
// Portcall[len]=0;
|
||
|
|
||
|
char MailForHeader[] = "Mail For:";
|
||
|
|
||
|
char MailForExpanded[100];
|
||
|
|
||
|
VOID ExpandMailFor()
|
||
|
{
|
||
|
char * OldP = MailForText;
|
||
|
char * NewP = MailForExpanded;
|
||
|
char * ptr, * pptr;
|
||
|
size_t len;
|
||
|
char Dollar[] = "\\";
|
||
|
char CR[] = "\r";
|
||
|
|
||
|
ptr = strchr(OldP, '\\');
|
||
|
|
||
|
while (ptr)
|
||
|
{
|
||
|
len = ptr - OldP; // Chars before Backslash
|
||
|
memcpy(NewP, OldP, len);
|
||
|
NewP += len;
|
||
|
|
||
|
switch (*++ptr)
|
||
|
{
|
||
|
case 'r': // Inserts a carriage return.
|
||
|
case 'R': // Inserts a carriage return.
|
||
|
|
||
|
pptr = CR;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
|
||
|
pptr = Dollar; // Just Copy Backslash
|
||
|
}
|
||
|
|
||
|
len = strlen(pptr);
|
||
|
memcpy(NewP, pptr, len);
|
||
|
NewP += len;
|
||
|
|
||
|
OldP = ++ptr;
|
||
|
ptr = strchr(OldP, '\\');
|
||
|
}
|
||
|
|
||
|
strcpy(NewP, OldP);
|
||
|
}
|
||
|
|
||
|
|
||
|
VOID SendMailFor(char * Msg, BOOL HaveCalls)
|
||
|
{
|
||
|
int Mask = UIPortMask;
|
||
|
int NumPorts = GetNumberofPorts();
|
||
|
int i;
|
||
|
|
||
|
if (!HaveCalls)
|
||
|
strcat(Msg, "None ");
|
||
|
|
||
|
Sleep(1000);
|
||
|
|
||
|
for (i=1; i <= NumPorts; i++)
|
||
|
{
|
||
|
if (Mask & 1)
|
||
|
{
|
||
|
if (UIMF[i] && (HaveCalls || UINull[i]))
|
||
|
{
|
||
|
Send_AX_Datagram(Msg, (int)strlen(Msg) - 1, i, AXMAIL, TRUE);
|
||
|
}
|
||
|
}
|
||
|
Mask>>=1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VOID SendMailForThread(VOID * Param)
|
||
|
{
|
||
|
struct UserInfo * user;
|
||
|
char MailForMessage[256] = "";
|
||
|
BOOL HaveMailFor;
|
||
|
struct UserInfo * ptr = NULL;
|
||
|
int i, Unread;
|
||
|
|
||
|
while (MailForInterval)
|
||
|
{
|
||
|
ExpandMailFor();
|
||
|
|
||
|
if (MailForText[0]) // User supplied header
|
||
|
strcpy(MailForMessage, MailForExpanded);
|
||
|
else
|
||
|
strcpy(MailForMessage, MailForHeader);
|
||
|
|
||
|
HaveMailFor = FALSE;
|
||
|
|
||
|
for (i=1; i <= NumberofUsers; i++)
|
||
|
{
|
||
|
user = UserRecPtr[i];
|
||
|
|
||
|
CountMessagesTo(user, &Unread);
|
||
|
|
||
|
if (Unread)
|
||
|
{
|
||
|
if (strlen(MailForMessage) > 240)
|
||
|
{
|
||
|
SendMailFor(MailForMessage, TRUE);
|
||
|
|
||
|
if (MailForText[0]) // User supplied header
|
||
|
strcpy(MailForMessage, MailForExpanded);
|
||
|
else
|
||
|
strcpy(MailForMessage, MailForHeader);
|
||
|
}
|
||
|
strcat(MailForMessage, user->Call);
|
||
|
strcat(MailForMessage, " ");
|
||
|
HaveMailFor = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SendMailFor(MailForMessage, HaveMailFor);
|
||
|
Sleep(MailForInterval * 60000);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
20:09:00R GM8BPQ-10>FBB Port=1 <UI C>:
|
||
|
103 !!
|
||
|
20:10:06R GM8BPQ-10>FBB Port=1 <UI C>:
|
||
|
19-Jul 21:08 <<< Mailbox GM8BPQ Skigersta >>> 2 active messages.
|
||
|
Messages for
|
||
|
ALL
|
||
|
|
||
|
20:11:11R GM8BPQ-10>FBB Port=1 <UI C>:
|
||
|
104 P 5 G8BPQ GM8BPQ 090719 ***
|
||
|
20:12:17R GM8BPQ-10>FBB Port=1 <UI C>:
|
||
|
105 B 5 ALL GM8BPQ 090719 test
|
||
|
|
||
|
12345 B 2053 TEST@ALL F6FBB 920325 This is the subject
|
||
|
|
||
|
|
||
|
|
||
|
20:13:23R GM8BPQ-10>FBB Port=1 <UI C>:
|
||
|
? 0000006464
|
||
|
|
||
|
20:15:34R GM8BPQ-10>FBB Port=1 <UI C>:
|
||
|
105 !!
|
||
|
20:15:45T GM8BPQ-10>MAIL Port=2 <UI C>:
|
||
|
|
||
|
20:16:40R GM8BPQ-10>FBB Port=1 <UI C>:
|
||
|
19-Jul 21:15 <<< Mailbox GM8BPQ Skigersta >>> 4 active messages.
|
||
|
Messages for
|
||
|
ALL G8BPQ
|
||
|
20:17:46R GM8BPQ-10>FBB Port=1 <UI C>:
|
||
|
106 P 5 GM8BPQ GM8BPQ 090719 ***
|
||
|
20:20:54R GM8BPQ-10>FBB Port=1 <UI C>:
|
||
|
? 0000006464
|
||
|
20:21:05T GM8BPQ-10>FBB Port=2 <UI C>:
|
||
|
? 0000006464
|
||
|
*/
|