/*
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 "UZ7HOStuff.h"

extern int RSID_SABM[4];
extern int RSID_UI[4];
extern int RSID_SetModem[4];
extern int needRSID[4];
extern int needRSID[4];

BOOL useKISSControls = 0;

#define FEND 0xc0
#define FESC 0xDB
#define TFEND 0xDC
#define TFESC 0xDD
#define KISS_ACKMODE 0x0C
#define KISS_DATA 0
#define QTSMKISSCMD 7


/*

unit ax25_l2;

interface

uses sysutils,classes;

  procedure frame_optimize(snd_ch,port: byte; var buf: TStringList);
  procedure analiz_frame(snd_ch: byte; frame,code: string);
  procedure send_data_buf(snd_ch,port: byte; path: string; nr: byte);
  procedure add_pkt_buf(snd_ch,port: byte; data: string);
  procedure set_link(snd_ch,port: byte; path: string);
  procedure set_unlink(socket: integer; snd_ch,port: byte; path: string);
  procedure set_chk_link(snd_ch,port: byte; path: string);
  procedure UpdateActiveConnects(snd_ch: byte);
  procedure timer_event;
  procedure rst_values(snd_ch,port: byte);
  procedure rst_timer(snd_ch,port: byte);
  function get_free_port(snd_ch: byte; var port: byte): boolean;
  function get_user_port_by_calls(snd_ch: byte; var port: byte; CallFrom,CallTo: string): boolean;

implementation

uses ax25,ax25_agw,sm_main,kiss_mode;
*/

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);

TAX25Port * get_user_port(int snd_ch, Byte * path);
void analiz_frame(int snd_ch, string * frame, char * code, boolean fecflag);
boolean is_last_digi(Byte *path);
int is_excluded_call(int snd_ch, unsigned char * path);
boolean is_correct_path(Byte * path, Byte pid);
void KISS_on_data_out(int port, string * frame, int TX);
void ax25_info_init(TAX25Port * AX25Sess);
void  clr_frm_win(TAX25Port * AX25Sess);
void decode_frame(Byte * frame, int len, Byte * path, string * data,
	Byte * pid, Byte * nr, Byte * ns, Byte * f_type, Byte * f_id,
	Byte *  rpt, Byte * pf, Byte * cr);
void ax25_info_init(TAX25Port * AX25Sess);
void AGW_AX25_disc(TAX25Port * AX25Sess, Byte mode);
void AGW_AX25_conn(TAX25Port * AX25Sess, int snd_ch, Byte mode);
int number_digi(char * path);
void AGW_AX25_data_in(void  * socket, int snd_ch, int PID, Byte * path, string * data);



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++;
	}
}

void  add_pkt_buf(TAX25Port * AX25Sess, string * data)
{
	boolean found = 0;
	string * frm;
	int i = 0;

	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, Byte * path)
{
	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;
	string path = { 0 }; 
	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->Path);
			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->Path);
		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->Path);
	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;
	boolean found = FALSE;
	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);

			found = TRUE;

			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);

		AGW_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();

	Data->Data[0] = frameType;
	Data->Data[1] = 0;
	Data->Data[2] = 1;			// Invalid CTL Byte
	Data->Length = 3;

	reverse_addr(path, revpath, strlen(path));

	add_pkt_buf(&AX25Port[snd_ch][0], make_frame(Data, revpath, 0, 0, 0, U_FRM, U_FRMR, FALSE, SET_P, SET_R));

	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�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)
{
	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)
{
	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)
{
	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)
{
	string * collector_data;
	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�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);

			AGW_AX25_data_in(socket, AX25Sess->snd_ch, PID, path, stringAdd(data, collector_data->Data, collector_data->Length));

			add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, pf, SET_R));
		}
		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  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;

		AGW_AX25_disc(AX25Sess, MODE_OTHER);
		Clear(&AX25Sess->frame_buf);

		AX25Sess->status = STAT_NO_LINK;
	}

	if (AX25Sess->status == STAT_TRY_LINK)
	{
		AGW_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);
			AGW_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)
	{
		AGW_AX25_conn(AX25Sess, AX25Sess->snd_ch, MODE_OTHER);
		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;

		if (RSID_SABM[AX25Sess->snd_ch])			// Send RSID before SABM/UA
			needRSID[AX25Sess->snd_ch] = 1;

	}

	add_pkt_buf(AX25Sess, make_frame(NULL, AX25Sess->Path, 0, 0, 0, U_FRM, U_UA, FALSE, SET_P, SET_R));
}

void on_DISC(void * socket, TAX25Port * AX25Sess)
{
	if (AX25Sess->status != STAT_NO_LINK)
	{
		AX25Sess->info.stat_end_ses = time(NULL);
		AGW_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));

		if (RSID_SABM[AX25Sess->snd_ch])			// Send RSID before SABM/UA
			needRSID[AX25Sess->snd_ch] = 1;
	}

	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(void * socket, TAX25Port * AX25Sess)
{
	if (AX25Sess->status != STAT_NO_LINK)
	{
		AX25Sess->info.stat_end_ses = time(NULL);
		AGW_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(void *socket, TAX25Port * AX25Sess)
{
	switch (AX25Sess->status)
	{
	case STAT_TRY_LINK:

		AX25Sess->info.stat_begin_ses = time(NULL);
		AX25Sess->status = STAT_LINK;
		AGW_AX25_conn(AX25Sess, AX25Sess->snd_ch, MODE_OUR);
		break;

	case STAT_TRY_UNLINK:

		AX25Sess->info.stat_end_ses = time(NULL);
		AGW_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)
{
}

void on_FRMR(void * socket, TAX25Port * AX25Sess, Byte * path)
{
	if (AX25Sess->status != STAT_NO_LINK)
	{
		AX25Sess->info.stat_end_ses = time(NULL);

		AGW_AX25_disc(AX25Sess, 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;
	void * socket;
	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 (snd_status[snd_ch] != SND_TX)  //when at the reception
				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);

					//socket:=get_incoming_socket_by_call(AX25Sess->mycall);

					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;
	int need_free_port;

	i = 0;
	
	need_free_port = FALSE;
	
	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;
	int port = 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)
{
	boolean addr_end, flag_replaced, digi_stop;
	word crc;
	char call[16];
	Byte * addr = &frame->Data[7];					// Origon
	int len = frame->Length - 2;					// not FCS
	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?

			UCHAR CRCString[2];

			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)
				{
					// 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 - 2);		// Exclude CRC

					addr[6] &= 0x7f;				// clear digi'ed from original;

					// Need to redo crc

					crc = get_fcs(frameCopy->Data, len);

					CRCString[0] = crc & 0xff;
					CRCString[1] = crc >> 8;

					stringAdd(frameCopy, CRCString, 2);

					Add(&all_frame_buf[snd_ch], frameCopy);

					return;
				}
			}
		}
	}
}

void analiz_frame(int snd_ch, string * frame, char * code, boolean fecflag)
{
	int  port, free_port;
	Byte path[80];
	string  *data = newString();
	Byte  pid, nr, ns, f_type, f_id, rpt, cr, pf;
	boolean	need_free_port;
	void * socket = NULL;
	boolean	incoming_conn = 0;
	Byte revpath[80];
	int pathlen;
	Byte * ptr;

	int excluded = 0;
	int len;

	TAX25Port * AX25Sess;

	//	mod_icon_status = mod_rx;

	len = frame->Length;

	if (len < PKT_ERR)
		return;

	bytes[snd_ch] += frame->Length;			// For AGW stats

	if (AGWServ)
		AGW_AX25_frame_analiz(snd_ch, TRUE, frame);

	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))
		excluded = TRUE;

	// if is_excluded_frm(snd_ch,f_id,data) then excluded:=TRUE;
	
	if (excluded)
		return;

	if (NonAX25[snd_ch])
	{
		if (AGWServ)
			AGW_Raw_monitor(snd_ch, frame);
		if (KISSServ)
			KISS_on_data_out(snd_ch, frame, 0);
	}
	
	// CRC Collision Check

	if (!is_correct_path(path, pid))
	{
		// Duff path - if Non-AX25 filter active log and discard

		if (NonAX25[snd_ch])
		{
			put_frame(snd_ch, frame, "NON-AX25", FALSE, excluded);
			return;
		}
	}

	put_frame(snd_ch, frame, code, 0, excluded);				// Monitor

	if (!NonAX25[snd_ch])
	{
		if (AGWServ)
			AGW_Raw_monitor(snd_ch, frame);

		if (KISSServ)
		{
			if (useKISSControls)
			{
				// Send a KISS Control frame with frame stats before data frame

				int len = strlen(code);
				UCHAR * Control = (UCHAR *)malloc(64);

				len = sprintf(Control, "%c%cPKTINFO [%s]%c", FEND, (snd_ch) << 4 | QTSMKISSCMD, code, FEND);

				KISSSendtoServer(NULL, Control, len);
			}
			KISS_on_data_out(snd_ch, frame, 0);
		}
	}

	// Digipeat frame

	Digipeater(snd_ch, frame);

	if (!is_last_digi(path))
		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)
	{
		// No Session. If socket is set (so call is in incoming calls list) and SABM set up session

		if (socket == NULL)
			return;						// not for us

		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);
				}


			}
			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
			return;

		AX25Sess = get_free_port(snd_ch);

		if (AX25Sess == NULL)
		{
			// if there are no free ports for connection - beat off

			Byte Rev[80];

			reverse_addr(path, Rev, strlen(path));
			set_DM(snd_ch, Rev);
			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(socket, AX25Sess);
		break;

	case U_UA:

		on_UA(socket, AX25Sess);
		break;

	case U_DM:

		on_DM(socket, AX25Sess);
		break;

	case U_UI:

		on_UI(AX25Sess, pf, cr);
		break;

	case U_FRMR:

		on_FRMR(socket, AX25Sess, path);
		break;
	}
}