468 lines
23 KiB
C
468 lines
23 KiB
C
/* h316_udp.c: IMP/TIP Modem and Host Interface socket routines using UDP
|
|
|
|
Copyright (c) 2013 Robert Armstrong, bob@jfcl.com
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
ROBERT ARMSTRONG BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
Except as contained in this notice, the name of Robert Armstrong shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from Robert Armstrong.
|
|
|
|
|
|
REVISION HISTORY
|
|
|
|
udp socket routines
|
|
|
|
26-Jun-13 RLA Rewritten from TCP version
|
|
26-Nov-13 MP Rewritten to use TMXR layer packet semantics thus
|
|
allowing portability to all simh hosts.
|
|
2-Dec-13 RLA Improve error recovery if the other simh is restarted
|
|
|
|
OVERVIEW
|
|
|
|
This module emulates low level communications between two virtual modems
|
|
using UDP packets over the modern network connections. It's used by both
|
|
the IMP modem interface and the host interface modules to implement IMP to
|
|
IMP and IMP to HOST connections.
|
|
|
|
TCP vs UDP
|
|
|
|
Why UDP and not TCP? TCP has a couple of advantages after all - it's
|
|
stream oriented, which is intrinsically like a modem, and it handles all
|
|
the network "funny stuff" for us. TCP has a couple of problems too - first,
|
|
it's inherently asymmetrical. There's a "server" end which opens a master
|
|
socket and passively listens for connections, and a "client" end which
|
|
actively attempts to connect. That's annoying, but it can be worked around.
|
|
|
|
The big problem with TCP is that even though it treats the data like a stream
|
|
it's internally buffering it, and you really have absolutely no control over
|
|
when TCP will decide to send its buffer. Google "nagle algorithm" to get an
|
|
idea. Yes, you can set TCP_NODELAY to disable Nagle, but the data's still
|
|
buffered and it doesn't fix the problem. What's the issue with buffering?
|
|
It introduces completely unpredictable delays into the message traffic. A
|
|
transmitting IMP could send two or three (or ten or twenty!) messages before
|
|
TCP actually decides to try to deliver them to the destination.
|
|
|
|
And it turns out that IMPs are extraordinarily sensitive to line speed. The
|
|
IMP firmware actually goes to the trouble of measuring the effective line
|
|
speed by using the RTC to figure out how long it takes to send a message.
|
|
One thing that screws up the IMP to no end is variation in the effective
|
|
line speed. I guess they had a lot of trouble with AT&T Long Lines back in
|
|
the Old Days, and the IMP has quite a bit of code to monitor line quality.
|
|
Even fairly minor variations in speed will cause it to mark the line as
|
|
"down" and sent a trouble report back to BBN. And no, I'm not making this up!
|
|
|
|
UDP gives us a few advantages. First, it's inherently packet oriented so
|
|
we can simply grab the entire packet from the transmitting IMP's memory, wrap
|
|
a little extra information around it, and ship it off in one datagram. The
|
|
receiving IMP gets the whole packet at once and it can simply BLT it into
|
|
the receiving IMP's memory. No fuss, no muss, no need convert the packet
|
|
into a stream, add word counts, wait for complete packets, etc. And UDP is
|
|
symmetrical - both ends listen and send in the same way. There's no need for
|
|
master sockets, passive (server) and active (client) ends, or any of that.
|
|
|
|
Also UDP has no buffering - the packet goes out on the wire when we send it.
|
|
The data doesn't wait around in some buffer for TCP to decide when it wants
|
|
to let it go. The latency and delay for UDP is much more predictable and
|
|
consistent, at least for local networks. If you're actually sending the
|
|
packets out on the big, wide, Internet then all bets are off on that.
|
|
|
|
UDP has a few problems that we have to worry about. First, it's not
|
|
guaranteed delivery so just because one IMP sends a packet doesn't mean that
|
|
the other end will ever see it. Surprisingly that's not a problem for us.
|
|
Phone lines have noise and dropouts, and real modems lose packets too. The
|
|
IMP code is completely happy and able to deal with that, and generally we
|
|
don't worry about dropped packets at all.
|
|
|
|
There are other issues with UDP - it doesn't guarantee packet order, so the
|
|
sending IMP might transmit packets 1, 2 and 3 and the receiving IMP will get
|
|
1, 3 then 2. THAT would never happen with a real modem and we have to shield
|
|
the IMP code from any such eventuality. Also, with UDP packets can be
|
|
duplicated so the receiving IMP might see 1, 2, 2, 3 (or even 1, 3, 2, 1!).
|
|
Again, a modem would never do that and we have to prevent it from happening.
|
|
Both cases are easily dealt with by adding a sequence number to the header
|
|
we wrap around the IMP's packet. Out of sequence or duplicate packets can
|
|
be detected and are simply dropped. If necessary, the IMP will deal with
|
|
retransmitting them in its own time.
|
|
|
|
One more thing about UDP - there is no way to tell whether a connection is
|
|
established or not and for that matter there is no "connection" at all
|
|
(that's why it's a "connectionless" protocol, after all!). We simply send
|
|
packets out and there's no way to know whether anybody is hearing them. The
|
|
real IMP modem hardware had no carrier detect or other dataset control
|
|
functions, so it was identical in that respect. An IMP sent messages out the
|
|
modem and, unless it received a message back, it had no way to know whether
|
|
the IMP on the other end was hearing them.
|
|
|
|
|
|
INTERFACE
|
|
|
|
This module provides a simplified UDP socket interface. These functions are
|
|
implemented -
|
|
|
|
udp_create define a connection to the remote IMP
|
|
udp_release release a connection
|
|
udp_send send an IMP message to the other end
|
|
udp_receive receive (w/o blocking!) a message if available
|
|
|
|
Note that each connection is assigned a unique "handle", a small integer,
|
|
which is used as an index into our internal connection data table. There
|
|
is a limit on the maximum number of connections available, as set my the
|
|
MAXLINKS parameter. Also, notice that all links are intrinsically full
|
|
duplex and bidirectional - data can be sent and received in both directions
|
|
independently. Real modems and host cards were exactly the same.
|
|
|
|
*/
|
|
#ifdef VM_IMPTIP
|
|
#include "sim_defs.h" // simh machine independent definitions
|
|
#include "sim_tmxr.h" // The MUX layer exposes packet send and receive semantics
|
|
#include "h316_defs.h" // H316 emulator definitions
|
|
#include "h316_imp.h" // ARPAnet IMP/TIP definitions
|
|
|
|
// Local constants ...
|
|
#define MAXLINKS 10 // maximum number of simultaneous connections
|
|
// This constant determines the longest possible IMP data payload that can be
|
|
// sent. Most IMP messages are trivially small - 68 words or so - but, when one
|
|
// IMP asks for a reload the neighbor IMP sends the entire memory image in a
|
|
// single message! That message is about 14K words long.
|
|
// The next thing you should worry about is whether the underlying IP network
|
|
// can actually send a UDP packet of this size. It turns out that there's no
|
|
// simple answer to that - it'll be fragmented for sure, but as long as all
|
|
// the fragments arrive intact then the destination should reassemble them.
|
|
#define MAXDATA 16384 // longest possible IMP packet (in H316 words)
|
|
|
|
// UDP connection data structure ...
|
|
// One of these blocks is allocated for every simulated modem link.
|
|
struct _UDP_LINK {
|
|
t_bool used; // TRUE if this UDP_LINK is in use
|
|
char rhostport[64]; // Remote host:port
|
|
char lport[64]; // Local port
|
|
uint32 rxsequence; // next message sequence number for receive
|
|
uint32 txsequence; // next message sequence number for transmit
|
|
DEVICE *dptr; // Device associated with link
|
|
};
|
|
typedef struct _UDP_LINK UDP_LINK;
|
|
|
|
// This magic number is stored at the beginning of every UDP message and is
|
|
// checked on receive. It's hardly foolproof, but its a simple attempt to
|
|
// guard against other applications dumping unsolicited UDP messages into our
|
|
// receiver socket...
|
|
#define MAGIC ((uint32) (((((('H' << 8) | '3') << 8) | '1') << 8) | '6'))
|
|
|
|
// UDP wrapper data structure ...
|
|
// This is the UDP packet which is actually transmitted or received. It
|
|
// contains the actual IMP packet, plus whatever additional information we
|
|
// need to keep track of things. NOTE THAT ALL DATA IN THIS PACKET, INCLUDING
|
|
// THE H316 MEMORY WORDS, ARE SENT AND RECEIVED WITH NETWORK BYTE ORDER!
|
|
struct _UDP_PACKET {
|
|
uint32 magic; // UDP "magic number" (see above)
|
|
uint32 sequence; // UDP packet sequence number
|
|
uint16 count; // number of H316 words to follow
|
|
uint16 data[MAXDATA]; // and the actual H316 data words/IMP packet
|
|
};
|
|
typedef struct _UDP_PACKET UDP_PACKET;
|
|
#define UDP_HEADER_LEN (2*sizeof(uint32) + sizeof(uint16))
|
|
|
|
// Locals ...
|
|
UDP_LINK udp_links[MAXLINKS] = { {0} }; // data for every active connection
|
|
TMLN udp_lines[MAXLINKS] = { {0} }; // line descriptors
|
|
TMXR udp_tmxr = { MAXLINKS, NULL, 0, udp_lines};// datagram mux
|
|
|
|
int32 udp_find_free_link (void)
|
|
{
|
|
// Find a free UDP_LINK block, initialize it and return its index. If none
|
|
// are free, then return -1 ...
|
|
int32 i;
|
|
for (i = 0; i < MAXLINKS; ++i) {
|
|
if (udp_links[i].used == 0) {
|
|
memset(&udp_links[i], 0, sizeof(UDP_LINK));
|
|
return i;
|
|
}
|
|
}
|
|
return NOLINK;
|
|
}
|
|
|
|
t_stat udp_parse_remote (int32 link, char *premote)
|
|
{
|
|
// This routine will parse a remote address string in any of these forms -
|
|
//
|
|
// llll:w.x.y.z:rrrr
|
|
// llll:name.domain.com:rrrr
|
|
// llll::rrrr
|
|
// w.x.y.z:rrrr
|
|
// name.domain.com:rrrr
|
|
//
|
|
// In all examples, "llll" is the local port number that we use for listening,
|
|
// and "rrrr" is the remote port number that we use for transmitting. The
|
|
// local port is optional and may be omitted, in which case it defaults to the
|
|
// same as the remote port. This works fine if the other IMP is actually on
|
|
// a different host, but don't try that with localhost - you'll be talking to
|
|
// yourself!! In both cases, "w.x.y.z" is a dotted IP for the remote machine
|
|
// and "name.domain.com" is its name (which will be looked up to get the IP).
|
|
// If the host name/IP is omitted then it defaults to "localhost".
|
|
char *end; int32 lport, rport; t_stat ret;
|
|
char host[64], port[16];
|
|
if (*premote == '\0') return SCPE_2FARG;
|
|
memset (udp_links[link].lport, 0, sizeof(udp_links[link].lport));
|
|
memset (udp_links[link].rhostport, 0, sizeof(udp_links[link].rhostport));
|
|
// Handle the llll::rrrr case first
|
|
if (2 == sscanf (premote, "%d::%d", &lport, &rport)) {
|
|
if ((lport < 1) || (lport >65535) || (rport < 1) || (rport >65535)) return SCPE_ARG;
|
|
sprintf (udp_links[link].lport, "%d", lport);
|
|
sprintf (udp_links[link].rhostport, "localhost:%d", rport);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
// Look for the local port number and save it away.
|
|
lport = strtoul(premote, &end, 10);
|
|
if ((*end == ':') && (lport > 0)) {
|
|
sprintf (udp_links[link].lport, "%d", lport);
|
|
premote = end+1;
|
|
}
|
|
|
|
ret = sim_parse_addr (premote, host, sizeof(host), "localhost", port, sizeof(port), NULL, NULL);
|
|
if (ret != SCPE_OK) return SCPE_ARG;
|
|
sprintf (udp_links[link].rhostport, "%s:%s", host, port);
|
|
if (udp_links[link].lport[0] == '\0')
|
|
strcpy (udp_links[link].lport, port);
|
|
|
|
if ((strcmp (udp_links[link].lport, port) == 0) &&
|
|
(strcmp ("localhost", host) == 0))
|
|
fprintf(stderr,"WARNING - use different transmit and receive ports!\n");
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat udp_error (int32 link, const char *msg)
|
|
{
|
|
// This routine is called whenever a SOCKET_ERROR is returned for any I/O.
|
|
fprintf(stderr,"UDP%d - %s failed with error %d\n", link, msg, WSAGetLastError());
|
|
return SCPE_IOERR;
|
|
}
|
|
|
|
t_stat udp_create (DEVICE *dptr, char *premote, int32 *pln)
|
|
{
|
|
// Create a logical UDP link to the specified remote system. The "remote"
|
|
// string specifies both the remote host name or IP and a port number. The
|
|
// port number is both the port we send datagrams to, and also the port we
|
|
// listen on for incoming datagrams. UDP doesn't have any real concept of a
|
|
// "connection" of course, and this routine simply creates the necessary
|
|
// sockets in this host. We have no way of knowing whether the remote host is
|
|
// listening or even if it exists.
|
|
//
|
|
// We return SCPE_OK if we're successful and an error code if we aren't. If
|
|
// we are successful, then the ln parameter is assigned the link number,
|
|
// which is a handle used to identify this connection to all future udp_xyz()
|
|
// calls.
|
|
t_stat ret;
|
|
char linkinfo[128];
|
|
int32 link = udp_find_free_link();
|
|
if (link < 0) return SCPE_MEM;
|
|
|
|
// Parse the remote name and set up the ipaddr and port ...
|
|
if ((ret = udp_parse_remote(link, premote)) != SCPE_OK) return ret;
|
|
|
|
// Create the socket connection to the destination ...
|
|
sprintf(linkinfo, "Buffer=%d,Line=%d,%s,UDP,Connect=%s", (int)(sizeof(UDP_PACKET)+sizeof(int32)), link, udp_links[link].lport, udp_links[link].rhostport);
|
|
ret = tmxr_open_master (&udp_tmxr, linkinfo);
|
|
if (ret != SCPE_OK) return ret;
|
|
|
|
// All done - mark the TCP_LINK data as "used" and return the index.
|
|
udp_links[link].used = TRUE; *pln = link;
|
|
udp_lines[link].dptr = udp_links[link].dptr = dptr; // save device
|
|
udp_tmxr.uptr = dptr->units;
|
|
udp_tmxr.last_poll_time = 1; // h316'a use of TMXR doesn't poll periodically for connects
|
|
tmxr_poll_conn (&udp_tmxr); // force connection initialization now
|
|
udp_tmxr.last_poll_time = 1; // h316'a use of TMXR doesn't poll periodically for connects
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - listening on port %s and sending to %s\n", link, udp_links[link].lport, udp_links[link].rhostport);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat udp_release (DEVICE *dptr, int32 link)
|
|
{
|
|
// Close a link that was created by udp_create() and release any resources
|
|
// allocated to it. We always return SCPE_OK unless the link specified is
|
|
// already unused.
|
|
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR;
|
|
if (!udp_links[link].used) return SCPE_IERR;
|
|
if (dptr != udp_links[link].dptr) return SCPE_IERR;
|
|
|
|
tmxr_detach_ln (&udp_lines[link]);
|
|
udp_links[link].used = FALSE;
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - closed\n", link);
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat udp_send (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count)
|
|
{
|
|
// This routine does all the work of sending an IMP data packet. pdata
|
|
// is a pointer (usually into H316 simulated memory) to the IMP packet data,
|
|
// count is the length of the data (in H316 words, not bytes!), and pdest is
|
|
// the destination socket. There are two things worthy of note here - first,
|
|
// notice that the H316 words are sent in network order, so the remote simh
|
|
// doesn't necessarily need to have the same endian-ness as this machine.
|
|
// Second, notice that transmitting sockets are NOT set to non blocking so
|
|
// this routine might wait, but we assume the wait will never be too long.
|
|
UDP_PACKET pkt; int pktlen; uint16 i; t_stat iret;
|
|
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR;
|
|
if (!udp_links[link].used) return SCPE_IERR;
|
|
if ((pdata == NULL) || (count == 0) || (count > MAXDATA)) return SCPE_IERR;
|
|
if (dptr != udp_links[link].dptr) return SCPE_IERR;
|
|
|
|
// Build the UDP packet, filling in our own header information and copying
|
|
// the H316 words from memory. REMEMBER THAT EVERYTHING IS IN NETWORK ORDER!
|
|
pkt.magic = htonl(MAGIC);
|
|
pkt.sequence = htonl(udp_links[link].txsequence++);
|
|
pkt.count = htons(count);
|
|
for (i = 0; i < count; ++i) pkt.data[i] = htons(*pdata++);
|
|
pktlen = UDP_HEADER_LEN + count*sizeof(uint16);
|
|
|
|
// Send it and we're outta here ...
|
|
iret = tmxr_put_packet_ln (&udp_lines[link], (const uint8 *)&pkt, (size_t)pktlen);
|
|
if (iret != SCPE_OK) return udp_error(link, "tmxr_put_packet_ln()");
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - packet sent (sequence=%d, length=%d)\n", link, ntohl(pkt.sequence), ntohs(pkt.count));
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat udp_set_link_loopback (DEVICE *dptr, int32 link, t_bool enable_loopback)
|
|
{
|
|
// Enable or disable the local (interface) loopback on this link...
|
|
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR;
|
|
if (!udp_links[link].used) return SCPE_IERR;
|
|
if (dptr != udp_links[link].dptr) return SCPE_IERR;
|
|
|
|
return tmxr_set_line_loopback (&udp_lines[link], enable_loopback);
|
|
}
|
|
|
|
int32 udp_receive_packet (int32 link, UDP_PACKET *ppkt)
|
|
{
|
|
// This routine will do the hard part of receiving a UDP packet. If it's
|
|
// successful the packet length, in bytes, is returned. The receiver socket
|
|
// is non-blocking, so if no packet is available then zero will be returned
|
|
// instead. Lastly, if a fatal socket I/O error occurs, -1 is returned.
|
|
//
|
|
// Note that this routine only receives the packet - it doesn't handle any
|
|
// of the checking for valid packets, unexpected packets, duplicate or out of
|
|
// sequence packets. That's strictly the caller's problem!
|
|
size_t pktsiz;
|
|
const uint8 *pbuf;
|
|
t_stat ret;
|
|
|
|
udp_lines[link].rcve = TRUE; // Enable receiver
|
|
tmxr_poll_rx (&udp_tmxr);
|
|
ret = tmxr_get_packet_ln (&udp_lines[link], &pbuf, &pktsiz);
|
|
udp_lines[link].rcve = FALSE; // Disable receiver
|
|
if (ret != SCPE_OK) {
|
|
udp_error(link, "tmxr_get_packet_ln()");
|
|
return NOLINK;
|
|
}
|
|
if (pbuf == NULL) return 0;
|
|
// Got a packet, so copy it to the packet buffer
|
|
memcpy (ppkt, pbuf, pktsiz);
|
|
return pktsiz;
|
|
}
|
|
|
|
int32 udp_receive (DEVICE *dptr, int32 link, uint16 *pdata, uint16 maxbuf)
|
|
{
|
|
// Receive an IMP packet from the virtual modem. pdata is a pointer usually
|
|
// directly into H316 simulated memory) to where the IMP packet data should
|
|
// be stored, and maxbuf is the maximum length of that buffer in H316 words
|
|
// (not bytes!). If a message is successfully received then this routine
|
|
// returns the length, again in H316 words, of the IMP packet. The caller
|
|
// can detect buffer overflows by comparing this result to maxbuf. If no
|
|
// packets are waiting right now then zero is returned, and -1 is returned
|
|
// in the event of any fatal socket I/O error.
|
|
//
|
|
// This routine also handles checking for unsolicited messages and duplicate
|
|
// or out of sequence messages. All of these are unceremoniously discarded.
|
|
//
|
|
// One final note - it's explicitly allowed for pdata to be null and/or
|
|
// maxbuf to be zero. In either case the received package is discarded, but
|
|
// the actual length of the discarded package is still returned.
|
|
UDP_PACKET pkt; int32 pktlen, explen, implen, i; uint32 magic, pktseq;
|
|
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR;
|
|
if (!udp_links[link].used) return SCPE_IERR;
|
|
if (dptr != udp_links[link].dptr) return SCPE_IERR;
|
|
|
|
while ((pktlen = udp_receive_packet(link, &pkt)) > 0) {
|
|
// First do some header checks for a valid UDP packet ...
|
|
if (((size_t)pktlen) < UDP_HEADER_LEN) {
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet w/o header (length=%d)\n", link, pktlen);
|
|
continue;
|
|
}
|
|
magic = ntohl(pkt.magic);
|
|
if (magic != MAGIC) {
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet w/bad magic number (magic=%08x)\n", link, magic);
|
|
continue;
|
|
}
|
|
implen = ntohs(pkt.count);
|
|
explen = UDP_HEADER_LEN + implen*sizeof(uint16);
|
|
if (explen != pktlen) {
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet length wrong (expected=%d received=%d)\n", link, explen, pktlen);
|
|
continue;
|
|
}
|
|
|
|
// Now the hard part = check the sequence number. The rxsequence value is
|
|
// the number of the next packet we expect to receive - that's the number
|
|
// this packet should have. If this packet's sequence is less than that,
|
|
// then this packet is out of order or a duplicate and we discard it. If
|
|
// this packet is greater than that, then we must have missed one or two
|
|
// packets. In that case we MUST update rxsequence to match this one;
|
|
// otherwise the two ends could never resynchronize after a lost packet.
|
|
//
|
|
// And there's one final complication to worry about - if the simh on the
|
|
// other end is restarted for some reason, then his sequence numbers will
|
|
// reset to zero. In that case we'll never recover synchronization without
|
|
// special efforts. The hack is to check for a packet sequence number of
|
|
// zero and, if we find it, force synchronization. This improves the
|
|
// situation, but I freely admit that it's possible to think of a number of
|
|
// cases where this also fails. The only absolute solution is to implement
|
|
// a more complicated system with non-IMP control messages exchanged between
|
|
// the modem emulation on both ends. That'd be nice, but I'll leave it as
|
|
// an exercise for later.
|
|
pktseq = ntohl(pkt.sequence);
|
|
if ((pktseq == 0) && (udp_links[link].rxsequence != 0)) {
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - remote modem restarted\n", link);
|
|
} else if (pktseq < udp_links[link].rxsequence) {
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet out of sequence 1 (expected=%d received=%d\n", link, udp_links[link].rxsequence, pktseq);
|
|
continue; // discard this packet!
|
|
}
|
|
else if (pktseq != udp_links[link].rxsequence) {
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet out of sequence 2 (expected=%d received=%d\n", link, udp_links[link].rxsequence, pktseq);
|
|
}
|
|
udp_links[link].rxsequence = pktseq+1;
|
|
|
|
// It's a valid packet - if there's no buffer then just discard it.
|
|
if ((pdata == NULL) || (maxbuf == 0)) {
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet discarded (no buffer available)\n", link);
|
|
return implen;
|
|
}
|
|
|
|
// Copy the data to the H316 memory and we're done!
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - packet received (sequence=%d, length=%d)\n", link, pktseq, pktlen);
|
|
for (i = 0; i < (implen < maxbuf ? implen : maxbuf); ++i)
|
|
*pdata++ = ntohs(pkt.data[i]);
|
|
return implen;
|
|
}
|
|
|
|
// Here if pktlen <= 0 ...
|
|
return pktlen;
|
|
}
|
|
|
|
#endif // ifdef VM_IMPTIP
|