1700 lines
47 KiB
C
1700 lines
47 KiB
C
/* proti.c
|
||
The 'i' protocol.
|
||
|
||
Copyright (C) 1992, 1993, 1995 Ian Lance Taylor
|
||
|
||
This file is part of the Taylor UUCP package.
|
||
|
||
This program 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 2 of the
|
||
License, or (at your option) any later version.
|
||
|
||
This program 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 this program; if not, write to the Free Software
|
||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||
|
||
The author of the program may be contacted at ian@airs.com or
|
||
c/o Cygnus Support, 48 Grove Street, Somerville, MA 02144.
|
||
*/
|
||
|
||
#include "uucp.h"
|
||
|
||
#if USE_RCS_ID
|
||
const char proti_rcsid[] = "$Id$";
|
||
#endif
|
||
|
||
#include <ctype.h>
|
||
#include <errno.h>
|
||
|
||
#include "uudefs.h"
|
||
#include "uuconf.h"
|
||
#include "conn.h"
|
||
#include "trans.h"
|
||
#include "system.h"
|
||
#include "prot.h"
|
||
|
||
/* The 'i' protocol is a simple sliding window protocol, created by
|
||
me. It is in many ways similar to the 'g' protocol. Several ideas
|
||
are also taken from the paper ``A High-Throughput Message Transport
|
||
System'' by P. Lauder. I don't know where the paper was published,
|
||
but the author's e-mail address is piers@cs.su.oz.au. However, I
|
||
haven't adopted his main idea, which is to dispense with windows
|
||
entirely. This is because some links still do require flow control
|
||
and, more importantly, because I want to have a limit to the amount
|
||
of data I must be able to resend upon request. To reduce the costs
|
||
of window acknowledgements, I use a large window and only require
|
||
an ack at the halfway point.
|
||
|
||
Each packet starts with a header containing the following
|
||
information:
|
||
|
||
Intro byte 8 bits byte 1
|
||
Packet number 5 bits byte 2
|
||
Local channel 3 bits
|
||
Packet ack 5 bits byte 3
|
||
Remote channel 3 bits
|
||
Packet type 3 bits bytes 4-5
|
||
Direction 1 bit
|
||
Data length 12 bits
|
||
Header check 8 bits byte 6
|
||
|
||
If the data length is not 0, this is followed by the data and a 32
|
||
bit CRC checksum.
|
||
|
||
The following packet types are defined:
|
||
|
||
SYNC Initialize the connection
|
||
DATA Data packet
|
||
ACK Simple acknowledgement with no data
|
||
NAK Negative acknowledgement; requests resend of single packet
|
||
SPOS Set file position
|
||
CLOSE Close the connection
|
||
*/
|
||
|
||
/* The offsets of the bytes in the packet header. */
|
||
|
||
#define IHDR_INTRO (0)
|
||
#define IHDR_LOCAL (1)
|
||
#define IHDR_REMOTE (2)
|
||
#define IHDR_CONTENTS1 (3)
|
||
#define IHDR_CONTENTS2 (4)
|
||
#define IHDR_CHECK (5)
|
||
|
||
/* Macros to set and extract values of IHDR_LOCAL and IHDR_REMOTE. */
|
||
#define IHDRWIN_SET(iseq, ichan) (((iseq) << 3) | (ichan))
|
||
#define IHDRWIN_GETSEQ(ival) (((ival) >> 3) & 0x1f)
|
||
#define IHDRWIN_GETCHAN(ival) ((ival) & 0x07)
|
||
|
||
/* Macros to set and extract values of IHDR_CONTENTS fields. */
|
||
#define IHDRCON_SET1(ttype, fcaller, cbytes) \
|
||
(((ttype) << 5) | ((fcaller) ? (1 << 4) : 0) | (((cbytes) >> 8) & 0x0f))
|
||
#define IHDRCON_SET2(ttype, fcaller, cbytes) ((cbytes) & 0xff)
|
||
#define THDRCON_GETTYPE(i1, i2) (((i1) >> 5) & 0x07)
|
||
#define FHDRCON_GETCALLER(i1, i2) (((i1) & (1 << 4)) != 0)
|
||
#define CHDRCON_GETBYTES(i1, i2) ((((i1) & 0x0f) << 8) | ((i2) & 0xff))
|
||
|
||
/* Macros for the IHDR_CHECK field. */
|
||
#define IHDRCHECK_VAL(zhdr) \
|
||
((zhdr[IHDR_LOCAL] \
|
||
^ zhdr[IHDR_REMOTE] \
|
||
^ zhdr[IHDR_CONTENTS1] \
|
||
^ zhdr[IHDR_CONTENTS2]) \
|
||
& 0xff)
|
||
|
||
/* Length of the packet header. */
|
||
#define CHDRLEN (6)
|
||
|
||
/* Amount of space to skip between start of packet and actual data.
|
||
This is used to make the actual data longword aligned, to encourage
|
||
good performance when copying data into the buffer. */
|
||
#define CHDRSKIPLEN (CHDRLEN + (sizeof (long) - CHDRLEN % sizeof (long)))
|
||
|
||
/* Amount of space to skip between memory buffer and header. */
|
||
#define CHDROFFSET (CHDRSKIPLEN - CHDRLEN)
|
||
|
||
/* Length of the trailing checksum. */
|
||
#define CCKSUMLEN (4)
|
||
|
||
/* Macros to set and get the checksum. These multiply evaluate their
|
||
arguments. */
|
||
#define ICKSUM_GET(z) \
|
||
((((((((unsigned long) ((z)[0] & 0xff)) << 8) \
|
||
| (unsigned long) ((z)[1] & 0xff)) << 8) \
|
||
| (unsigned long) ((z)[2] & 0xff)) << 8) \
|
||
| (unsigned long) ((z)[3] & 0xff))
|
||
#define UCKSUM_SET(z, i) \
|
||
(void) ((z)[0] = (((i) >> 24) & 0xff), \
|
||
(z)[1] = (((i) >> 16) & 0xff), \
|
||
(z)[2] = (((i) >> 8) & 0xff), \
|
||
(z)[3] = ((i) & 0xff))
|
||
|
||
/* The header introduction character. */
|
||
#define IINTRO ('\007')
|
||
|
||
/* The packet types. */
|
||
|
||
#define DATA (0)
|
||
#define SYNC (1)
|
||
#define ACK (2)
|
||
#define NAK (3)
|
||
#define SPOS (4)
|
||
#define CLOSE (5)
|
||
|
||
/* Largest possible packet size. */
|
||
#define IMAXPACKSIZE ((1 << 12) - 1)
|
||
|
||
/* Largest possible sequence number (plus 1). */
|
||
#define IMAXSEQ 32
|
||
|
||
/* Get the next sequence number given a sequence number. */
|
||
#define INEXTSEQ(i) (((i) + 1) & (IMAXSEQ - 1))
|
||
|
||
/* Get the previous sequence number given a sequence number. */
|
||
#define IPREVSEQ(i) (((i) + IMAXSEQ - 1) & (IMAXSEQ - 1))
|
||
|
||
/* Compute i1 - i2 in sequence space (i.e., the number of packets from
|
||
i2 to i1). */
|
||
#define CSEQDIFF(i1, i2) (((i1) + IMAXSEQ - (i2)) & (IMAXSEQ - 1))
|
||
|
||
/* Largest possible channel number (plus 1). */
|
||
#define IMAXICHAN (8)
|
||
|
||
/* Default packet size to request (protocol parameter
|
||
``packet-size''). */
|
||
#define IREQUEST_PACKSIZE (1024)
|
||
|
||
/* Default window size to request (protocol parameter ``window''). */
|
||
#define IREQUEST_WINSIZE (16)
|
||
|
||
/* Default timeout to use when sending the SYNC packet (protocol
|
||
parameter ``sync-timeout''). */
|
||
#define CSYNC_TIMEOUT (10)
|
||
|
||
/* Default number of times to retry sending the SYNC packet (protocol
|
||
parameter ``sync-retries''). */
|
||
#define CSYNC_RETRIES (6)
|
||
|
||
/* Default timeout to use when waiting for a packet (protocol
|
||
parameter ``timeout''). */
|
||
#define CTIMEOUT (10)
|
||
|
||
/* Default number of times to retry sending a packet before giving up
|
||
(protocol parameter ``retries''). */
|
||
#define CRETRIES (6)
|
||
|
||
/* Default maximum level of errors to accept before giving up
|
||
(protocol parameter ``errors''). */
|
||
#define CERRORS (100)
|
||
|
||
/* Default decay rate. Each time we receive this many packets
|
||
succesfully, we decrement the error level by one (protocol
|
||
parameter ``error-decay''). */
|
||
#define CERROR_DECAY (10)
|
||
|
||
/* The default list of characters to avoid: XON and XOFF. This string
|
||
is processed as an escape sequence. This is 'j' protocol parameter
|
||
``avoid''; it is defined in this file because the 'i' and 'j'
|
||
protocols share protocol parameters. */
|
||
#define ZAVOID "\\021\\023"
|
||
|
||
/* Local variables. */
|
||
|
||
/* Packet size to request (protocol parameter ``packet-size''). */
|
||
static int iIrequest_packsize = IREQUEST_PACKSIZE;
|
||
|
||
/* Window size to request (protocol parameter ``window''). */
|
||
static int iIrequest_winsize = IREQUEST_WINSIZE;
|
||
|
||
/* Remote packet size (set from SYNC packet or from
|
||
iIforced_remote_packsize). */
|
||
static int iIremote_packsize;
|
||
|
||
/* Size which buffers were allocated for. */
|
||
static int iIalc_packsize;
|
||
|
||
/* Forced remote packet size, used if non-zero (protocol parameter
|
||
``remote-packet-size''). There is no forced remote window size
|
||
because the ACK strategy requires that both sides agree on the
|
||
window size. */
|
||
static int iIforced_remote_packsize = 0;
|
||
|
||
/* Remote window size (set from SYNC packet). */
|
||
static int iIremote_winsize;
|
||
|
||
/* Timeout to use when sending the SYNC packet (protocol
|
||
parameter ``sync-timeout''). */
|
||
int cIsync_timeout = CSYNC_TIMEOUT;
|
||
|
||
/* Number of times to retry sending the SYNC packet (protocol
|
||
parameter ``sync-retries''). */
|
||
static int cIsync_retries = CSYNC_RETRIES;
|
||
|
||
/* Timeout to use when waiting for a packet (protocol parameter
|
||
``timeout''). */
|
||
static int cItimeout = CTIMEOUT;
|
||
|
||
/* Timeout to use when waiting for an acknowledgement to open up space
|
||
in the window. This is computed based on the window size and the
|
||
connection speed. */
|
||
static int cIwindow_timeout = CTIMEOUT;
|
||
|
||
/* Number of times to retry sending a packet before giving up
|
||
(protocol parameter ``retries''). */
|
||
static int cIretries = CRETRIES;
|
||
|
||
/* Maximum level of errors to accept before giving up (protocol
|
||
parameter ``errors''). */
|
||
static int cIerrors = CERRORS;
|
||
|
||
/* Each time we receive this many packets succesfully, we decrement
|
||
the error level by one (protocol parameter ``error-decay''). */
|
||
static int cIerror_decay = CERROR_DECAY;
|
||
|
||
/* The number of packets we should wait to receive before sending an
|
||
ACK; this is set by default to half the window size we have
|
||
requested (protocol parameter ``ack-frequency''). */
|
||
static int cIack_frequency = 0;
|
||
|
||
/* The set of characters to avoid (protocol parameter ``avoid'').
|
||
This is actually part of the 'j' protocol; it is defined in this
|
||
file because the 'i' and 'j' protocols use the same protocol
|
||
parameters. */
|
||
const char *zJavoid_parameter = ZAVOID;
|
||
|
||
/* Routine to use when sending data. This is a hook for the 'j'
|
||
protocol. */
|
||
static boolean (*pfIsend) P((struct sconnection *qconn, const char *zsend,
|
||
size_t csend, boolean fdoread));
|
||
|
||
/* Routine to use to use when reading data. This is a hook for the
|
||
'j' protocol. */
|
||
static boolean (*pfIreceive) P((struct sconnection *qconn, size_t cneed,
|
||
size_t *pcrec, int ctimeout,
|
||
boolean freport));
|
||
|
||
/* Next sequence number to send. */
|
||
static int iIsendseq;
|
||
|
||
/* Last sequence number received. */
|
||
static int iIrecseq;
|
||
|
||
/* Last sequence number we have acknowledged. */
|
||
static int iIlocal_ack;
|
||
|
||
/* Last sequence number remote system has acknowledged. */
|
||
static int iIremote_ack;
|
||
|
||
/* File position we are sending from. */
|
||
static long iIsendpos;
|
||
|
||
/* File position we are receiving to. */
|
||
static long iIrecpos;
|
||
|
||
/* TRUE if closing the connection. */
|
||
static boolean fIclosing;
|
||
|
||
/* Array of sent packets indexed by packet number. */
|
||
static char *azIsendbuffers[IMAXSEQ];
|
||
|
||
/* Array of received packets that we aren't ready to process yet,
|
||
indexed by packet number. */
|
||
static char *azIrecbuffers[IMAXSEQ];
|
||
|
||
/* For each packet sequence number, record whether we sent a NAK for
|
||
the packet. */
|
||
static boolean afInaked[IMAXSEQ];
|
||
|
||
/* Number of SYNC packets received (used only to detect whether one
|
||
was received). */
|
||
static int cIsyncs;
|
||
|
||
/* Number of packets sent. */
|
||
static long cIsent_packets;
|
||
|
||
/* Number of packets received. */
|
||
static long cIreceived_packets;
|
||
|
||
/* Number of packets resent. */
|
||
static long cIresent_packets;
|
||
|
||
/* Number of bad packet headers received. */
|
||
static long cIbad_hdr;
|
||
|
||
/* Number of out of order packets received. */
|
||
static long cIbad_order;
|
||
|
||
/* Number of bad checksums received. */
|
||
static long cIbad_cksum;
|
||
|
||
/* Number of packets rejected by remote system. */
|
||
static long cIremote_rejects;
|
||
|
||
/* Protocol parameter commands. */
|
||
|
||
struct uuconf_cmdtab asIproto_params[] =
|
||
{
|
||
{ "packet-size", UUCONF_CMDTABTYPE_INT, (pointer) &iIrequest_packsize,
|
||
NULL },
|
||
{ "window", UUCONF_CMDTABTYPE_INT, (pointer) &iIrequest_winsize, NULL },
|
||
{ "remote-packet-size", UUCONF_CMDTABTYPE_INT,
|
||
(pointer) &iIforced_remote_packsize, NULL },
|
||
{ "sync-timeout", UUCONF_CMDTABTYPE_INT, (pointer) &cIsync_timeout,
|
||
NULL },
|
||
{ "sync-retries", UUCONF_CMDTABTYPE_INT, (pointer) &cIsync_retries,
|
||
NULL },
|
||
{ "timeout", UUCONF_CMDTABTYPE_INT, (pointer) &cItimeout, NULL },
|
||
{ "retries", UUCONF_CMDTABTYPE_INT, (pointer) &cIretries, NULL },
|
||
{ "errors", UUCONF_CMDTABTYPE_INT, (pointer) &cIerrors, NULL },
|
||
{ "error-decay", UUCONF_CMDTABTYPE_INT, (pointer) &cIerror_decay, NULL },
|
||
{ "ack-frequency", UUCONF_CMDTABTYPE_INT, (pointer) &cIack_frequency, NULL },
|
||
/* The ``avoid'' protocol parameter is part of the 'j' protocol, but
|
||
it is convenient for the 'i' and 'j' protocols to share the same
|
||
protocol parameter table. */
|
||
{ "avoid", UUCONF_CMDTABTYPE_STRING, (pointer) &zJavoid_parameter, NULL },
|
||
{ NULL, 0, NULL, NULL }
|
||
};
|
||
|
||
/* Local functions. */
|
||
|
||
static boolean finak P((struct sdaemon *qdaemon, int iseq));
|
||
static boolean firesend P((struct sdaemon *qdaemon));
|
||
static boolean fiwindow_wait P((struct sdaemon *qdaemon));
|
||
static boolean fiwait_for_packet P((struct sdaemon *qdaemon,
|
||
int ctimeout, int cretries,
|
||
boolean fone, boolean *ftimedout));
|
||
static boolean ficheck_errors P((struct sdaemon *qdaemon));
|
||
static boolean fiprocess_data P((struct sdaemon *qdaemon,
|
||
boolean *pfexit, boolean *pffound,
|
||
size_t *pcneed));
|
||
static boolean fiprocess_packet P((struct sdaemon *qdaemon,
|
||
const char *zhdr,
|
||
const char *zfirst, int cfirst,
|
||
const char *zsecond, int csecond,
|
||
boolean *pfexit));
|
||
|
||
/* The 'i' protocol start routine. The work is done in a routine
|
||
which is also called by the 'j' protocol start routine. */
|
||
|
||
boolean
|
||
fistart (qdaemon, pzlog)
|
||
struct sdaemon *qdaemon;
|
||
char **pzlog;
|
||
{
|
||
return fijstart (qdaemon, pzlog, IMAXPACKSIZE, fsend_data, freceive_data);
|
||
}
|
||
|
||
/* Start the protocol. This routine is called by both the 'i' and 'j'
|
||
protocol start routines. We keep transmitting a SYNC packet
|
||
containing the window and packet size we would like to receive
|
||
until we receive a SYNC packet from the remote system. The first
|
||
two bytes of the data contents of a SYNC packet are the maximum
|
||
packet size we want to receive (high byte, low byte), and the next
|
||
byte is the maximum window size we want to use. */
|
||
|
||
boolean
|
||
fijstart (qdaemon, pzlog, imaxpacksize, pfsend, pfreceive)
|
||
struct sdaemon *qdaemon;
|
||
char **pzlog;
|
||
int imaxpacksize;
|
||
boolean (*pfsend) P((struct sconnection *qconn, const char *zsend,
|
||
size_t csend, boolean fdoread));
|
||
boolean (*pfreceive) P((struct sconnection *qconn, size_t cneed,
|
||
size_t *pcrec, int ctimeout, boolean freport));
|
||
{
|
||
char ab[CHDRLEN + 4 + CCKSUMLEN];
|
||
unsigned long icksum;
|
||
int ctries;
|
||
int csyncs;
|
||
long ibaud;
|
||
|
||
*pzlog = NULL;
|
||
|
||
pfIsend = pfsend;
|
||
pfIreceive = pfreceive;
|
||
|
||
if (iIforced_remote_packsize <= 0
|
||
|| iIforced_remote_packsize > imaxpacksize)
|
||
iIforced_remote_packsize = 0;
|
||
else
|
||
iIremote_packsize = iIforced_remote_packsize;
|
||
iIalc_packsize = 0;
|
||
|
||
iIsendseq = 1;
|
||
iIrecseq = 0;
|
||
iIlocal_ack = 0;
|
||
iIremote_ack = 0;
|
||
iIsendpos = 0;
|
||
iIrecpos = 0;
|
||
fIclosing = FALSE;
|
||
|
||
cIsent_packets = 0;
|
||
cIreceived_packets = 0;
|
||
cIresent_packets = 0;
|
||
cIbad_hdr = 0;
|
||
cIbad_order = 0;
|
||
cIbad_cksum = 0;
|
||
cIremote_rejects = 0;
|
||
|
||
if (iIrequest_packsize < 0 || iIrequest_packsize > imaxpacksize)
|
||
{
|
||
ulog (LOG_ERROR, "Illegal protocol '%c' packet size; using %d",
|
||
qdaemon->qproto->bname, imaxpacksize);
|
||
iIrequest_packsize = imaxpacksize;
|
||
}
|
||
|
||
/* The maximum permissible window size is 16. Otherwise the
|
||
protocol can get confused because a duplicated packet may arrive
|
||
out of order. If the window size is large in such a case, the
|
||
duplicate packet may be treated as a packet in the upcoming
|
||
window, causing the protocol to assume that all intermediate
|
||
packets have been lost, leading to immense confusion. */
|
||
if (iIrequest_winsize < 0 || iIrequest_winsize > IMAXSEQ / 2)
|
||
{
|
||
ulog (LOG_ERROR, "Illegal protocol '%c' window size; using %d",
|
||
qdaemon->qproto->bname, IREQUEST_WINSIZE);
|
||
iIrequest_winsize = IREQUEST_WINSIZE;
|
||
}
|
||
|
||
/* The default for the ACK frequency is half the window size. */
|
||
if (cIack_frequency <= 0 || cIack_frequency >= iIrequest_winsize)
|
||
cIack_frequency = iIrequest_winsize / 2;
|
||
|
||
ab[IHDR_INTRO] = IINTRO;
|
||
ab[IHDR_LOCAL] = ab[IHDR_REMOTE] = IHDRWIN_SET (0, 0);
|
||
ab[IHDR_CONTENTS1] = IHDRCON_SET1 (SYNC, qdaemon->fcaller, 4);
|
||
ab[IHDR_CONTENTS2] = IHDRCON_SET2 (SYNC, qdaemon->fcaller, 4);
|
||
ab[IHDR_CHECK] = IHDRCHECK_VAL (ab);
|
||
ab[CHDRLEN + 0] = (iIrequest_packsize >> 8) & 0xff;
|
||
ab[CHDRLEN + 1] = iIrequest_packsize & 0xff;
|
||
ab[CHDRLEN + 2] = iIrequest_winsize;
|
||
ab[CHDRLEN + 3] = qdaemon->cchans;
|
||
icksum = icrc (ab + CHDRLEN, 4, ICRCINIT);
|
||
UCKSUM_SET (ab + CHDRLEN + 4, icksum);
|
||
|
||
/* The static cIsyncs is incremented each time a SYNC packet is
|
||
received. */
|
||
csyncs = cIsyncs;
|
||
ctries = 0;
|
||
|
||
while (TRUE)
|
||
{
|
||
boolean ftimedout;
|
||
|
||
DEBUG_MESSAGE3 (DEBUG_PROTO,
|
||
"fistart: Sending SYNC packsize %d winsize %d channels %d",
|
||
iIrequest_packsize, iIrequest_winsize, qdaemon->cchans);
|
||
|
||
if (! (*pfIsend) (qdaemon->qconn, ab, CHDRLEN + 4 + CCKSUMLEN,
|
||
TRUE))
|
||
return FALSE;
|
||
|
||
if (fiwait_for_packet (qdaemon, cIsync_timeout, 0, FALSE,
|
||
&ftimedout))
|
||
{
|
||
if (csyncs != cIsyncs)
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
if (! ftimedout)
|
||
return FALSE;
|
||
|
||
++ctries;
|
||
if (ctries > cIsync_retries)
|
||
{
|
||
ulog (LOG_ERROR, "Protocol startup failed");
|
||
return FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Calculate the window timeout. */
|
||
ibaud = iconn_baud (qdaemon->qconn);
|
||
if (ibaud == 0)
|
||
cIwindow_timeout = cItimeout;
|
||
else
|
||
{
|
||
/* We expect to receive an ACK about halfway through each
|
||
window. In principle, an entire window might be sitting in a
|
||
modem buffer while we are waiting for an ACK. Therefore, the
|
||
minimum time we should wait for an ACK is
|
||
(1/2 window size) * (seconds / byte) + (roundtrip time) ==
|
||
(1/2 window size) * (1 / (baud / 10)) + (roundtrip time) ==
|
||
(1/2 window size) * (10 / baud) + (roundtrip time) ==
|
||
(5 * (window size)) / baud + (roundtrip time)
|
||
|
||
The window size is iIremote_packsize * iIremote_winsize. For
|
||
typical settings of packsize == 1024, winsize == 16, baud ==
|
||
9600, this equation works out to
|
||
(5 * 1024 * 16) / 9600 == 8 seconds
|
||
We then take cItimeout as the round trip time, which gives us
|
||
some flexibility. We get more flexibility because it is
|
||
quite likely that by the time we have finished sending out
|
||
the last packet in a window, the first one has already been
|
||
received by the remote system. */
|
||
cIwindow_timeout = ((5 * iIremote_packsize * iIremote_winsize) / ibaud
|
||
+ cItimeout);
|
||
}
|
||
|
||
/* If we are the callee, bump both timeouts by one, to make it less
|
||
likely that both systems will timeout simultaneously. */
|
||
if (! qdaemon->fcaller)
|
||
{
|
||
++cItimeout;
|
||
++cIwindow_timeout;
|
||
}
|
||
|
||
/* We got a SYNC packet; set up packet buffers to use. */
|
||
if (iIremote_packsize > imaxpacksize)
|
||
iIremote_packsize = imaxpacksize;
|
||
do
|
||
{
|
||
int iseq;
|
||
|
||
for (iseq = 0; iseq < IMAXSEQ; iseq++)
|
||
{
|
||
azIrecbuffers[iseq] = NULL;
|
||
afInaked[iseq] = FALSE;
|
||
azIsendbuffers[iseq] = (char *) malloc (iIremote_packsize
|
||
+ CHDRSKIPLEN
|
||
+ CCKSUMLEN);
|
||
if (azIsendbuffers[iseq] == NULL)
|
||
{
|
||
int ifree;
|
||
|
||
for (ifree = 0; ifree < iseq; ifree++)
|
||
free ((pointer) azIsendbuffers[ifree]);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (iseq >= IMAXSEQ)
|
||
{
|
||
*pzlog =
|
||
zbufalc (sizeof "protocol '' sending packet/window / receiving /"
|
||
+ 64);
|
||
sprintf (*pzlog,
|
||
"protocol '%c' sending packet/window %d/%d receiving %d/%d",
|
||
qdaemon->qproto->bname, (int) iIremote_packsize,
|
||
(int) iIremote_winsize, (int) iIrequest_packsize,
|
||
(int) iIrequest_winsize);
|
||
|
||
iIalc_packsize = iIremote_packsize;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
iIremote_packsize >>= 1;
|
||
}
|
||
while (iIremote_packsize > 200);
|
||
|
||
ulog (LOG_ERROR,
|
||
"'%c' protocol startup failed; insufficient memory for packets",
|
||
qdaemon->qproto->bname);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Shut down the protocol. We can be fairly informal about this,
|
||
since we know that the upper level protocol has already exchanged
|
||
hangup messages. If we didn't know that, we would have to make
|
||
sure that all packets before the CLOSE had been received. */
|
||
|
||
boolean
|
||
fishutdown (qdaemon)
|
||
struct sdaemon *qdaemon;
|
||
{
|
||
char *z;
|
||
size_t clen;
|
||
|
||
fIclosing = TRUE;
|
||
|
||
z = zigetspace (qdaemon, &clen) - CHDRLEN;
|
||
|
||
z[IHDR_INTRO] = IINTRO;
|
||
z[IHDR_LOCAL] = IHDRWIN_SET (iIsendseq, 0);
|
||
z[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0);
|
||
iIlocal_ack = iIrecseq;
|
||
z[IHDR_CONTENTS1] = IHDRCON_SET1 (CLOSE, qdaemon->fcaller, 0);
|
||
z[IHDR_CONTENTS2] = IHDRCON_SET2 (CLOSE, qdaemon->fcaller, 0);
|
||
z[IHDR_CHECK] = IHDRCHECK_VAL (z);
|
||
|
||
DEBUG_MESSAGE0 (DEBUG_PROTO, "fishutdown: Sending CLOSE");
|
||
|
||
if (! (*pfIsend) (qdaemon->qconn, z, CHDRLEN, FALSE))
|
||
return FALSE;
|
||
|
||
ulog (LOG_NORMAL,
|
||
"Protocol '%c' packets: sent %ld, resent %ld, received %ld",
|
||
qdaemon->qproto->bname, cIsent_packets, cIresent_packets,
|
||
cIreceived_packets);
|
||
if (cIbad_hdr != 0
|
||
|| cIbad_cksum != 0
|
||
|| cIbad_order != 0
|
||
|| cIremote_rejects != 0)
|
||
ulog (LOG_NORMAL,
|
||
"Errors: header %ld, checksum %ld, order %ld, remote rejects %ld",
|
||
cIbad_hdr, cIbad_cksum, cIbad_order, cIremote_rejects);
|
||
|
||
/* Reset the protocol parameters to their default values. */
|
||
iIrequest_packsize = IREQUEST_PACKSIZE;
|
||
iIrequest_winsize = IREQUEST_WINSIZE;
|
||
iIforced_remote_packsize = 0;
|
||
cIsync_timeout = CSYNC_TIMEOUT;
|
||
cIsync_retries = CSYNC_RETRIES;
|
||
cItimeout = CTIMEOUT;
|
||
cIwindow_timeout = CTIMEOUT;
|
||
cIretries = CRETRIES;
|
||
cIerrors = CERRORS;
|
||
cIerror_decay = CERROR_DECAY;
|
||
cIack_frequency = 0;
|
||
zJavoid_parameter = ZAVOID;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Send a command string. These are just sent as normal packets,
|
||
ending in a packet containing a null byte. */
|
||
|
||
boolean
|
||
fisendcmd (qdaemon, z, ilocal, iremote)
|
||
struct sdaemon *qdaemon;
|
||
const char *z;
|
||
int ilocal;
|
||
int iremote;
|
||
{
|
||
size_t clen;
|
||
|
||
DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, "fisendcmd: Sending command \"%s\"", z);
|
||
|
||
clen = strlen (z);
|
||
|
||
while (TRUE)
|
||
{
|
||
char *zpacket;
|
||
size_t csize;
|
||
|
||
zpacket = zigetspace (qdaemon, &csize);
|
||
|
||
if (clen < csize)
|
||
{
|
||
memcpy (zpacket, z, clen + 1);
|
||
return fisenddata (qdaemon, zpacket, clen + 1, ilocal, iremote,
|
||
(long) -1);
|
||
}
|
||
|
||
memcpy (zpacket, z, csize);
|
||
z += csize;
|
||
clen -= csize;
|
||
|
||
if (! fisenddata (qdaemon, zpacket, csize, ilocal, iremote, (long) -1))
|
||
return FALSE;
|
||
}
|
||
/*NOTREACHED*/
|
||
}
|
||
|
||
/* Send a NAK. */
|
||
|
||
static boolean
|
||
finak (qdaemon, iseq)
|
||
struct sdaemon *qdaemon;
|
||
int iseq;
|
||
{
|
||
char abnak[CHDRLEN];
|
||
|
||
abnak[IHDR_INTRO] = IINTRO;
|
||
abnak[IHDR_LOCAL] = IHDRWIN_SET (iseq, 0);
|
||
abnak[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0);
|
||
iIlocal_ack = iIrecseq;
|
||
abnak[IHDR_CONTENTS1] = IHDRCON_SET1 (NAK, qdaemon->fcaller, 0);
|
||
abnak[IHDR_CONTENTS2] = IHDRCON_SET2 (NAK, qdaemon->fcaller, 0);
|
||
abnak[IHDR_CHECK] = IHDRCHECK_VAL (abnak);
|
||
|
||
afInaked[iseq] = TRUE;
|
||
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"finak: Sending NAK %d", iseq);
|
||
|
||
return (*pfIsend) (qdaemon->qconn, abnak, CHDRLEN, TRUE);
|
||
}
|
||
|
||
/* Resend the latest packet the remote has not acknowledged. */
|
||
|
||
static boolean
|
||
firesend (qdaemon)
|
||
struct sdaemon *qdaemon;
|
||
{
|
||
int iseq;
|
||
char *zhdr;
|
||
size_t clen;
|
||
|
||
iseq = INEXTSEQ (iIremote_ack);
|
||
if (iseq == iIsendseq)
|
||
{
|
||
/* Everything has been ACKed and there is nothing to resend. */
|
||
return TRUE;
|
||
}
|
||
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"firesend: Resending packet %d", iseq);
|
||
|
||
/* Update the received sequence number. */
|
||
zhdr = azIsendbuffers[iseq] + CHDROFFSET;
|
||
if (IHDRWIN_GETSEQ (zhdr[IHDR_REMOTE]) != iIrecseq)
|
||
{
|
||
int iremote;
|
||
|
||
iremote = IHDRWIN_GETCHAN (zhdr[IHDR_REMOTE]);
|
||
zhdr[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, iremote);
|
||
zhdr[IHDR_CHECK] = IHDRCHECK_VAL (zhdr);
|
||
iIlocal_ack = iIrecseq;
|
||
}
|
||
|
||
++cIresent_packets;
|
||
|
||
clen = CHDRCON_GETBYTES (zhdr[IHDR_CONTENTS1],
|
||
zhdr[IHDR_CONTENTS2]);
|
||
|
||
return (*pfIsend) (qdaemon->qconn, zhdr,
|
||
CHDRLEN + clen + (clen > 0 ? CCKSUMLEN : 0),
|
||
TRUE);
|
||
}
|
||
|
||
/* Wait until there is an opening in the receive window of the remote
|
||
system. */
|
||
|
||
static boolean
|
||
fiwindow_wait (qdaemon)
|
||
struct sdaemon *qdaemon;
|
||
{
|
||
/* iIsendseq is the sequence number we are sending, and iIremote_ack
|
||
is the last sequence number acknowledged by the remote. */
|
||
while (CSEQDIFF (iIsendseq, iIremote_ack) > iIremote_winsize)
|
||
{
|
||
/* If a NAK is lost, it is possible for the other side to be
|
||
sending a stream of packets while we are waiting for an ACK.
|
||
This should be caught in fiprocess_data; if it is about to
|
||
send an ACK, but it has an unacknowledged packet to send, it
|
||
sends the entire packet. Hopefully that will trigger an ACK
|
||
or a NAK and get us going again. */
|
||
DEBUG_MESSAGE0 (DEBUG_PROTO, "fiwindow_wait: Waiting for ACK");
|
||
if (! fiwait_for_packet (qdaemon, cIwindow_timeout, cIretries,
|
||
TRUE, (boolean *) NULL))
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Get buffer space to use for packet data. We return a pointer to
|
||
the space to be used for the actual data, leaving room for the
|
||
header. */
|
||
|
||
/*ARGSUSED*/
|
||
char *
|
||
zigetspace (qdaemon, pclen)
|
||
struct sdaemon *qdaemon;
|
||
size_t *pclen;
|
||
{
|
||
*pclen = iIremote_packsize;
|
||
return azIsendbuffers[iIsendseq] + CHDRSKIPLEN;
|
||
}
|
||
|
||
/* Send a data packet. The zdata argument will always point to value
|
||
returned by zigetspace, so we know that we have room before it for
|
||
the header information. */
|
||
|
||
boolean
|
||
fisenddata (qdaemon, zdata, cdata, ilocal, iremote, ipos)
|
||
struct sdaemon *qdaemon;
|
||
char *zdata;
|
||
size_t cdata;
|
||
int ilocal;
|
||
int iremote;
|
||
long ipos;
|
||
{
|
||
char *zhdr;
|
||
unsigned long icksum;
|
||
boolean fret;
|
||
|
||
#if DEBUG > 0
|
||
if (ilocal < 0 || ilocal >= IMAXICHAN
|
||
|| iremote < 0 || iremote >= IMAXICHAN)
|
||
ulog (LOG_FATAL, "fisenddata: ilocal %d iremote %d", ilocal, iremote);
|
||
#endif
|
||
|
||
/* If we are changing the file position, we must send an SPOS
|
||
packet. */
|
||
if (ipos != iIsendpos && ipos != (long) -1)
|
||
{
|
||
int inext;
|
||
char *zspos;
|
||
|
||
/* We need to get a buffer to hold the SPOS packet, and it needs
|
||
to be next sequence number. However, the data we have been
|
||
given is currently in the next sequence number buffer. So we
|
||
shuffle the buffers around. */
|
||
inext = INEXTSEQ (iIsendseq);
|
||
zspos = azIsendbuffers[inext];
|
||
azIsendbuffers[inext] = zdata - CHDRSKIPLEN;
|
||
azIsendbuffers[iIsendseq] = zspos;
|
||
zspos += CHDROFFSET;
|
||
|
||
zspos[IHDR_INTRO] = IINTRO;
|
||
zspos[IHDR_LOCAL] = IHDRWIN_SET (iIsendseq, 0);
|
||
zspos[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0);
|
||
iIlocal_ack = iIrecseq;
|
||
zspos[IHDR_CONTENTS1] = IHDRCON_SET1 (SPOS, qdaemon->fcaller,
|
||
CCKSUMLEN);
|
||
zspos[IHDR_CONTENTS2] = IHDRCON_SET2 (SPOS, qdaemon->fcaller,
|
||
CCKSUMLEN);
|
||
zspos[IHDR_CHECK] = IHDRCHECK_VAL (zspos);
|
||
UCKSUM_SET (zspos + CHDRLEN, (unsigned long) ipos);
|
||
icksum = icrc (zspos + CHDRLEN, CCKSUMLEN, ICRCINIT);
|
||
UCKSUM_SET (zspos + CHDRLEN + CCKSUMLEN, icksum);
|
||
|
||
/* Wait for an opening in the window. */
|
||
if (iIremote_winsize > 0
|
||
&& CSEQDIFF (iIsendseq, iIremote_ack) > iIremote_winsize)
|
||
{
|
||
if (! fiwindow_wait (qdaemon))
|
||
return FALSE;
|
||
}
|
||
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO, "fisenddata: Sending SPOS %ld",
|
||
ipos);
|
||
|
||
if (! (*pfIsend) (qdaemon->qconn, zspos,
|
||
CHDRLEN + CCKSUMLEN + CCKSUMLEN, TRUE))
|
||
return FALSE;
|
||
|
||
iIsendseq = INEXTSEQ (iIsendseq);
|
||
iIsendpos = ipos;
|
||
}
|
||
|
||
zhdr = zdata - CHDRLEN;
|
||
zhdr[IHDR_INTRO] = IINTRO;
|
||
zhdr[IHDR_LOCAL] = IHDRWIN_SET (iIsendseq, ilocal);
|
||
zhdr[IHDR_CONTENTS1] = IHDRCON_SET1 (DATA, qdaemon->fcaller, cdata);
|
||
zhdr[IHDR_CONTENTS2] = IHDRCON_SET2 (DATA, qdaemon->fcaller, cdata);
|
||
|
||
/* Compute and set the checksum. */
|
||
if (cdata > 0)
|
||
{
|
||
icksum = icrc (zdata, cdata, ICRCINIT);
|
||
UCKSUM_SET (zdata + cdata, icksum);
|
||
}
|
||
|
||
/* Wait until there is an opening in the window (we hope to not have
|
||
to wait here at all, actually; ideally the window should be large
|
||
enough to avoid a wait). */
|
||
if (iIremote_winsize > 0
|
||
&& CSEQDIFF (iIsendseq, iIremote_ack) > iIremote_winsize)
|
||
{
|
||
if (! fiwindow_wait (qdaemon))
|
||
return FALSE;
|
||
}
|
||
|
||
/* We only fill in IHDR_REMOTE now, since only now do know the
|
||
correct value of iIrecseq. */
|
||
zhdr[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, iremote);
|
||
iIlocal_ack = iIrecseq;
|
||
zhdr[IHDR_CHECK] = IHDRCHECK_VAL (zhdr);
|
||
|
||
DEBUG_MESSAGE4 (DEBUG_PROTO,
|
||
"fisenddata: Sending packet %d size %d local %d remote %d",
|
||
iIsendseq, (int) cdata, ilocal, iremote);
|
||
|
||
iIsendseq = INEXTSEQ (iIsendseq);
|
||
++cIsent_packets;
|
||
|
||
fret = (*pfIsend) (qdaemon->qconn, zhdr,
|
||
cdata + CHDRLEN + (cdata > 0 ? CCKSUMLEN : 0),
|
||
TRUE);
|
||
|
||
iIsendpos += cdata;
|
||
|
||
if (fret && iPrecstart != iPrecend)
|
||
{
|
||
boolean fexit;
|
||
|
||
fret = fiprocess_data (qdaemon, &fexit, (boolean *) NULL,
|
||
(size_t *) NULL);
|
||
}
|
||
|
||
return fret;
|
||
}
|
||
|
||
/* Wait for data to come in. */
|
||
|
||
boolean
|
||
fiwait (qdaemon)
|
||
struct sdaemon *qdaemon;
|
||
{
|
||
return fiwait_for_packet (qdaemon, cItimeout, cIretries,
|
||
FALSE, (boolean *) NULL);
|
||
}
|
||
|
||
/* Wait for a packet. Either there is no data to send, or the remote
|
||
window is full. */
|
||
|
||
static boolean
|
||
fiwait_for_packet (qdaemon, ctimeout, cretries, fone, pftimedout)
|
||
struct sdaemon *qdaemon;
|
||
int ctimeout;
|
||
int cretries;
|
||
boolean fone;
|
||
boolean *pftimedout;
|
||
{
|
||
int cshort;
|
||
int ctimeouts;
|
||
|
||
if (pftimedout != NULL)
|
||
*pftimedout = FALSE;
|
||
|
||
cshort = 0;
|
||
ctimeouts = 0;
|
||
|
||
while (TRUE)
|
||
{
|
||
boolean fexit, ffound;
|
||
size_t cneed;
|
||
size_t crec;
|
||
|
||
if (! fiprocess_data (qdaemon, &fexit, &ffound, &cneed))
|
||
return FALSE;
|
||
|
||
if (fexit || (fone && ffound))
|
||
return TRUE;
|
||
|
||
if (cneed == 0)
|
||
continue;
|
||
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO, "fiwait_for_packet: Need %d bytes",
|
||
(int) cneed);
|
||
|
||
if (! (*pfIreceive) (qdaemon->qconn, cneed, &crec, ctimeout, TRUE))
|
||
return FALSE;
|
||
|
||
if (crec != 0)
|
||
{
|
||
/* If we didn't get enough data twice in a row, we may have
|
||
dropped some data and be waiting for the end of a large
|
||
packet. Incrementing iPrecstart will force
|
||
fiprocess_data to skip the current packet and try to find
|
||
the next one. */
|
||
if (crec >= cneed)
|
||
cshort = 0;
|
||
else
|
||
{
|
||
++cshort;
|
||
if (cshort > 1)
|
||
{
|
||
iPrecstart = (iPrecstart + 1) % CRECBUFLEN;
|
||
cshort = 0;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
int i;
|
||
|
||
/* We timed out on the read. */
|
||
++ctimeouts;
|
||
if (ctimeouts > cretries)
|
||
{
|
||
if (cretries > 0)
|
||
ulog (LOG_ERROR, "Timed out waiting for packet");
|
||
if (pftimedout != NULL)
|
||
*pftimedout = TRUE;
|
||
return FALSE;
|
||
}
|
||
|
||
/* Clear out the list of packets we have sent NAKs for. We
|
||
should have seen some sort of response by now. */
|
||
for (i = 0; i < IMAXSEQ; i++)
|
||
afInaked[i] = FALSE;
|
||
|
||
/* Send a NAK for the packet we want, and, if we have an
|
||
unacknowledged packet, send it again. */
|
||
if (! finak (qdaemon, INEXTSEQ (iIrecseq))
|
||
|| ! firesend (qdaemon))
|
||
return FALSE;
|
||
}
|
||
}
|
||
/*NOTREACHED*/
|
||
}
|
||
|
||
/* Make sure we haven't overflowed the permissible error level. */
|
||
|
||
static boolean
|
||
ficheck_errors (qdaemon)
|
||
struct sdaemon *qdaemon;
|
||
{
|
||
if (cIerrors < 0)
|
||
return TRUE;
|
||
|
||
if (((cIbad_order + cIbad_hdr + cIbad_cksum + cIremote_rejects)
|
||
- (cIreceived_packets / cIerror_decay))
|
||
> cIerrors)
|
||
{
|
||
/* Try shrinking the packet size. */
|
||
if (iIrequest_packsize > 400)
|
||
{
|
||
char absync[CHDRLEN + 3 + CCKSUMLEN];
|
||
unsigned long icksum;
|
||
|
||
/* Don't bother sending the number of channels in this
|
||
packet. */
|
||
iIrequest_packsize /= 2;
|
||
absync[IHDR_INTRO] = IINTRO;
|
||
absync[IHDR_LOCAL] = IHDRWIN_SET (0, 0);
|
||
absync[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0);
|
||
iIlocal_ack = iIrecseq;
|
||
absync[IHDR_CONTENTS1] = IHDRCON_SET1 (SYNC, qdaemon->fcaller, 3);
|
||
absync[IHDR_CONTENTS2] = IHDRCON_SET2 (SYNC, qdaemon->fcaller, 3);
|
||
absync[IHDR_CHECK] = IHDRCHECK_VAL (absync);
|
||
absync[CHDRLEN + 0] = (iIrequest_packsize >> 8) & 0xff;
|
||
absync[CHDRLEN + 1] = iIrequest_packsize & 0xff;
|
||
absync[CHDRLEN + 2] = iIrequest_winsize;
|
||
icksum = icrc (absync + CHDRLEN, 3, ICRCINIT);
|
||
UCKSUM_SET (absync + CHDRLEN + 3, icksum);
|
||
|
||
cIerrors *= 2;
|
||
|
||
DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"ficheck_errors: Sending SYNC packsize %d winsize %d",
|
||
iIrequest_packsize, iIrequest_winsize);
|
||
|
||
return (*pfIsend) (qdaemon->qconn, absync,
|
||
CHDRLEN + 3 + CCKSUMLEN, TRUE);
|
||
}
|
||
|
||
ulog (LOG_ERROR, "Too many '%c' protocol errors",
|
||
qdaemon->qproto->bname);
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Process data waiting in the receive buffer, passing to the
|
||
fgot_data function. */
|
||
|
||
static boolean
|
||
fiprocess_data (qdaemon, pfexit, pffound, pcneed)
|
||
struct sdaemon *qdaemon;
|
||
boolean *pfexit;
|
||
boolean *pffound;
|
||
size_t *pcneed;
|
||
{
|
||
boolean fbadhdr;
|
||
|
||
if (pfexit != NULL)
|
||
*pfexit = FALSE;
|
||
if (pffound != NULL)
|
||
*pffound = FALSE;
|
||
|
||
fbadhdr = FALSE;
|
||
|
||
while (iPrecstart != iPrecend)
|
||
{
|
||
char ab[CHDRLEN];
|
||
int cfirst, csecond;
|
||
char *zfirst, *zsecond;
|
||
int i;
|
||
int iget;
|
||
int ttype;
|
||
int iseq;
|
||
int csize;
|
||
int iack;
|
||
|
||
/* If we're closing the connection, ignore any data remaining in
|
||
the input buffer. */
|
||
if (fIclosing)
|
||
{
|
||
if (pfexit != NULL)
|
||
*pfexit = TRUE;
|
||
if (pcneed != NULL)
|
||
*pcneed = 0;
|
||
return TRUE;
|
||
}
|
||
|
||
/* Look for the IINTRO character. */
|
||
if (abPrecbuf[iPrecstart] != IINTRO)
|
||
{
|
||
char *zintro;
|
||
int cintro;
|
||
|
||
cintro = iPrecend - iPrecstart;
|
||
if (cintro < 0)
|
||
cintro = CRECBUFLEN - iPrecstart;
|
||
|
||
zintro = memchr (abPrecbuf + iPrecstart, IINTRO, (size_t) cintro);
|
||
|
||
if (zintro == NULL)
|
||
{
|
||
iPrecstart = (iPrecstart + cintro) % CRECBUFLEN;
|
||
continue;
|
||
}
|
||
|
||
/* We don't need % CRECBUFLEN here because zintro - (abPrecbuf
|
||
+ iPrecstart) < cintro <= CRECBUFLEN - iPrecstart. */
|
||
iPrecstart += zintro - (abPrecbuf + iPrecstart);
|
||
}
|
||
|
||
/* Get the header into ab. */
|
||
for (i = 0, iget = iPrecstart;
|
||
i < CHDRLEN && iget != iPrecend;
|
||
i++, iget = (iget + 1) % CRECBUFLEN)
|
||
ab[i] = abPrecbuf[iget];
|
||
|
||
if (i < CHDRLEN)
|
||
{
|
||
if (pcneed != NULL)
|
||
*pcneed = CHDRLEN - i;
|
||
return TRUE;
|
||
}
|
||
|
||
if ((ab[IHDR_CHECK] & 0xff) != IHDRCHECK_VAL (ab)
|
||
|| (FHDRCON_GETCALLER (ab[IHDR_CONTENTS1], ab[IHDR_CONTENTS2])
|
||
? qdaemon->fcaller : ! qdaemon->fcaller))
|
||
{
|
||
/* We only report a single bad header message per call, to
|
||
avoid generating many errors if we get many INTRO bytes
|
||
in a row. */
|
||
if (! fbadhdr)
|
||
{
|
||
DEBUG_MESSAGE0 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"fiprocess_data: Bad header");
|
||
|
||
++cIbad_hdr;
|
||
if (! ficheck_errors (qdaemon))
|
||
return FALSE;
|
||
|
||
fbadhdr = TRUE;
|
||
}
|
||
|
||
iPrecstart = (iPrecstart + 1) % CRECBUFLEN;
|
||
continue;
|
||
}
|
||
|
||
zfirst = zsecond = NULL;
|
||
cfirst = csecond = 0;
|
||
|
||
ttype = THDRCON_GETTYPE (ab[IHDR_CONTENTS1], ab[IHDR_CONTENTS2]);
|
||
if (ttype == DATA || ttype == SPOS || ttype == CLOSE)
|
||
iseq = IHDRWIN_GETSEQ (ab[IHDR_LOCAL]);
|
||
else
|
||
iseq = -1;
|
||
csize = CHDRCON_GETBYTES (ab[IHDR_CONTENTS1], ab[IHDR_CONTENTS2]);
|
||
|
||
if (iseq != -1)
|
||
{
|
||
/* Make sure this packet is in our receive window. The last
|
||
packet we have acked is iIlocal_ack. */
|
||
if (iIrequest_winsize > 0
|
||
&& CSEQDIFF (iseq, iIlocal_ack) > iIrequest_winsize)
|
||
{
|
||
DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"fiprocess_data: Out of order packet %d (ack %d)",
|
||
iseq, iIlocal_ack);
|
||
|
||
++cIbad_order;
|
||
if (! ficheck_errors (qdaemon))
|
||
return FALSE;
|
||
|
||
iPrecstart = (iPrecstart + 1) % CRECBUFLEN;
|
||
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (csize > 0)
|
||
{
|
||
int cinbuf;
|
||
char abcksum[CCKSUMLEN];
|
||
unsigned long ickdata;
|
||
|
||
cinbuf = iPrecend - iPrecstart;
|
||
if (cinbuf < 0)
|
||
cinbuf += CRECBUFLEN;
|
||
if (cinbuf < CHDRLEN + csize + CCKSUMLEN)
|
||
{
|
||
if (pcneed != NULL)
|
||
*pcneed = CHDRLEN + csize + CCKSUMLEN - cinbuf;
|
||
return TRUE;
|
||
}
|
||
|
||
if (iPrecend > iPrecstart)
|
||
{
|
||
cfirst = csize;
|
||
zfirst = abPrecbuf + iPrecstart + CHDRLEN;
|
||
}
|
||
else
|
||
{
|
||
cfirst = CRECBUFLEN - (iPrecstart + CHDRLEN);
|
||
if (cfirst <= 0)
|
||
{
|
||
/* Here cfirst is non-positive, so subtracting from
|
||
abPrecbuf will actually skip the appropriate number
|
||
of bytes at the start of abPrecbuf. */
|
||
zfirst = abPrecbuf - cfirst;
|
||
cfirst = csize;
|
||
}
|
||
else
|
||
{
|
||
if (cfirst >= csize)
|
||
cfirst = csize;
|
||
else
|
||
{
|
||
zsecond = abPrecbuf;
|
||
csecond = csize - cfirst;
|
||
}
|
||
zfirst = abPrecbuf + iPrecstart + CHDRLEN;
|
||
}
|
||
}
|
||
|
||
/* Get the checksum into abcksum. */
|
||
for (i = 0, iget = (iPrecstart + CHDRLEN + csize) % CRECBUFLEN;
|
||
i < CCKSUMLEN;
|
||
i++, iget = (iget + 1) % CRECBUFLEN)
|
||
abcksum[i] = abPrecbuf[iget];
|
||
|
||
ickdata = icrc (zfirst, (size_t) cfirst, ICRCINIT);
|
||
if (csecond > 0)
|
||
ickdata = icrc (zsecond, (size_t) csecond, ickdata);
|
||
|
||
if (ICKSUM_GET (abcksum) != ickdata)
|
||
{
|
||
DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"fiprocess_data: Bad checksum; data %lu, frame %lu",
|
||
ickdata, ICKSUM_GET (abcksum));
|
||
|
||
++cIbad_cksum;
|
||
if (! ficheck_errors (qdaemon))
|
||
return FALSE;
|
||
|
||
/* If this sequence number is in our receive window,
|
||
send a NAK. iIrecseq is the last sequence number we
|
||
have succesfully received. */
|
||
if (iseq != -1
|
||
&& iseq != iIrecseq
|
||
&& (iIrequest_winsize <= 0
|
||
|| CSEQDIFF (iseq, iIrecseq) <= iIrequest_winsize)
|
||
&& azIrecbuffers[iseq] == NULL)
|
||
{
|
||
if (! finak (qdaemon, iseq))
|
||
return FALSE;
|
||
}
|
||
|
||
iPrecstart = (iPrecstart + 1) % CRECBUFLEN;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
/* Here we know that this is a valid packet, so we can adjust
|
||
iPrecstart accordingly. */
|
||
if (csize == 0)
|
||
iPrecstart = (iPrecstart + CHDRLEN) % CRECBUFLEN;
|
||
else
|
||
{
|
||
iPrecstart = ((iPrecstart + CHDRLEN + csize + CCKSUMLEN)
|
||
% CRECBUFLEN);
|
||
++cIreceived_packets;
|
||
}
|
||
|
||
/* Get the ack from the packet, if appropriate. iIsendseq is
|
||
the next sequence number we are going to send, and
|
||
iIremote_ack is the last sequence number acknowledged by the
|
||
remote system. */
|
||
iack = IHDRWIN_GETSEQ (ab[IHDR_REMOTE]);
|
||
if (iIremote_winsize > 0
|
||
&& iack != iIsendseq
|
||
&& CSEQDIFF (iack, iIremote_ack) <= iIremote_winsize
|
||
&& CSEQDIFF (iIsendseq, iack) <= iIremote_winsize)
|
||
{
|
||
/* Call uwindow_acked each time packet 0 is acked. */
|
||
if (iack < iIremote_ack)
|
||
uwindow_acked (qdaemon, FALSE);
|
||
iIremote_ack = iack;
|
||
}
|
||
|
||
if (iseq != -1)
|
||
{
|
||
/* If we already sent a NAK for this packet, and we have not
|
||
seen the previous packet, then forget that we sent a NAK
|
||
for this and any preceding packets. This is to handle
|
||
the following sequence:
|
||
receive packet 0
|
||
packets 1 and 2 lost
|
||
receive packet 3
|
||
send NAK 1
|
||
send NAK 2
|
||
packet 1 lost
|
||
receive packet 2
|
||
At this point we want to send NAK 1. */
|
||
if (afInaked[iseq]
|
||
&& azIrecbuffers[IPREVSEQ (iseq)] == NULL)
|
||
{
|
||
for (i = INEXTSEQ (iIrecseq);
|
||
i != iseq;
|
||
i = INEXTSEQ (i))
|
||
afInaked[i] = FALSE;
|
||
afInaked[iseq] = FALSE;
|
||
}
|
||
|
||
/* If we haven't handled all previous packets, we must save
|
||
off this packet and deal with it later. */
|
||
if (iseq != INEXTSEQ (iIrecseq))
|
||
{
|
||
if (iseq == iIrecseq
|
||
|| (iIrequest_winsize > 0
|
||
&& CSEQDIFF (iseq, iIrecseq) > iIrequest_winsize))
|
||
{
|
||
DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"fiprocess_data: Ignoring out of order packet %d (recseq %d)",
|
||
iseq, iIrecseq);
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"fiprocess_data: Saving unexpected packet %d (recseq %d)",
|
||
iseq, iIrecseq);
|
||
|
||
if (azIrecbuffers[iseq] == NULL)
|
||
{
|
||
azIrecbuffers[iseq] = zbufalc ((size_t) (CHDRLEN
|
||
+ csize));
|
||
memcpy (azIrecbuffers[iseq], ab, CHDRLEN);
|
||
if (csize > 0)
|
||
{
|
||
memcpy (azIrecbuffers[iseq] + CHDRLEN, zfirst,
|
||
(size_t) cfirst);
|
||
if (csecond > 0)
|
||
memcpy (azIrecbuffers[iseq] + CHDRLEN + cfirst,
|
||
zsecond, (size_t) csecond);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Send NAK's for each packet between the last one we
|
||
received and this one, avoiding any packets for which
|
||
we've already sent NAK's or which we've already
|
||
received. */
|
||
for (i = INEXTSEQ (iIrecseq);
|
||
i != iseq;
|
||
i = INEXTSEQ (i))
|
||
{
|
||
if (! afInaked[i]
|
||
&& azIrecbuffers[i] == NULL)
|
||
{
|
||
if (! finak (qdaemon, i))
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
iIrecseq = iseq;
|
||
}
|
||
|
||
if (pffound != NULL)
|
||
*pffound = TRUE;
|
||
|
||
if (! fiprocess_packet (qdaemon, ab, zfirst, cfirst, zsecond, csecond,
|
||
pfexit))
|
||
return FALSE;
|
||
|
||
if (iseq != -1)
|
||
{
|
||
int inext;
|
||
|
||
/* If we've already received the next packet(s), process
|
||
them. */
|
||
inext = INEXTSEQ (iIrecseq);
|
||
while (azIrecbuffers[inext] != NULL)
|
||
{
|
||
char *z;
|
||
int c;
|
||
|
||
z = azIrecbuffers[inext];
|
||
c = CHDRCON_GETBYTES (z[IHDR_CONTENTS1], z[IHDR_CONTENTS2]);
|
||
iIrecseq = inext;
|
||
if (! fiprocess_packet (qdaemon, z, z + CHDRLEN, c,
|
||
(char *) NULL, 0, pfexit))
|
||
return FALSE;
|
||
ubuffree (azIrecbuffers[inext]);
|
||
azIrecbuffers[inext] = NULL;
|
||
inext = INEXTSEQ (inext);
|
||
}
|
||
}
|
||
|
||
/* If we have received half of our window size or more since the
|
||
last ACK, send one now. Sending an ACK for half the window
|
||
at a time should significantly cut the acknowledgement
|
||
traffic when only one side is sending. We should normally
|
||
not have to send an ACK if we have data to send, since each
|
||
packet sent will ACK the most recently received packet.
|
||
However, it can happen if we receive a burst of short
|
||
packets, such as a set of command acknowledgements. */
|
||
if (iIrequest_winsize > 0
|
||
&& CSEQDIFF (iIrecseq, iIlocal_ack) >= cIack_frequency)
|
||
{
|
||
char aback[CHDRLEN];
|
||
|
||
aback[IHDR_INTRO] = IINTRO;
|
||
aback[IHDR_LOCAL] = IHDRWIN_SET (0, 0);
|
||
aback[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0);
|
||
iIlocal_ack = iIrecseq;
|
||
aback[IHDR_CONTENTS1] = IHDRCON_SET1 (ACK, qdaemon->fcaller, 0);
|
||
aback[IHDR_CONTENTS2] = IHDRCON_SET2 (ACK, qdaemon->fcaller, 0);
|
||
aback[IHDR_CHECK] = IHDRCHECK_VAL (aback);
|
||
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO, "fiprocess_data: Sending ACK %d",
|
||
iIrecseq);
|
||
|
||
if (! (*pfIsend) (qdaemon->qconn, aback, CHDRLEN, TRUE))
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (pcneed != NULL)
|
||
*pcneed = CHDRLEN;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Process a single packet. */
|
||
|
||
static boolean
|
||
fiprocess_packet (qdaemon, zhdr, zfirst, cfirst, zsecond, csecond, pfexit)
|
||
struct sdaemon *qdaemon;
|
||
const char *zhdr;
|
||
const char *zfirst;
|
||
int cfirst;
|
||
const char *zsecond;
|
||
int csecond;
|
||
boolean *pfexit;
|
||
{
|
||
int ttype;
|
||
|
||
ttype = THDRCON_GETTYPE (zhdr[IHDR_CONTENTS1], zhdr[IHDR_CONTENTS2]);
|
||
switch (ttype)
|
||
{
|
||
case DATA:
|
||
{
|
||
int iseq;
|
||
boolean fret;
|
||
|
||
iseq = IHDRWIN_GETSEQ (zhdr[IHDR_LOCAL]);
|
||
DEBUG_MESSAGE4 (DEBUG_PROTO,
|
||
"fiprocess_packet: Got DATA packet %d size %d local %d remote %d",
|
||
iseq, cfirst + csecond,
|
||
IHDRWIN_GETCHAN (zhdr[IHDR_REMOTE]),
|
||
IHDRWIN_GETCHAN (zhdr[IHDR_LOCAL]));
|
||
fret = fgot_data (qdaemon, zfirst, (size_t) cfirst,
|
||
zsecond, (size_t) csecond,
|
||
IHDRWIN_GETCHAN (zhdr[IHDR_REMOTE]),
|
||
IHDRWIN_GETCHAN (zhdr[IHDR_LOCAL]),
|
||
iIrecpos,
|
||
INEXTSEQ (iIremote_ack) == iIsendseq,
|
||
pfexit);
|
||
iIrecpos += cfirst + csecond;
|
||
return fret;
|
||
}
|
||
|
||
case SYNC:
|
||
{
|
||
int ipack, iwin, cchans;
|
||
|
||
/* We accept a SYNC packet to adjust the packet and window
|
||
sizes at any time. */
|
||
if (cfirst + csecond < 3)
|
||
{
|
||
ulog (LOG_ERROR, "Bad SYNC packet");
|
||
return FALSE;
|
||
}
|
||
ipack = (zfirst[0] & 0xff) << 8;
|
||
if (cfirst > 1)
|
||
ipack |= zfirst[1] & 0xff;
|
||
else
|
||
ipack |= zsecond[0];
|
||
if (cfirst > 2)
|
||
iwin = zfirst[2];
|
||
else
|
||
iwin = zsecond[2 - cfirst];
|
||
|
||
/* The fourth byte in a SYNC packet is the number of channels
|
||
to use. This is optional. Switching the number of
|
||
channels in the middle of a conversation may cause
|
||
problems. */
|
||
if (cfirst + csecond <= 3)
|
||
cchans = 0;
|
||
else
|
||
{
|
||
if (cfirst > 3)
|
||
cchans = zfirst[3];
|
||
else
|
||
cchans = zsecond[3 - cfirst];
|
||
if (cchans > 0 && cchans < 8)
|
||
qdaemon->cchans = cchans;
|
||
}
|
||
|
||
DEBUG_MESSAGE3 (DEBUG_PROTO,
|
||
"fiprocess_packet: Got SYNC packsize %d winsize %d channels %d",
|
||
ipack, iwin, cchans);
|
||
|
||
if (iIforced_remote_packsize == 0
|
||
&& (iIalc_packsize == 0
|
||
|| ipack <= iIalc_packsize))
|
||
iIremote_packsize = ipack;
|
||
iIremote_winsize = iwin;
|
||
|
||
/* We increment a static variable to tell the initialization
|
||
code that a SYNC was received, and we set *pfexit to TRUE
|
||
to get out to the initialization code (this will do no harm
|
||
if we are called from elsewhere). */
|
||
++cIsyncs;
|
||
*pfexit = TRUE;
|
||
return TRUE;
|
||
}
|
||
|
||
case ACK:
|
||
/* There is nothing to do here, since the ack was already
|
||
handled in fiprocess_data. */
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO,
|
||
"fiprocess_packet: Got ACK %d",
|
||
IHDRWIN_GETSEQ (zhdr[IHDR_REMOTE]));
|
||
return TRUE;
|
||
|
||
case NAK:
|
||
/* We must resend the requested packet. */
|
||
{
|
||
int iseq;
|
||
char *zsend;
|
||
size_t clen;
|
||
|
||
++cIremote_rejects;
|
||
if (! ficheck_errors (qdaemon))
|
||
return FALSE;
|
||
|
||
iseq = IHDRWIN_GETSEQ (zhdr[IHDR_LOCAL]);
|
||
|
||
/* If the remote side times out while waiting for a packet, it
|
||
will send a NAK for the next packet it wants to see. If we
|
||
have not sent that packet yet, and we have no
|
||
unacknowledged data, it implies that the remote side has a
|
||
window full of data to send, which implies that our ACK has
|
||
been lost. Therefore, we send an ACK. */
|
||
if (iseq == iIsendseq &&
|
||
INEXTSEQ (iIremote_ack) == iIsendseq)
|
||
{
|
||
char aback[CHDRLEN];
|
||
|
||
aback[IHDR_INTRO] = IINTRO;
|
||
aback[IHDR_LOCAL] = IHDRWIN_SET (0, 0);
|
||
aback[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0);
|
||
iIlocal_ack = iIrecseq;
|
||
aback[IHDR_CONTENTS1] = IHDRCON_SET1 (ACK, qdaemon->fcaller, 0);
|
||
aback[IHDR_CONTENTS2] = IHDRCON_SET2 (ACK, qdaemon->fcaller, 0);
|
||
aback[IHDR_CHECK] = IHDRCHECK_VAL (aback);
|
||
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO, "fiprocess_packet: Sending ACK %d",
|
||
iIrecseq);
|
||
|
||
return (*pfIsend) (qdaemon->qconn, aback, CHDRLEN, TRUE);
|
||
}
|
||
else
|
||
{
|
||
if (iseq == iIsendseq
|
||
|| (iIremote_winsize > 0
|
||
&& (CSEQDIFF (iseq, iIremote_ack) > iIremote_winsize
|
||
|| CSEQDIFF (iIsendseq, iseq) > iIremote_winsize)))
|
||
{
|
||
DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"fiprocess_packet: Ignoring out of order NAK %d (sendseq %d)",
|
||
iseq, iIsendseq);
|
||
return TRUE;
|
||
}
|
||
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL,
|
||
"fiprocess_packet: Got NAK %d; resending packet",
|
||
iseq);
|
||
|
||
/* Update the received sequence number. */
|
||
zsend = azIsendbuffers[iseq] + CHDROFFSET;
|
||
if (IHDRWIN_GETSEQ (zsend[IHDR_REMOTE]) != iIrecseq)
|
||
{
|
||
int iremote;
|
||
|
||
iremote = IHDRWIN_GETCHAN (zsend[IHDR_REMOTE]);
|
||
zsend[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, iremote);
|
||
zsend[IHDR_CHECK] = IHDRCHECK_VAL (zsend);
|
||
iIlocal_ack = iIrecseq;
|
||
}
|
||
|
||
++cIresent_packets;
|
||
|
||
clen = CHDRCON_GETBYTES (zsend[IHDR_CONTENTS1],
|
||
zsend[IHDR_CONTENTS2]);
|
||
|
||
return (*pfIsend) (qdaemon->qconn, zsend,
|
||
CHDRLEN + clen + (clen > 0 ? CCKSUMLEN : 0),
|
||
TRUE);
|
||
}
|
||
}
|
||
|
||
case SPOS:
|
||
/* Set the file position. */
|
||
{
|
||
char abpos[CCKSUMLEN];
|
||
const char *zpos;
|
||
|
||
if (cfirst >= CCKSUMLEN)
|
||
zpos = zfirst;
|
||
else
|
||
{
|
||
memcpy (abpos, zfirst, (size_t) cfirst);
|
||
memcpy (abpos + cfirst, zsecond, (size_t) (CCKSUMLEN - cfirst));
|
||
zpos = abpos;
|
||
}
|
||
iIrecpos = (long) ICKSUM_GET (zpos);
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO,
|
||
"fiprocess_packet: Got SPOS %ld", iIrecpos);
|
||
return TRUE;
|
||
}
|
||
|
||
case CLOSE:
|
||
{
|
||
boolean fexpected;
|
||
|
||
fexpected = ! fLog_sighup || fIclosing;
|
||
if (! fexpected)
|
||
ulog (LOG_ERROR, "Received unexpected CLOSE packet");
|
||
else
|
||
DEBUG_MESSAGE0 (DEBUG_PROTO, "fiprocess_packet: Got CLOSE packet");
|
||
|
||
fIclosing = TRUE;
|
||
*pfexit = TRUE;
|
||
return fexpected;
|
||
}
|
||
|
||
default:
|
||
/* Just ignore unrecognized packet types, for future protocol
|
||
enhancements. */
|
||
DEBUG_MESSAGE1 (DEBUG_PROTO, "fiprocess_packet: Got packet type %d",
|
||
ttype);
|
||
return TRUE;
|
||
}
|
||
/*NOTREACHED*/
|
||
}
|