1727 lines
36 KiB
C
1727 lines
36 KiB
C
|
/*
|
|||
|
Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO
|
|||
|
|
|||
|
This file is part of QtSoundModem
|
|||
|
|
|||
|
QtSoundModem 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.
|
|||
|
|
|||
|
QtSoundModem 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 QtSoundModem. If not, see http://www.gnu.org/licenses
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
// UZ7HO Soundmodem Port by John Wiseman G8BPQ
|
|||
|
|
|||
|
#include "ax25.h"
|
|||
|
|
|||
|
UCHAR TimerEvent = TIMER_EVENT_OFF;
|
|||
|
extern int busy;
|
|||
|
int listenEnable;
|
|||
|
void * KISSSockCopy[4];
|
|||
|
|
|||
|
string * make_frame(string * data, Byte * path, Byte pid, Byte nr, Byte ns, Byte f_type, Byte f_id, boolean rpt, boolean pf, boolean cr);
|
|||
|
void rst_t3(TAX25Port * AX25Sess);
|
|||
|
void CheckUIFrame(unsigned char * path, string * data);
|
|||
|
TAX25Port * get_user_port(int snd_ch, Byte * path);
|
|||
|
|
|||
|
void inc_frack(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
AX25Sess->clk_frack++;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void rst_frack(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
AX25Sess->clk_frack = 0;
|
|||
|
}
|
|||
|
|
|||
|
void inc_t1(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
AX25Sess->t1++;
|
|||
|
}
|
|||
|
|
|||
|
void rst_t1(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
AX25Sess->t1 = 0;
|
|||
|
}
|
|||
|
|
|||
|
void inc_t3(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
AX25Sess->t3++;
|
|||
|
}
|
|||
|
|
|||
|
void rst_t3(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
AX25Sess->t3 = 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void rst_values(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
AX25Sess->IPOLL_cnt = 0;
|
|||
|
AX25Sess->hi_vs = 0;
|
|||
|
AX25Sess->vs = 0;
|
|||
|
AX25Sess->vr = 0;
|
|||
|
Clear(&AX25Sess->I_frame_buf);
|
|||
|
Clear(&AX25Sess->in_data_buf);
|
|||
|
Clear(&AX25Sess->frm_collector);
|
|||
|
|
|||
|
ax25_info_init(AX25Sess);
|
|||
|
clr_frm_win(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void rst_timer(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
rst_frack(AX25Sess);
|
|||
|
rst_t1(AX25Sess);
|
|||
|
rst_t3(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
void upd_i_lo(TAX25Port * AX25Sess, int n) //Update the counter of the first frame in the I-frame buffer
|
|||
|
{
|
|||
|
AX25Sess->i_lo = n;
|
|||
|
}
|
|||
|
|
|||
|
void upd_i_hi(TAX25Port * AX25Sess, int n) //Update last frame counter in I-frame buffer
|
|||
|
{
|
|||
|
AX25Sess->i_hi = n;
|
|||
|
}
|
|||
|
|
|||
|
void upd_vs(TAX25Port * AX25Sess, int n) //Update the counter of the next frame to transmit
|
|||
|
{
|
|||
|
AX25Sess->vs = ++n & 7;
|
|||
|
}
|
|||
|
|
|||
|
void upd_vr(TAX25Port * AX25Sess, int n) //Refresh the counter of the next frame at the reception
|
|||
|
{
|
|||
|
AX25Sess->vr = ++n & 7;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void Frame_Optimize(TAX25Port * AX25Sess, TStringList * buf)
|
|||
|
{
|
|||
|
// I think this removes redundant frames from the TX Queue (eg repeated RR frames)
|
|||
|
|
|||
|
string * frame;
|
|||
|
Byte path[80];
|
|||
|
string * data = newString();
|
|||
|
|
|||
|
Byte pid, nr, ns, f_type, f_id, rpt, cr, pf;
|
|||
|
boolean curr_req, optimize;
|
|||
|
int i, k;
|
|||
|
char need_frm[8] = "";
|
|||
|
int index = 0;
|
|||
|
boolean PollRR;
|
|||
|
boolean PollREJ;
|
|||
|
|
|||
|
PollRR = FALSE;
|
|||
|
PollREJ = FALSE;
|
|||
|
curr_req = FALSE;
|
|||
|
|
|||
|
// Check Poll RR and REJ frame
|
|||
|
|
|||
|
i = 0;
|
|||
|
|
|||
|
while (i < buf->Count && !PollREJ)
|
|||
|
{
|
|||
|
frame = Strings(buf, i);
|
|||
|
// TX frame has kiss control on front
|
|||
|
|
|||
|
decode_frame(frame->Data + 1, frame->Length - 1, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr);
|
|||
|
|
|||
|
if (cr == SET_R && pf == SET_P)
|
|||
|
{
|
|||
|
if (f_id == S_REJ)
|
|||
|
PollREJ = TRUE;
|
|||
|
else if (f_id == S_RR && nr == AX25Sess->vr)
|
|||
|
PollRR = TRUE;
|
|||
|
}
|
|||
|
i++;
|
|||
|
}
|
|||
|
|
|||
|
// Performance of the REJ Cards: Optional Rej Cards
|
|||
|
|
|||
|
i = 0;
|
|||
|
|
|||
|
while (i < buf->Count)
|
|||
|
{
|
|||
|
optimize = TRUE;
|
|||
|
frame = Strings(buf, i);
|
|||
|
decode_frame(frame->Data + 1, frame->Length - 1, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr);
|
|||
|
|
|||
|
if (f_id == S_REJ && cr == SET_R)
|
|||
|
{
|
|||
|
if ((pf == SET_F && PollREJ) || nr != AX25Sess->vr)
|
|||
|
{
|
|||
|
Debugprintf("Optimizer dropping REJ nr %d vr %d pf %d PollREJ %d", nr, AX25Sess->vr, pf, PollREJ);
|
|||
|
Delete(buf, i);
|
|||
|
optimize = FALSE;
|
|||
|
}
|
|||
|
if (nr == AX25Sess->vr)
|
|||
|
curr_req = TRUE;
|
|||
|
}
|
|||
|
if (optimize)
|
|||
|
i++;
|
|||
|
}
|
|||
|
|
|||
|
// Performance Options
|
|||
|
|
|||
|
i = 0;
|
|||
|
|
|||
|
while (i < buf->Count)
|
|||
|
{
|
|||
|
need_frm[0] = 0;
|
|||
|
index = 0;
|
|||
|
k = AX25Sess->i_lo;
|
|||
|
|
|||
|
while (k != AX25Sess->vs)
|
|||
|
{
|
|||
|
need_frm[index++] = k + 'A';
|
|||
|
k++;
|
|||
|
k &= 7;
|
|||
|
}
|
|||
|
|
|||
|
optimize = TRUE;
|
|||
|
|
|||
|
frame = Strings(buf, i);
|
|||
|
|
|||
|
decode_frame(frame->Data +1 , frame->Length - 1, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr);
|
|||
|
|
|||
|
if (f_id == S_RR)
|
|||
|
{
|
|||
|
// RR Cards Methods: Optional RR, F Cards
|
|||
|
if (cr == SET_R)
|
|||
|
{
|
|||
|
if (nr != AX25Sess->vr || ((pf == SET_F) && PollRR) || curr_req)
|
|||
|
{
|
|||
|
Debugprintf("Optimizer dropping RR nr %d vr %d pf %d PollRR %d", nr, AX25Sess->vr, pf, PollRR);
|
|||
|
|
|||
|
Delete(buf, i);
|
|||
|
optimize = FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// RR Cards Methods : Optional RR, P Cards
|
|||
|
if (cr == SET_C)
|
|||
|
{
|
|||
|
if (AX25Sess->status == STAT_LINK || AX25Sess->status == STAT_WAIT_ANS)
|
|||
|
{
|
|||
|
Debugprintf("Optimizer dropping RR nr %d vr %d pf %d PollRR %d", nr, AX25Sess->vr, pf, PollRR);
|
|||
|
Delete(buf, i);
|
|||
|
optimize = FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
// I - Cards Methods : Options for I - Cards
|
|||
|
else if (f_id == I_I)
|
|||
|
{
|
|||
|
if (strchr(need_frm, ns + 'A') == 0)
|
|||
|
{
|
|||
|
Delete(buf, i);
|
|||
|
optimize = FALSE;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (nr != AX25Sess->vr)
|
|||
|
buf->Items[i] = make_frame(data, path, pid, AX25Sess->vr, ns, f_type, f_id, rpt, pf, cr);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// SABM Applications
|
|||
|
|
|||
|
if (f_id == U_SABM)
|
|||
|
{
|
|||
|
if (AX25Sess->status != STAT_TRY_LINK)
|
|||
|
{
|
|||
|
Delete(buf, i);
|
|||
|
optimize = FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (optimize)
|
|||
|
i++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
int KISS_encode(UCHAR * KISSBuffer, int port, string * frame)
|
|||
|
{
|
|||
|
// Encode frame
|
|||
|
|
|||
|
UCHAR * ptr1 = frame->Data;
|
|||
|
UCHAR TXCCC = 0;
|
|||
|
int Len = frame->Length;
|
|||
|
UCHAR * ptr2 = &KISSBuffer[2];
|
|||
|
UCHAR c;
|
|||
|
|
|||
|
// TX Frame has control byte on front
|
|||
|
|
|||
|
ptr1++;
|
|||
|
Len--;
|
|||
|
|
|||
|
KISSBuffer[0] = FEND;
|
|||
|
KISSBuffer[1] = port << 4;
|
|||
|
|
|||
|
TXCCC ^= KISSBuffer[1];
|
|||
|
|
|||
|
while (Len--)
|
|||
|
{
|
|||
|
c = *(ptr1++);
|
|||
|
TXCCC ^= c;
|
|||
|
|
|||
|
switch (c)
|
|||
|
{
|
|||
|
case FEND:
|
|||
|
(*ptr2++) = FESC;
|
|||
|
(*ptr2++) = TFEND;
|
|||
|
break;
|
|||
|
|
|||
|
case FESC:
|
|||
|
|
|||
|
(*ptr2++) = FESC;
|
|||
|
(*ptr2++) = TFESC;
|
|||
|
break;
|
|||
|
|
|||
|
// Drop through
|
|||
|
|
|||
|
default:
|
|||
|
|
|||
|
(*ptr2++) = c;
|
|||
|
}
|
|||
|
}
|
|||
|
(*ptr2++) = FEND;
|
|||
|
|
|||
|
return (int)(ptr2 - KISSBuffer);
|
|||
|
}
|
|||
|
|
|||
|
void KISSSendtoServer(void * Socket, char * Data, int Length);
|
|||
|
|
|||
|
void add_pkt_buf(TAX25Port * AX25Sess, string * data)
|
|||
|
{
|
|||
|
// boolean found = 0;
|
|||
|
// int i = 0;
|
|||
|
UCHAR KISSBuffer[512];
|
|||
|
int Length;
|
|||
|
|
|||
|
// ? Don't we just send to TNC?
|
|||
|
|
|||
|
Length = KISS_encode(KISSBuffer, 0, data);
|
|||
|
|
|||
|
KISSSendtoServer(AX25Sess->socket, KISSBuffer, Length);
|
|||
|
|
|||
|
monitor_frame(0, data, "", 1, 0); // Monitor
|
|||
|
freeString(data);
|
|||
|
|
|||
|
|
|||
|
// while (i < AX25Sess->frame_buf.Count && !found)
|
|||
|
// {
|
|||
|
// found = compareStrings(Strings(&AX25Sess->frame_buf, i++), data);
|
|||
|
// }
|
|||
|
|
|||
|
// if (found)
|
|||
|
// freeString(data);
|
|||
|
// else
|
|||
|
// Add(&AX25Sess->frame_buf, data);
|
|||
|
}
|
|||
|
|
|||
|
void add_I_FRM(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
string * data;
|
|||
|
int i;
|
|||
|
|
|||
|
upd_i_lo(AX25Sess, AX25Sess->vs);
|
|||
|
|
|||
|
while (AX25Sess->in_data_buf.Count > 0 && AX25Sess->I_frame_buf.Count != maxframe[AX25Sess->snd_ch])
|
|||
|
{
|
|||
|
data = duplicateString(Strings(&AX25Sess->in_data_buf, 0));
|
|||
|
Delete(&AX25Sess->in_data_buf, 0);
|
|||
|
Add(&AX25Sess->I_frame_buf, data);
|
|||
|
}
|
|||
|
if (AX25Sess->I_frame_buf.Count > 0)
|
|||
|
{
|
|||
|
for (i = 0; i < AX25Sess->I_frame_buf.Count; i++)
|
|||
|
{
|
|||
|
upd_i_hi(AX25Sess, AX25Sess->vs);
|
|||
|
upd_vs(AX25Sess, AX25Sess->vs);
|
|||
|
AX25Sess->hi_vs = AX25Sess->vs; // Last transmitted frame
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void delete_I_FRM(TAX25Port * AX25Sess, int nr)
|
|||
|
{
|
|||
|
int i;
|
|||
|
|
|||
|
i = AX25Sess->i_lo;
|
|||
|
|
|||
|
while (i != nr)
|
|||
|
{
|
|||
|
if (AX25Sess->I_frame_buf.Count > 0)
|
|||
|
{
|
|||
|
AX25Sess->info.stat_s_pkt++;
|
|||
|
AX25Sess->info.stat_s_byte += Strings(&AX25Sess->I_frame_buf, 0)->Length;
|
|||
|
Delete(&AX25Sess->I_frame_buf, 0);
|
|||
|
}
|
|||
|
|
|||
|
i++;
|
|||
|
i &= 7;
|
|||
|
}
|
|||
|
upd_i_lo(AX25Sess, nr);
|
|||
|
}
|
|||
|
|
|||
|
void delete_I_FRM_port(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
string * frame;
|
|||
|
char path[80] = "";
|
|||
|
string data= { 0 };
|
|||
|
|
|||
|
Byte pid, nr, ns, f_type, f_id, rpt, cr, pf;
|
|||
|
boolean optimize;
|
|||
|
int i = 0;
|
|||
|
|
|||
|
while (i < AX25Sess->frame_buf.Count)
|
|||
|
{
|
|||
|
optimize = TRUE;
|
|||
|
frame = Strings(&AX25Sess->frame_buf, i);
|
|||
|
|
|||
|
decode_frame(frame->Data, frame->Length, path, &data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr);
|
|||
|
|
|||
|
if (f_id == I_I)
|
|||
|
{
|
|||
|
Delete(&AX25Sess->frame_buf, i);
|
|||
|
optimize = FALSE;
|
|||
|
}
|
|||
|
if (optimize)
|
|||
|
i++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void send_data_buf(TAX25Port * AX25Sess, int nr)
|
|||
|
{
|
|||
|
int i;
|
|||
|
boolean new_frames;
|
|||
|
boolean PF_bit;
|
|||
|
|
|||
|
if (AX25Sess->status != STAT_LINK)
|
|||
|
return;
|
|||
|
|
|||
|
AX25Sess->IPOLL_cnt = 0;
|
|||
|
AX25Sess->vs = nr;
|
|||
|
delete_I_FRM(AX25Sess, nr); // ?? free acked frames
|
|||
|
// delete_I_FRM_port(AX25Sess);
|
|||
|
|
|||
|
if (TXFrmMode[AX25Sess->snd_ch] == 1)
|
|||
|
{
|
|||
|
new_frames = FALSE;
|
|||
|
|
|||
|
if (AX25Sess->I_frame_buf.Count < 2)
|
|||
|
{
|
|||
|
add_I_FRM(AX25Sess);
|
|||
|
AX25Sess->status = STAT_LINK;
|
|||
|
new_frames = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
if (AX25Sess->I_frame_buf.Count > 0)
|
|||
|
{
|
|||
|
if (new_frames)
|
|||
|
{
|
|||
|
for (i = 0; i < AX25Sess->I_frame_buf.Count; i++)
|
|||
|
{
|
|||
|
if (i == AX25Sess->I_frame_buf.Count - 1)
|
|||
|
PF_bit = SET_P;
|
|||
|
else
|
|||
|
PF_bit = SET_F;
|
|||
|
|
|||
|
add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, i), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, ((AX25Sess->i_lo + i) & 7), I_FRM, I_I, FALSE, PF_bit, SET_C));
|
|||
|
}
|
|||
|
}
|
|||
|
if (!new_frames)
|
|||
|
{
|
|||
|
add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, 0), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, AX25Sess->i_lo, I_FRM, I_I, FALSE, SET_P, SET_C)); //SET_P
|
|||
|
upd_vs(AX25Sess, AX25Sess->vs);
|
|||
|
}
|
|||
|
AX25Sess->status = STAT_WAIT_ANS;
|
|||
|
rst_timer(AX25Sess);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (TXFrmMode[AX25Sess->snd_ch] == 0)
|
|||
|
{
|
|||
|
add_I_FRM(AX25Sess);
|
|||
|
AX25Sess->status = STAT_LINK;
|
|||
|
|
|||
|
if (AX25Sess->I_frame_buf.Count > 0)
|
|||
|
{
|
|||
|
for (i = 0; i < AX25Sess->I_frame_buf.Count; i++)
|
|||
|
{
|
|||
|
if (i == AX25Sess->I_frame_buf.Count - 1)
|
|||
|
PF_bit = SET_P;
|
|||
|
else
|
|||
|
PF_bit = SET_F;
|
|||
|
add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, i), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, ((AX25Sess->i_lo + i) & 7), I_FRM, I_I, FALSE, PF_bit, SET_C));
|
|||
|
}
|
|||
|
AX25Sess->status = STAT_WAIT_ANS;
|
|||
|
rst_timer(AX25Sess);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void send_data_buf_srej(TAX25Port * AX25Sess, int nr)
|
|||
|
{
|
|||
|
// Similar to send_data_buf but only sends the requested I frame with P set
|
|||
|
|
|||
|
int i = 0;
|
|||
|
boolean new_frames;
|
|||
|
boolean PF_bit;
|
|||
|
|
|||
|
if (AX25Sess->status != STAT_LINK)
|
|||
|
return;
|
|||
|
|
|||
|
AX25Sess->IPOLL_cnt = 0;
|
|||
|
AX25Sess->vs = nr;
|
|||
|
delete_I_FRM(AX25Sess, nr); // ?? free acked frames
|
|||
|
|
|||
|
new_frames = FALSE;
|
|||
|
|
|||
|
add_I_FRM(AX25Sess);
|
|||
|
AX25Sess->status = STAT_LINK;
|
|||
|
new_frames = TRUE;
|
|||
|
|
|||
|
if (AX25Sess->I_frame_buf.Count > 0)
|
|||
|
{
|
|||
|
if (new_frames)
|
|||
|
{
|
|||
|
PF_bit = SET_P;
|
|||
|
|
|||
|
add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, i), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, ((AX25Sess->i_lo + i) & 7), I_FRM, I_I, FALSE, PF_bit, SET_C));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
|
|||
|
add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, 0), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, AX25Sess->i_lo, I_FRM, I_I, FALSE, SET_P, SET_C)); //SET_P
|
|||
|
upd_vs(AX25Sess, AX25Sess->vs);
|
|||
|
}
|
|||
|
}
|
|||
|
AX25Sess->status = STAT_WAIT_ANS;
|
|||
|
rst_timer(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
void write_frame_collector(TAX25Port * AX25Sess, int ns, string * data)
|
|||
|
{
|
|||
|
Byte i;
|
|||
|
char frm_ns;
|
|||
|
string * frm;
|
|||
|
boolean found ;
|
|||
|
boolean need_frm;
|
|||
|
|
|||
|
if (max_frame_collector[AX25Sess->snd_ch] < 1)
|
|||
|
return;
|
|||
|
|
|||
|
need_frm = FALSE;
|
|||
|
i = 1;
|
|||
|
do
|
|||
|
{
|
|||
|
if (ns == ((AX25Sess->vr + i) & 7))
|
|||
|
need_frm = TRUE;
|
|||
|
|
|||
|
i++;
|
|||
|
|
|||
|
} while (i <= max_frame_collector[AX25Sess->snd_ch] && !need_frm);
|
|||
|
|
|||
|
if (need_frm)
|
|||
|
{
|
|||
|
frm_ns = ns;
|
|||
|
found = FALSE;
|
|||
|
i = 0;
|
|||
|
|
|||
|
if (AX25Sess->frm_collector.Count > 0)
|
|||
|
{
|
|||
|
do
|
|||
|
{
|
|||
|
frm = Strings(&AX25Sess->frm_collector, i);
|
|||
|
|
|||
|
if (frm_ns == frm->Data[0])
|
|||
|
found = TRUE;
|
|||
|
i++;
|
|||
|
}
|
|||
|
while (!found && i != AX25Sess->frm_collector.Count);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
if (!found)
|
|||
|
{
|
|||
|
string * frm = newString();
|
|||
|
|
|||
|
stringAdd(frm, (char *)&frm_ns, 1);
|
|||
|
stringAdd(frm, data->Data, data->Length);
|
|||
|
Add(&AX25Sess->frm_collector, frm);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
string * read_frame_collector(TAX25Port * AX25Sess, boolean fecflag)
|
|||
|
{
|
|||
|
// Look for required frame no in saved frames
|
|||
|
|
|||
|
string * frm;
|
|||
|
string * data = newString();
|
|||
|
|
|||
|
int i = 0;
|
|||
|
Byte frm_ns;
|
|||
|
|
|||
|
while (i < AX25Sess->frm_collector.Count)
|
|||
|
{
|
|||
|
frm = duplicateString(Strings(&AX25Sess->frm_collector, i));
|
|||
|
|
|||
|
frm_ns = frm->Data[0];
|
|||
|
|
|||
|
if (frm_ns == AX25Sess->vr)
|
|||
|
{
|
|||
|
Delete(&AX25Sess->frm_collector, i);
|
|||
|
|
|||
|
upd_vr(AX25Sess, AX25Sess->vr);
|
|||
|
|
|||
|
mydelete(frm, 0, 1); // Remove vr from front
|
|||
|
|
|||
|
stringAdd(data, frm->Data, frm->Length);
|
|||
|
|
|||
|
AX25Sess->info.stat_r_pkt++;
|
|||
|
AX25Sess->info.stat_r_fc++;
|
|||
|
|
|||
|
if (fecflag)
|
|||
|
AX25Sess->info.stat_fec_count++;
|
|||
|
|
|||
|
AX25Sess->info.stat_r_byte += frm->Length;
|
|||
|
AX25Sess->frm_win[frm_ns].Length = frm->Length; //Save the frame to the window buffer
|
|||
|
AX25Sess->frm_win[frm_ns].Data = frm->Data; //Save the frame to the window buffer
|
|||
|
}
|
|||
|
|
|||
|
i++;
|
|||
|
}
|
|||
|
return data;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/////////////////////////// SET-FRAMES //////////////////////////////////
|
|||
|
|
|||
|
void set_chk_link(TAX25Port * AX25Sess, Byte * path)
|
|||
|
{
|
|||
|
boolean b_IPOLL;
|
|||
|
int len;
|
|||
|
|
|||
|
AX25Sess->status = STAT_CHK_LINK;
|
|||
|
|
|||
|
// if AX25Sess->digi<>'' then path:=path+','+reverse_digi(AX25Sess->digi);
|
|||
|
|
|||
|
b_IPOLL = FALSE;
|
|||
|
|
|||
|
if (AX25Sess->I_frame_buf.Count > 0 && AX25Sess->IPOLL_cnt < 2)
|
|||
|
{
|
|||
|
len = Strings(&AX25Sess->I_frame_buf, 0)->Length;
|
|||
|
|
|||
|
if (len > 0 && len <= IPOLL[AX25Sess->snd_ch])
|
|||
|
{
|
|||
|
|
|||
|
b_IPOLL = TRUE;
|
|||
|
AX25Sess->IPOLL_cnt++;
|
|||
|
}
|
|||
|
}
|
|||
|
if (b_IPOLL)
|
|||
|
add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, 0), path, AX25Sess->PID, AX25Sess->vr, AX25Sess->i_lo, I_FRM, I_I, FALSE, SET_P, SET_C));
|
|||
|
else
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, path, 0xF0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, SET_P, SET_C));
|
|||
|
|
|||
|
inc_frack(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// This seems to start a connection
|
|||
|
|
|||
|
void set_link(TAX25Port * AX25Sess, UCHAR * axpath)
|
|||
|
{
|
|||
|
if (AX25Sess->status != STAT_LINK)
|
|||
|
{
|
|||
|
string nullstring;
|
|||
|
nullstring.Length = 0;
|
|||
|
|
|||
|
rst_values(AX25Sess);
|
|||
|
|
|||
|
AX25Sess->status = STAT_TRY_LINK;
|
|||
|
|
|||
|
// if (AX25Sess->digi[0] != 0)
|
|||
|
// path: = path + ',' + reverse_digi(AX25Sess->digi);
|
|||
|
|
|||
|
add_pkt_buf(AX25Sess, make_frame(&nullstring, axpath, 0, 0, 0, U_FRM, U_SABM, SET_NO_RPT, SET_P, SET_C));
|
|||
|
|
|||
|
inc_frack(AX25Sess);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#define MODE_OUR 0
|
|||
|
|
|||
|
void set_try_unlink(TAX25Port * AX25Sess, Byte * path)
|
|||
|
{
|
|||
|
string nullstring;
|
|||
|
nullstring.Length = 0;
|
|||
|
|
|||
|
AX25Sess->status = STAT_TRY_UNLINK;
|
|||
|
add_pkt_buf(AX25Sess, make_frame(&nullstring, path, 0, 0, 0, U_FRM, U_DISC, SET_NO_RPT, SET_P, SET_C));
|
|||
|
inc_frack(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void set_unlink(TAX25Port * AX25Sess, Byte * path)
|
|||
|
{
|
|||
|
if (AX25Sess->status == STAT_TRY_UNLINK
|
|||
|
|| AX25Sess->status == STAT_TRY_LINK
|
|||
|
|| AX25Sess->status == STAT_NO_LINK)
|
|||
|
{
|
|||
|
string nullstring;
|
|||
|
nullstring.Length = 0;
|
|||
|
|
|||
|
// if (AX25Sess->digi[0] != 0)
|
|||
|
// path: = path + ',' + reverse_digi(AX25Sess->digi);
|
|||
|
|
|||
|
AX25_disc(AX25Sess, MODE_OUR);
|
|||
|
|
|||
|
if (AX25Sess->status != STAT_TRY_UNLINK)
|
|||
|
add_pkt_buf(AX25Sess, make_frame(&nullstring, path, 0, 0, 0, U_FRM, U_DISC, SET_NO_RPT, SET_P, SET_C));
|
|||
|
|
|||
|
AX25Sess->info.stat_end_ses = time(NULL);
|
|||
|
|
|||
|
write_ax25_info(AX25Sess);
|
|||
|
rst_values(AX25Sess);
|
|||
|
|
|||
|
memset(AX25Sess->corrcall, 0, 10);
|
|||
|
memset(AX25Sess->mycall, 0, 10);
|
|||
|
AX25Sess->digi[0] = 0;
|
|||
|
AX25Sess->status = STAT_NO_LINK;
|
|||
|
|
|||
|
}
|
|||
|
else
|
|||
|
set_try_unlink(AX25Sess, AX25Sess->Path);
|
|||
|
}
|
|||
|
|
|||
|
void set_FRMR(int snd_ch, Byte * path, unsigned char frameType)
|
|||
|
{
|
|||
|
//We may not have a session when sending FRMR so reverse path and send
|
|||
|
|
|||
|
Byte revpath[80];
|
|||
|
string * Data = newString();
|
|||
|
string * Frame;
|
|||
|
|
|||
|
UCHAR KISSBuffer[512];
|
|||
|
int Length;
|
|||
|
|
|||
|
Data->Data[0] = frameType;
|
|||
|
Data->Data[1] = 0;
|
|||
|
Data->Data[2] = 1; // Invalid CTL Byte
|
|||
|
Data->Length = 3;
|
|||
|
|
|||
|
reverse_addr(path, revpath, strlen(path));
|
|||
|
|
|||
|
Frame = make_frame(Data, revpath, 0, 0, 0, U_FRM, U_FRMR, FALSE, SET_P, SET_R);
|
|||
|
|
|||
|
// ? Don't we just send to TNC?
|
|||
|
|
|||
|
Length = KISS_encode(KISSBuffer, 0, Frame);
|
|||
|
|
|||
|
KISSSendtoServer(KISSSockCopy[snd_ch], KISSBuffer, Length);
|
|||
|
|
|||
|
monitor_frame(0, Frame, "", 1, 0); // Monitor
|
|||
|
freeString(Frame);
|
|||
|
freeString(Data);
|
|||
|
}
|
|||
|
|
|||
|
void set_DM(int snd_ch, Byte * path)
|
|||
|
{
|
|||
|
//We may not have a session when sending DM so reverse path and send
|
|||
|
|
|||
|
Byte revpath[80];
|
|||
|
|
|||
|
reverse_addr(path, revpath, strlen(path));
|
|||
|
|
|||
|
add_pkt_buf(&AX25Port[snd_ch][0], make_frame(NULL, revpath, 0, 0, 0, U_FRM,U_DM,FALSE,SET_P,SET_R));
|
|||
|
}
|
|||
|
|
|||
|
/////////////////////////// S-FRAMES ////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
void on_RR(TAX25Port * AX25Sess, Byte * path, int nr, int pf, int cr)
|
|||
|
{
|
|||
|
char need_frame[16] = "";
|
|||
|
int index = 0;
|
|||
|
|
|||
|
int i;
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_TRY_LINK)
|
|||
|
return;
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK)
|
|||
|
{
|
|||
|
if (cr == SET_C)
|
|||
|
set_DM(AX25Sess->snd_ch, path);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (cr == SET_R)
|
|||
|
{
|
|||
|
// Determine which frames could get into the user<65>s frame buffer.
|
|||
|
i = AX25Sess->vs;
|
|||
|
|
|||
|
need_frame[index++] = i + '0';
|
|||
|
|
|||
|
// Debugprintf("RR Rxed vs = %d hi_vs = %d", AX25Sess->vs, AX25Sess->hi_vs);
|
|||
|
while (i != AX25Sess->hi_vs)
|
|||
|
{
|
|||
|
i++;
|
|||
|
i &= 7;
|
|||
|
|
|||
|
need_frame[index++] = i + '0';
|
|||
|
if (index > 10)
|
|||
|
{
|
|||
|
Debugprintf("Index corrupt %d need_frame = %s", index, need_frame);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//Clear the received frames from the transmission buffer.
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_WAIT_ANS)
|
|||
|
delete_I_FRM(AX25Sess, nr);
|
|||
|
|
|||
|
// We restore the link if the number is valid
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_CHK_LINK || strchr(need_frame, nr + '0') != 0)
|
|||
|
{
|
|||
|
rst_timer(AX25Sess);
|
|||
|
AX25Sess->status = STAT_LINK;
|
|||
|
send_data_buf(AX25Sess, nr);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (cr == SET_C)
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, pf, SET_R));
|
|||
|
|
|||
|
rst_t3(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void on_RNR(TAX25Port * AX25Sess, Byte * path, int nr, int pf, int cr)
|
|||
|
{
|
|||
|
UNUSED(pf);
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_TRY_LINK)
|
|||
|
return;
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK)
|
|||
|
{
|
|||
|
if (cr == SET_C)
|
|||
|
set_DM(AX25Sess->snd_ch, path);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (cr == SET_R)
|
|||
|
{
|
|||
|
rst_timer(AX25Sess);
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_WAIT_ANS)
|
|||
|
delete_I_FRM(AX25Sess, nr);
|
|||
|
|
|||
|
AX25Sess->status = STAT_CHK_LINK;
|
|||
|
}
|
|||
|
|
|||
|
if (cr == SET_C)
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, SET_P, SET_R));
|
|||
|
|
|||
|
rst_t3(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void on_REJ(TAX25Port * AX25Sess, Byte * path, int nr, int pf, int cr)
|
|||
|
{
|
|||
|
UNUSED(pf);
|
|||
|
if (AX25Sess->status == STAT_TRY_LINK)
|
|||
|
return;
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK)
|
|||
|
{
|
|||
|
if (cr == SET_C)
|
|||
|
set_DM(AX25Sess->snd_ch, path);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (cr == SET_R)
|
|||
|
{
|
|||
|
rst_timer(AX25Sess);
|
|||
|
AX25Sess->status = STAT_LINK;
|
|||
|
|
|||
|
send_data_buf(AX25Sess, nr);
|
|||
|
}
|
|||
|
|
|||
|
if (cr == SET_C)
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, SET_P, SET_R));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void on_SREJ(TAX25Port * AX25Sess, Byte * path, int nr, int pf, int cr)
|
|||
|
{
|
|||
|
UNUSED(pf);
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_TRY_LINK)
|
|||
|
return;
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK)
|
|||
|
{
|
|||
|
if (cr == SET_C)
|
|||
|
set_DM(AX25Sess->snd_ch, path);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (cr == SET_R)
|
|||
|
{
|
|||
|
rst_timer(AX25Sess);
|
|||
|
AX25Sess->status = STAT_LINK;
|
|||
|
|
|||
|
send_data_buf_srej(AX25Sess, nr);
|
|||
|
}
|
|||
|
|
|||
|
if (cr == SET_C)
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, SET_P, SET_R));
|
|||
|
}
|
|||
|
/////////////////////////// I-FRAMES ////////////////////////////////////
|
|||
|
|
|||
|
void on_I(void * socket, TAX25Port * AX25Sess, int PID, Byte * path, string * data, int nr, int ns, int pf, int cr, boolean fecflag)
|
|||
|
{
|
|||
|
UNUSED(cr);
|
|||
|
UNUSED(socket);
|
|||
|
string * collector_data = 0;
|
|||
|
int i;
|
|||
|
Byte need_frame[16] = "";
|
|||
|
int index = 0;
|
|||
|
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_TRY_LINK)
|
|||
|
return;
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK)
|
|||
|
{
|
|||
|
set_DM(0, path);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (busy)
|
|||
|
{
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, path, PID, AX25Sess->vr, 0, S_FRM, S_RNR, FALSE, pf, SET_R));
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Determine which frames could get into the user<65>s frame buffer.
|
|||
|
|
|||
|
i = AX25Sess->vs;
|
|||
|
|
|||
|
need_frame[index++] = i + '0';
|
|||
|
|
|||
|
// Debugprintf("I Rxed vs = %d hi_vs = %d", AX25Sess->vs, AX25Sess->hi_vs);
|
|||
|
|
|||
|
while (i != AX25Sess->hi_vs)
|
|||
|
{
|
|||
|
i++;
|
|||
|
i &= 7;
|
|||
|
|
|||
|
need_frame[index++] = i + '0';
|
|||
|
if (index > 10)
|
|||
|
{
|
|||
|
Debugprintf("Index corrupt %d need_frame = %s", index, need_frame);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//Clear received frames from the transmission buffer.
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_WAIT_ANS)
|
|||
|
delete_I_FRM(AX25Sess, nr);
|
|||
|
|
|||
|
//We restore the link if the number is valid
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_CHK_LINK || strchr(need_frame, nr + '0') != 0)
|
|||
|
{
|
|||
|
//We restore the link if the number is valid
|
|||
|
|
|||
|
rst_timer(AX25Sess);
|
|||
|
AX25Sess->status = STAT_LINK;
|
|||
|
send_data_buf(AX25Sess, nr);
|
|||
|
}
|
|||
|
|
|||
|
if (ns == AX25Sess->vr)
|
|||
|
{
|
|||
|
//If the expected frame, send RR, F
|
|||
|
|
|||
|
AX25Sess->info.stat_r_pkt++;
|
|||
|
AX25Sess->info.stat_r_byte += data->Length;
|
|||
|
|
|||
|
if (fecflag)
|
|||
|
AX25Sess->info.stat_fec_count++;
|
|||
|
|
|||
|
upd_vr(AX25Sess, AX25Sess->vr);
|
|||
|
|
|||
|
AX25Sess->frm_win[ns].Length = data->Length; //Save the frame to the window buffer
|
|||
|
AX25Sess->frm_win[ns].Data = data->Data; //Save the frame to the window buffer
|
|||
|
|
|||
|
collector_data = read_frame_collector(AX25Sess, fecflag);
|
|||
|
|
|||
|
stringAdd(data, collector_data->Data, collector_data->Length);
|
|||
|
|
|||
|
SendtoTerm(AX25Sess->Sess, (char *)data->Data, data->Length);
|
|||
|
|
|||
|
// Andy's code queues RR immediately and uses frame optimiser to remove
|
|||
|
// redundant RR (not P) frames. I can't do that so need a proper resptime
|
|||
|
// system. Try setting an RRNeeded timer (t2).
|
|||
|
|
|||
|
if (pf)
|
|||
|
{
|
|||
|
// Poll set, so respond immediately
|
|||
|
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, pf, SET_R));
|
|||
|
AX25Sess->t2 = 0;
|
|||
|
}
|
|||
|
else
|
|||
|
AX25Sess->t2 = resptime[0] / 100; // resptime is in mS
|
|||
|
|
|||
|
freeString(collector_data);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If the frame is not expected, send REJ, F
|
|||
|
|
|||
|
if ((AX25Sess->frm_win[ns].Length != data->Length) &&
|
|||
|
memcmp(&AX25Sess->frm_win[ns].Data, data->Data, data->Length) != 0)
|
|||
|
|
|||
|
write_frame_collector(AX25Sess, ns, data);
|
|||
|
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_REJ, FALSE, pf, SET_R));
|
|||
|
}
|
|||
|
rst_t3(AX25Sess);
|
|||
|
|
|||
|
}
|
|||
|
/////////////////////////// U-FRAMES ////////////////////////////////////
|
|||
|
|
|||
|
void * ax25IncommingConnect(TAX25Port * AX25Sess);
|
|||
|
|
|||
|
void on_SABM(void * socket, TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
if (AX25Sess->status == STAT_TRY_UNLINK)
|
|||
|
{
|
|||
|
AX25Sess->info.stat_end_ses = time(NULL);
|
|||
|
|
|||
|
write_ax25_info(AX25Sess);
|
|||
|
rst_values(AX25Sess);
|
|||
|
|
|||
|
memset(AX25Sess->corrcall, 0, 10);
|
|||
|
memset(AX25Sess->mycall, 0, 10);
|
|||
|
AX25Sess->digi[0] = 0;
|
|||
|
|
|||
|
AX25_disc(AX25Sess, MODE_OTHER);
|
|||
|
Clear(&AX25Sess->frame_buf);
|
|||
|
|
|||
|
AX25Sess->status = STAT_NO_LINK;
|
|||
|
}
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_TRY_LINK)
|
|||
|
{
|
|||
|
AX25_disc(AX25Sess, MODE_OTHER);
|
|||
|
|
|||
|
rst_timer(AX25Sess);
|
|||
|
rst_values(AX25Sess);
|
|||
|
Clear(&AX25Sess->frame_buf);
|
|||
|
AX25Sess->status = STAT_NO_LINK;
|
|||
|
}
|
|||
|
|
|||
|
if (AX25Sess->status != STAT_NO_LINK)
|
|||
|
{
|
|||
|
if ((strcmp(AX25Sess->kind, "Outgoing") == 0) ||
|
|||
|
AX25Sess->status == STAT_TRY_UNLINK || AX25Sess->info.stat_s_byte > 0 ||
|
|||
|
AX25Sess->info.stat_r_byte > 0 || AX25Sess->frm_collector.Count > 0)
|
|||
|
{
|
|||
|
AX25Sess->info.stat_end_ses = time(NULL);
|
|||
|
AX25_disc(AX25Sess, MODE_OTHER);
|
|||
|
write_ax25_info(AX25Sess);
|
|||
|
rst_timer(AX25Sess);
|
|||
|
rst_values(AX25Sess);
|
|||
|
Clear(&AX25Sess->frame_buf);
|
|||
|
AX25Sess->status = STAT_NO_LINK;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_NO_LINK)
|
|||
|
{
|
|||
|
AX25Sess->vr = 0;
|
|||
|
AX25Sess->vs = 0;
|
|||
|
AX25Sess->hi_vs = 0;
|
|||
|
|
|||
|
Clear(&AX25Sess->frm_collector);
|
|||
|
clr_frm_win(AX25Sess);
|
|||
|
AX25Sess->status = STAT_LINK;
|
|||
|
AX25Sess->info.stat_begin_ses = time(NULL);
|
|||
|
strcpy(AX25Sess->kind, "Incoming");
|
|||
|
AX25Sess->socket = socket;
|
|||
|
|
|||
|
// Must send UA before any ctext
|
|||
|
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, AX25Sess->Path, 0, 0, 0, U_FRM, U_UA, FALSE, SET_P, SET_R));
|
|||
|
|
|||
|
if (ax25IncommingConnect(AX25Sess)) // Attach to Terminal
|
|||
|
AX25_conn(AX25Sess, AX25Sess->snd_ch, MODE_OTHER);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, AX25Sess->Path, 0, 0, 0, U_FRM, U_UA, FALSE, SET_P, SET_R));
|
|||
|
}
|
|||
|
|
|||
|
void on_DISC(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
if (AX25Sess->status != STAT_NO_LINK)
|
|||
|
{
|
|||
|
AX25Sess->info.stat_end_ses = time(NULL);
|
|||
|
AX25_disc(AX25Sess, MODE_OTHER);
|
|||
|
write_ax25_info(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_LINK)
|
|||
|
set_DM(AX25Sess->snd_ch, AX25Sess->Path);
|
|||
|
else
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, AX25Sess->Path, 0, 0, 0, U_FRM, U_UA, FALSE, SET_P, SET_R));
|
|||
|
|
|||
|
rst_values(AX25Sess);
|
|||
|
memset(AX25Sess->corrcall, 0, 10);
|
|||
|
memset(AX25Sess->mycall, 0, 10);
|
|||
|
AX25Sess->digi[0] = 0;
|
|||
|
AX25Sess->status = STAT_NO_LINK;
|
|||
|
}
|
|||
|
|
|||
|
void on_DM(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
if (AX25Sess->status == STAT_TRY_LINK)
|
|||
|
{
|
|||
|
|
|||
|
char Msg[128];
|
|||
|
int Len;
|
|||
|
|
|||
|
Len = sprintf(Msg, "*** Busy From %s\r", AX25Sess->corrcall);
|
|||
|
SendtoTerm(AX25Sess->Sess, Msg, Len);
|
|||
|
}
|
|||
|
else if (AX25Sess->status != STAT_NO_LINK)
|
|||
|
{
|
|||
|
AX25Sess->info.stat_end_ses = time(NULL);
|
|||
|
AX25_disc(AX25Sess, MODE_OTHER);
|
|||
|
write_ax25_info(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
rst_timer(AX25Sess);
|
|||
|
rst_values(AX25Sess);
|
|||
|
memset(AX25Sess->corrcall, 0, 10);
|
|||
|
memset(AX25Sess->mycall, 0, 10);
|
|||
|
AX25Sess->digi[0] = 0;
|
|||
|
AX25Sess->status = STAT_NO_LINK;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void on_UA(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
switch (AX25Sess->status)
|
|||
|
{
|
|||
|
case STAT_TRY_LINK:
|
|||
|
|
|||
|
AX25Sess->info.stat_begin_ses = time(NULL);
|
|||
|
AX25Sess->status = STAT_LINK;
|
|||
|
AX25_conn(AX25Sess, AX25Sess->snd_ch, MODE_OUR);
|
|||
|
break;
|
|||
|
|
|||
|
case STAT_TRY_UNLINK:
|
|||
|
|
|||
|
AX25Sess->info.stat_end_ses = time(NULL);
|
|||
|
AX25_disc(AX25Sess, MODE_OUR);
|
|||
|
write_ax25_info(AX25Sess);
|
|||
|
|
|||
|
rst_values(AX25Sess);
|
|||
|
AX25Sess->status = STAT_NO_LINK;
|
|||
|
memset(AX25Sess->corrcall, 0, 10);
|
|||
|
memset(AX25Sess->mycall, 0, 10);
|
|||
|
AX25Sess->digi[0] = 0;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
rst_timer(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
void on_UI(TAX25Port * AX25Sess, int pf, int cr)
|
|||
|
{
|
|||
|
UNUSED(AX25Sess);
|
|||
|
UNUSED(pf);
|
|||
|
UNUSED(cr);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
void on_FRMR(void * socket, TAX25Port * AX25Sess, Byte * path)
|
|||
|
{
|
|||
|
if (AX25Sess->status != STAT_NO_LINK)
|
|||
|
{
|
|||
|
AX25Sess->info.stat_end_ses = time(NULL);
|
|||
|
|
|||
|
AX25_disc(socket, MODE_OTHER);
|
|||
|
write_ax25_info(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
set_DM(AX25Sess->snd_ch, path);
|
|||
|
|
|||
|
rst_values(AX25Sess);
|
|||
|
memset(AX25Sess->corrcall, 0, 10);
|
|||
|
memset(AX25Sess->mycall, 0, 10);
|
|||
|
AX25Sess->digi[0] = 0;
|
|||
|
AX25Sess->status = STAT_NO_LINK;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
void UpdateActiveConnects(int snd_ch)
|
|||
|
{
|
|||
|
int port;
|
|||
|
|
|||
|
users[snd_ch] = 0;
|
|||
|
|
|||
|
for (port = 0; port < port_num; port++)
|
|||
|
if (AX25Port[snd_ch][port].status != STAT_NO_LINK)
|
|||
|
users[snd_ch]++;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void timer_event()
|
|||
|
{
|
|||
|
int snd_ch, port;
|
|||
|
single frack;
|
|||
|
Byte active;
|
|||
|
TAX25Port * AX25Sess;
|
|||
|
|
|||
|
TimerEvent = TIMER_EVENT_OFF;
|
|||
|
|
|||
|
for (snd_ch = 0; snd_ch < 4; snd_ch++)
|
|||
|
{
|
|||
|
//reset the slottime timer
|
|||
|
if (dyn_frack[snd_ch])
|
|||
|
{
|
|||
|
UpdateActiveConnects(snd_ch);
|
|||
|
if (users[snd_ch] > 0)
|
|||
|
active = users[snd_ch] - 1;
|
|||
|
else
|
|||
|
active = 0;
|
|||
|
|
|||
|
frack = frack_time[snd_ch] + frack_time[snd_ch] * active * 0.5;
|
|||
|
}
|
|||
|
else
|
|||
|
frack = frack_time[snd_ch];
|
|||
|
|
|||
|
//
|
|||
|
for (port = 0; port < port_num; port++)
|
|||
|
{
|
|||
|
AX25Sess = &AX25Port[snd_ch][port];
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_NO_LINK)
|
|||
|
continue;
|
|||
|
|
|||
|
if (AX25Sess->t2)
|
|||
|
{
|
|||
|
AX25Sess->t2--;
|
|||
|
if (AX25Sess->t2 == 0)
|
|||
|
{
|
|||
|
// Expired - send RR (without P)
|
|||
|
|
|||
|
add_pkt_buf(AX25Sess, make_frame(NULL, AX25Sess->Path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, 0, SET_R));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (AX25Sess->frame_buf.Count == 0) //when the transfer buffer is empty
|
|||
|
inc_t1(AX25Sess); // we consider time of the timer of repeated requests
|
|||
|
|
|||
|
// Wouldn't it make more sense to keep path in ax.25 struct?
|
|||
|
|
|||
|
if (AX25Sess->t1 >= frack * 10 + (number_digi(AX25Sess->digi) * frack * 20))
|
|||
|
{
|
|||
|
if (AX25Sess->clk_frack >= fracks[snd_ch])
|
|||
|
{
|
|||
|
// This disconnects after retries expires
|
|||
|
|
|||
|
rst_frack(AX25Sess);
|
|||
|
set_unlink(AX25Sess, AX25Sess->Path);
|
|||
|
}
|
|||
|
|
|||
|
switch (AX25Sess->status)
|
|||
|
{
|
|||
|
case STAT_TRY_LINK:
|
|||
|
|
|||
|
set_link(AX25Sess, AX25Sess->Path);
|
|||
|
break;
|
|||
|
|
|||
|
case STAT_TRY_UNLINK:
|
|||
|
|
|||
|
set_try_unlink(AX25Sess, AX25Sess->Path);
|
|||
|
break;
|
|||
|
|
|||
|
case STAT_WAIT_ANS:
|
|||
|
|
|||
|
set_chk_link(AX25Sess, AX25Sess->Path);
|
|||
|
break;
|
|||
|
|
|||
|
case STAT_CHK_LINK:
|
|||
|
|
|||
|
set_chk_link(AX25Sess, AX25Sess->Path);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
rst_t1(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (AX25Sess->t3 >= idletime[snd_ch] * 10)
|
|||
|
{
|
|||
|
set_chk_link(AX25Sess, AX25Sess->Path);
|
|||
|
rst_t1(AX25Sess);
|
|||
|
rst_t3(AX25Sess);
|
|||
|
}
|
|||
|
|
|||
|
if (AX25Sess->status == STAT_LINK)
|
|||
|
inc_t3(AX25Sess);
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
// KISS ACKMODE
|
|||
|
//if (snd_status[snd_ch]<>SND_TX) and KISSServ then KISS_send_ack1(snd_ch);
|
|||
|
}
|
|||
|
|
|||
|
TAX25Port * get_free_port(int snd_ch)
|
|||
|
{
|
|||
|
int i = 0;
|
|||
|
|
|||
|
while (i < port_num)
|
|||
|
{
|
|||
|
if (AX25Port[snd_ch][i].status == STAT_NO_LINK)
|
|||
|
return &AX25Port[snd_ch][i];
|
|||
|
|
|||
|
i++;
|
|||
|
}
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
TAX25Port * get_user_port(int snd_ch, Byte * path)
|
|||
|
{
|
|||
|
TAX25Port * AX25Sess = NULL;
|
|||
|
|
|||
|
int i = 0;
|
|||
|
|
|||
|
|
|||
|
while (i < port_num)
|
|||
|
{
|
|||
|
AX25Sess = &AX25Port[snd_ch][i];
|
|||
|
|
|||
|
if (AX25Sess->status != STAT_NO_LINK && memcmp(AX25Sess->ReversePath, path, AX25Sess->pathLen) == 0)
|
|||
|
return AX25Sess;
|
|||
|
|
|||
|
i++;
|
|||
|
}
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
boolean get_user_dupe(int snd_ch, Byte * path)
|
|||
|
{
|
|||
|
int i = 0;
|
|||
|
TAX25Port * AX25Sess;
|
|||
|
|
|||
|
while (i < port_num)
|
|||
|
{
|
|||
|
AX25Sess = &AX25Port[snd_ch][i];
|
|||
|
|
|||
|
if (AX25Sess->status != STAT_NO_LINK && memcmp(AX25Sess->ReversePath, path, AX25Sess->pathLen) == 0)
|
|||
|
return TRUE;
|
|||
|
|
|||
|
i++;
|
|||
|
}
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
TAX25Port * get_user_port_by_calls(int snd_ch, char * CallFrom, char * CallTo)
|
|||
|
{
|
|||
|
int i = 0;
|
|||
|
TAX25Port * AX25Sess = NULL;
|
|||
|
|
|||
|
while (i < port_num)
|
|||
|
{
|
|||
|
AX25Sess = &AX25Port[snd_ch][i];
|
|||
|
|
|||
|
if (AX25Sess->status != STAT_NO_LINK &&
|
|||
|
strcmp(CallFrom, AX25Sess->mycall) == 0 && strcmp(CallTo, AX25Sess->corrcall) == 0)
|
|||
|
|
|||
|
return AX25Sess;
|
|||
|
|
|||
|
i++;
|
|||
|
}
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
void * get_sock_by_port(TAX25Port * AX25Sess)
|
|||
|
{
|
|||
|
void * socket = (void *)-1;
|
|||
|
|
|||
|
if (AX25Sess)
|
|||
|
socket = AX25Sess->socket;
|
|||
|
|
|||
|
return socket;
|
|||
|
}
|
|||
|
|
|||
|
void Digipeater(int snd_ch, string * frame)
|
|||
|
{
|
|||
|
char call[16];
|
|||
|
Byte * addr = &frame->Data[7]; // Origin
|
|||
|
string * frameCopy;
|
|||
|
|
|||
|
int n = 8; // Max digis
|
|||
|
|
|||
|
if (list_digi_callsigns[snd_ch].Count == 0)
|
|||
|
return;
|
|||
|
|
|||
|
// Look for first unused digi
|
|||
|
|
|||
|
while ((addr[6] & 1) == 0 && n--) // until end of address
|
|||
|
{
|
|||
|
addr += 7;
|
|||
|
|
|||
|
if ((addr[6] & 128) == 0)
|
|||
|
{
|
|||
|
// unused digi - is it addressed to us?
|
|||
|
|
|||
|
memcpy(call, addr, 7);
|
|||
|
call[6] &= 0x7E; // Mask end of call
|
|||
|
|
|||
|
// See if in digi list
|
|||
|
|
|||
|
int i;
|
|||
|
|
|||
|
for (i = 0; i < list_digi_callsigns->Count; i++)
|
|||
|
{
|
|||
|
if (memcmp(list_digi_callsigns->Items[i]->Data, call, 7) == 0)
|
|||
|
{
|
|||
|
UCHAR KISSBuffer[512];
|
|||
|
int Length;
|
|||
|
|
|||
|
// for us
|
|||
|
|
|||
|
addr[6] |= 128; // set digi'ed
|
|||
|
|
|||
|
// TX Frames need a KISS control on front
|
|||
|
|
|||
|
frameCopy = newString();
|
|||
|
|
|||
|
frameCopy->Data[0] = 0;
|
|||
|
frameCopy->Length = 1;
|
|||
|
|
|||
|
stringAdd(frameCopy, frame->Data, frame->Length); // Exclude CRC
|
|||
|
|
|||
|
addr[6] &= 0x7f; // clear digi'ed from original;
|
|||
|
|
|||
|
// Send to TNC
|
|||
|
|
|||
|
// ? Don't we just send to TNC?
|
|||
|
|
|||
|
Length = KISS_encode(KISSBuffer, 0, frameCopy);
|
|||
|
|
|||
|
KISSSendtoServer(KISSSockCopy[snd_ch], KISSBuffer, Length);
|
|||
|
|
|||
|
monitor_frame(0, frameCopy, "", 1, 0); // Monitor
|
|||
|
freeString(frameCopy);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void analiz_frame(int snd_ch, string * frame, void * socket, boolean fecflag)
|
|||
|
{
|
|||
|
Byte path[80];
|
|||
|
string * data = 0;
|
|||
|
Byte pid, nr, ns, f_type, f_id, rpt, cr, pf;
|
|||
|
Byte * ptr;
|
|||
|
|
|||
|
int excluded = 0;
|
|||
|
int len;
|
|||
|
|
|||
|
TAX25Port * AX25Sess;
|
|||
|
|
|||
|
// mod_icon_status = mod_rx;
|
|||
|
|
|||
|
len = frame->Length;
|
|||
|
|
|||
|
if (len < PKT_ERR)
|
|||
|
return;
|
|||
|
|
|||
|
data = newString();
|
|||
|
|
|||
|
decode_frame(frame->Data, frame->Length, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr);
|
|||
|
|
|||
|
// if is_excluded_call(snd_ch,path) then excluded:=TRUE;
|
|||
|
// if is_excluded_frm(snd_ch,f_id,data) then excluded:=TRUE;
|
|||
|
|
|||
|
// CRC Collision Check
|
|||
|
|
|||
|
if (!is_correct_path(path, pid))
|
|||
|
{
|
|||
|
// Duff path - if Non-AX25 filter active log and discard
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
monitor_frame(snd_ch, frame, "", 0, excluded); // Monitor
|
|||
|
|
|||
|
// Digipeat frame
|
|||
|
|
|||
|
Digipeater(snd_ch, frame);
|
|||
|
|
|||
|
if (!is_last_digi(path))
|
|||
|
{
|
|||
|
freeString(data);
|
|||
|
return; // Don't process if still unused digis
|
|||
|
}
|
|||
|
|
|||
|
// Clear reapeated bits from digi path
|
|||
|
|
|||
|
ptr = &path[13];
|
|||
|
|
|||
|
while ((*ptr & 1) == 0) // end of address
|
|||
|
{
|
|||
|
ptr += 7;
|
|||
|
*(ptr) &= 0x7f; // Clear digi'd bit
|
|||
|
}
|
|||
|
|
|||
|
// search for port of correspondent
|
|||
|
|
|||
|
AX25Sess = get_user_port(snd_ch, path);
|
|||
|
|
|||
|
// if not an active session, AX25Sess will be NULL
|
|||
|
|
|||
|
// if (AX25Sess == NULL)
|
|||
|
// socket = in_list_incoming_mycall(path);
|
|||
|
// else
|
|||
|
// socket = get_sock_by_port(AX25Sess);
|
|||
|
|
|||
|
// link analysis
|
|||
|
|
|||
|
if (AX25Sess == NULL)
|
|||
|
{
|
|||
|
if (f_id == U_UI)
|
|||
|
{
|
|||
|
CheckUIFrame(path, data);
|
|||
|
freeString(data);
|
|||
|
return; // Don't precess UI at the moment
|
|||
|
}
|
|||
|
|
|||
|
// No Session. If socket is set (so call is in incoming calls list) and SABM set up session
|
|||
|
|
|||
|
if (listenEnable == 0)
|
|||
|
{
|
|||
|
set_DM(snd_ch, path);
|
|||
|
freeString(data);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (f_id != U_SABM) // Not SABM
|
|||
|
{
|
|||
|
// // send DM if P set
|
|||
|
|
|||
|
if (cr == SET_C)
|
|||
|
{
|
|||
|
switch (f_id)
|
|||
|
{
|
|||
|
case U_DISC:
|
|||
|
case S_RR:
|
|||
|
case S_REJ:
|
|||
|
case S_RNR:
|
|||
|
case I_I:
|
|||
|
|
|||
|
set_DM(snd_ch, path);
|
|||
|
break;
|
|||
|
|
|||
|
case U_UI:
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
set_FRMR(snd_ch, path, f_id);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
freeString(data);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Must be SABM. See if it would duplicate an existing session (but could it - wouldn't that be found earlier ??
|
|||
|
|
|||
|
if (get_user_dupe(snd_ch, path)) // Not SABM or a duplicate call pair
|
|||
|
{
|
|||
|
freeString(data);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
AX25Sess = get_free_port(snd_ch);
|
|||
|
|
|||
|
if (AX25Sess == NULL)
|
|||
|
{
|
|||
|
// if there are no free ports for connection - reject
|
|||
|
|
|||
|
Byte Rev[80];
|
|||
|
|
|||
|
reverse_addr(path, Rev, strlen(path));
|
|||
|
set_DM(snd_ch, Rev);
|
|||
|
freeString(data);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// initialise new session
|
|||
|
|
|||
|
AX25Sess->snd_ch = snd_ch;
|
|||
|
|
|||
|
AX25Sess->corrcall[ConvFromAX25(&path[7], AX25Sess->corrcall)] = 0;
|
|||
|
AX25Sess->mycall[ConvFromAX25(path, AX25Sess->mycall)] = 0;
|
|||
|
AX25Sess->digi[0] = 0;
|
|||
|
|
|||
|
// rst_timer(snd_ch, free_port);
|
|||
|
|
|||
|
strcpy(AX25Sess->kind, "Incoming");
|
|||
|
AX25Sess->socket = socket;
|
|||
|
|
|||
|
Debugprintf("incoming call socket = %x", socket);
|
|||
|
|
|||
|
// I think we need to reverse the path
|
|||
|
|
|||
|
AX25Sess->pathLen = strlen(path);
|
|||
|
strcpy(AX25Sess->ReversePath, path);
|
|||
|
reverse_addr(path, AX25Sess->Path, strlen(path));
|
|||
|
}
|
|||
|
|
|||
|
// we process a packet on the necessary port
|
|||
|
|
|||
|
memcpy(path, AX25Sess->Path, AX25Sess->pathLen);
|
|||
|
|
|||
|
switch (f_id)
|
|||
|
{
|
|||
|
case I_I:
|
|||
|
|
|||
|
on_I(socket, AX25Sess, pid, path, data, nr, ns, pf, cr, fecflag);
|
|||
|
break;
|
|||
|
|
|||
|
case S_RR:
|
|||
|
|
|||
|
on_RR(AX25Sess, path, nr, pf, cr);
|
|||
|
break;
|
|||
|
|
|||
|
case S_RNR:
|
|||
|
|
|||
|
on_RNR(AX25Sess, path, nr, pf, cr);
|
|||
|
break;
|
|||
|
|
|||
|
case S_REJ:
|
|||
|
|
|||
|
on_REJ(AX25Sess, path, nr, pf, cr);
|
|||
|
break;
|
|||
|
|
|||
|
case S_SREJ:
|
|||
|
|
|||
|
on_SREJ(AX25Sess, path, nr, pf, cr);
|
|||
|
break;
|
|||
|
|
|||
|
case U_SABM:
|
|||
|
|
|||
|
on_SABM(socket, AX25Sess);
|
|||
|
break;
|
|||
|
|
|||
|
case U_DISC:
|
|||
|
|
|||
|
on_DISC(AX25Sess);
|
|||
|
break;
|
|||
|
|
|||
|
case U_UA:
|
|||
|
|
|||
|
on_UA(AX25Sess);
|
|||
|
break;
|
|||
|
|
|||
|
case U_DM:
|
|||
|
|
|||
|
on_DM(AX25Sess);
|
|||
|
break;
|
|||
|
|
|||
|
case U_UI:
|
|||
|
|
|||
|
on_UI(AX25Sess, pf, cr);
|
|||
|
break;
|
|||
|
|
|||
|
case U_FRMR:
|
|||
|
|
|||
|
on_FRMR(socket, AX25Sess, path);
|
|||
|
break;
|
|||
|
}
|
|||
|
freeString(data);
|
|||
|
}
|
|||
|
|
|||
|
int get_addr(char * Calls, UCHAR * AXCalls);
|
|||
|
|
|||
|
void Send_UI(int port, Byte PID, char * CallFrom, char *CallTo, Byte * Msg, int MsgLen)
|
|||
|
{
|
|||
|
Byte path[80];
|
|||
|
char Calls[80];
|
|||
|
string * Data = newString();
|
|||
|
string * Frame;
|
|||
|
|
|||
|
UCHAR KISSBuffer[512];
|
|||
|
int Length;
|
|||
|
|
|||
|
stringAdd(Data, Msg, MsgLen);
|
|||
|
|
|||
|
sprintf(Calls, "%s,%s", CallTo, CallFrom);
|
|||
|
|
|||
|
get_addr(Calls, path);
|
|||
|
|
|||
|
Frame = make_frame(Data, path, PID, 0, 0, U_FRM, U_UI, FALSE, SET_F, SET_C);
|
|||
|
|
|||
|
// ? Don't we just send to TNC?
|
|||
|
|
|||
|
Length = KISS_encode(KISSBuffer, 0, Frame);
|
|||
|
|
|||
|
KISSSendtoServer(KISSSockCopy[port], KISSBuffer, Length);
|
|||
|
|
|||
|
monitor_frame(0, Frame, "", 1, 0); // Monitor
|
|||
|
freeString(Frame);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|