Table of Contents
Introduction
This page is large enough to give some browsers problems. In particular,
HotJava (1.0.1/JRE 1.1.3, 1997.06.28) can't handle it; Netscape 3.01 can handle
it. Last time I checked, Netscape turns this HTML page into about eighty pages
of PostScript.
This page last modified on 30 March 1998.
Two NETBLT Clients
Exposition begins at the top, with two NETBLT clients, which together implement
a simple file transfer application.
send_file() sends the file named fname to the file-transfer server
associated with UDP port port on the host with IP address host; the
file is sent over the already opened (but not yet connected) NETBLT endpoint
nbid.
<sndr.c>= [D->]
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "netblt.h"
static void send_file(netblt_id nbid, u4b host, u2b port, char * fname) {
u1b * bp;
u4b i, f;
struct stat b;
<open the source file>
<establish a NETBLT connection to the server>
<send the file>
close(f);
free((char *) bp);
}
Defines send_file (links are to index).
Open the source file and call fstat() to get its size.
<open the source file>= (<-U)
if ((f = open(fname, O_RDONLY)) == -1) {
perror("open() failed");
exit(1);
}
if (fstat(f, &b) == -1) {
perror("fstat() failed");
exit(1);
}
Connect to the file-transfer server using the file size as the transfer size;
the other NETBLT parameters (e.g., buffer size and burst length) are left at
their default values. If the connection is established, allocate a buffer
based on the negotiated buffer size.
<establish a NETBLT connection to the server>= (<-U)
nbid->nnp.transfer_size = b.st_size;
connect_netblt(nbid, 's', host, port);
if (nbid->r_host == 0) {
fprintf(stderr, "Connect attempt failed.\n");
exit(1);
}
bp = (u1b *) malloc(nbid->nnp.buffer_size);
assert(bp);
Send the file's contents to the file-transer server. The sender doesn't deal
explicitly with buffer numbering or a final buffer containing less then the
negotiated amount of data; NETBLT takes care of all that. To do so however,
NETBLT must assume the sender is sending the file in start-to-end order.
<send the file>= (<-U)
for (i = nbid->buffers_per_transfer; i > 0; i--) {
if (read(f, bp, nbid->nnp.buffer_size) == -1) {
perror("read() failed");
exit(1);
}
send_netblt(nbid, bp);
}
The client of the pair is simple: it opens a NETBLT connection, sends a file
over it, and closes the connection. open_netblt(0, 0) associates any host
IP address and available UDP port with the opened NETBLT endpoint.
<sndr.c>+= [<-D]
void main(int argc, char * argv[]) {
int r_port;
char r_addr[20];
extern u4b inet_addr(const char *);
netblt_id nbid = open_netblt(0, 0);
<process the sndr command line>
send_file(nbid, str2ina(r_addr), r_port, argv[2]);
close_netblt(nbid);
exit(0);
}
The file-transfer client command line indicates the IP address and UDP port
associated with the file-transfer server, as well as the name of the file to
send.
<process the sndr command line>= (<-U)
if (argc != 3) {
fprintf(stderr, "Command format is \"%s host:port fname\".\n", argv[0]);
exit(1);
}
if (sscanf(argv[1], "%[^:]:%d", r_addr, &r_port) != 2) {
fprintf(stderr, "missing host:port spec.\n");
exit(1);
}
<rcvr.c>= [D->]
#include "netblt.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifndef nebtlt_ip_addr
#define netblt_ip_addr "0.0.0.0"
#endif
static void receive_file(netblt_id nbid) {
u1b * bp;
u4b j, ts;
bp = (u1b *) malloc(nbid->nnp.buffer_size);
assert(bp);
ts = nbid->nnp.transfer_size;
for (j = nbid->buffers_per_transfer; j > 0; j--) {
fprintf(stderr, "receiving buffer %d\n", nbid->buffers_per_transfer-j+1);
receive_netblt(nbid, bp);
fwrite(bp, 1, minimum(nbid->nnp.buffer_size, ts), stdout);
ts -= nbid->nnp.buffer_size;
}
free((char *) bp);
}
Defines receive_file (links are to index).
The file-transfer server opens the NETBLT endpoint nbid at the IP address
given by netblt_ip_addr with any available UDP port, prints a message
(suitable for cutting and pasting) about what a client command line looks like,
waits for a connection on the NETBLT endpoint, receives the file over the
connection, closes the connection, and exits.
<rcvr.c>+= [<-D]
void main(void) {
netblt_id nbid;
u4b h;
nbid = open_netblt(str2ina(netblt_ip_addr), 0);
h = skt_addr(nbid->skt, 0);
if (!h) h = host_addr(0);
fprintf(stderr, "./sndr %s:%d\n", ina2str(h), port(nbid->skt));
connect_netblt(nbid, 'r', 0, 0);
receive_file(nbid);
close_netblt(nbid);
exit(0);
}
The NETBLT Implementation
The NETBLT Message
The kinds of NETBLT messages shipped around.
<NETBLT message definitions>= (U->) [D->]
# define open_msg_type 0
# define response_msg_type 1
# define keepalive_msg_type 2
# define quit_msg_type 3
# define quitack_msg_type 4
# define abort_msg_type 5
# define data_msg_type 6
# define ldata_msg_type 7
# define nullack_msg_type 8
# define control_msg_type 9
# define refused_msg_type 10
# define done_msg_type 11
# define msg_type_count 12
The header found on the front of every NETBLT message; it's also the
keepalive, quitack, and done message. The more interesting fields of the
common header are:
-
checksum. The usual 16-bit one's-compliment checksum over the
message header.
-
type. The msg_type values defined previously.
-
length. In bytes; must be evenly divisible by four.
-
local_port. At the message sender; the remote port at the
message receiver.
-
remote_port. At the message sender; the local port at the
message receiver.
<NETBLT message definitions>+= (U->) [<-D->]
typedef struct {
u2b checksum;
u1b version;
u1b type;
u2b length;
u2b local_port;
u2b remote_port;
u2b padding;
} common_msg_hdr;
Defines common_msg_hdr (links are to index).
Some useful definitions related to the common message header. The maximum
packet size is a little smaller than the Ethernet MTU.
<NETBLT message definitions>+= (U->) [<-D->]
typedef common_msg_hdr msg;
typedef common_msg_hdr * msgp;
# define msg_length(_mp) (((msgp) (_mp))->length)
# define msg_type(_mp) (((msgp) (_mp))->type)
# define is_msg_type(_mp, _t) (msg_type(_mp) == _t ## _msg_type)
# define max_packet_size 1500
Defines is_msg_type, max_packet_size, msg, msg_length, msgp, msg_type (links are to index).
Message manipulation routines.
<NETBLT message definitions>+= (U->) [<-D->]
extern msgp
make_msg(void);
extern void
free_msg(msgp),
reset_msg(msgp),
send_msg(netblt_id, msgp),
send_pkt(netblt_id, u4b, u2b, u1b *, u2b),
set_checksum(msgp, bool),
put_msg_data(msgp, const void *, u2b);
extern bool
checksum_ok(msgp, bool);
An open or response message.
<NETBLT message definitions>+= (U->) [<-D->]
typedef struct {
common_msg_hdr cmh;
u4b connection_id;
u4b buffer_size;
u4b transfer_size;
u2b packet_size;
u2b burst_size;
millisecs burst_rate;
seconds death_timer_interval;
u2b reserved;
u2b max_concurrent_buffers;
} or_msg, * or_msgp;
#define set_data_checksum_bit(_mp) (((or_msgp) (_mp))->reserved |= 2)
#define clear_data_checksum_bit(_mp) (((or_msgp) (_mp))->reserved &= ~2)
#define get_data_checksum_bit(_mp) (((or_msgp) (_mp))->reserved & 2)
Defines clear_data_checksum_bit, get_data_checksum_bit, or_msg, or_msgp, set_data_checksum_bit (links are to index).
An abort, control, quit, or refused message. This message is different from
the common message header because it can take user data, while the common
message header (in the form of the keepalive, quitack, or done message) does
not.
<NETBLT message definitions>+= (U->) [<-D->]
typedef struct {
common_msg_hdr cmh;
} acqr_msg, * acqr_msgp;
Defines acqr_msg, acqr_msgp (links are to index).
An data or ldata message.
<NETBLT message definitions>+= (U->) [<-D->]
typedef struct {
common_msg_hdr cmh;
u4b buffernumber;
u2b max_sequence;
u2b packet_number;
u2b data_checksum;
u2b reserved;
} dl_msg, * dl_msgp;
#define set_ldata_bit(_mp) (((dl_msgp) (_mp))->reserved |= 1)
#define get_ldata_bit(_mp) (((dl_msgp) (_mp))->reserved & 1)
Defines dl_msg, dl_msgp, get_ldata_bit, set_ldata_bit (links are to index).
A nullack message.
<NETBLT message definitions>+= (U->) [<-D->]
typedef struct {
common_msg_hdr cmh;
u2b max_sequence;
u2b burst_size;
millisecs burst_rate;
u2b padding;
} n_msg, * n_msgp;
Defines n_msg, n_msgp (links are to index).
message_info is an keeps track of useful information about NETBLT messages;
useful information includes a message's name, the number of bytes in a
message's header, and whether or not a message can take user data.
messag_inf is index by message type.
<NETBLT message definitions>+= (U->) [<-D->]
static const struct {
char * name;
u1b header_size;
bool takes_data;
} message_info[] = {
{"open" , sizeof(or_msg) , true},
{"response" , sizeof(or_msg) , true},
{"keepalive", sizeof(msg) , false},
{"quit" , sizeof(acqr_msg), true},
{"quitack" , sizeof(msg) , true},
{"abort" , sizeof(acqr_msg), true},
{"data" , sizeof(dl_msg) , true},
{"ldata" , sizeof(dl_msg) , true},
{"nullack" , sizeof(n_msg) , false},
{"control" , sizeof(acqr_msg), true},
{"refused" , sizeof(acqr_msg), true},
{"done" , sizeof(msg) , false}
};
#define msg_header_size(_mp) (message_info[msg_type(_mp)].header_size)
#define msg_takes_data(_mp) (message_info[msg_type(_mp)].takes_data)
#define msg_name(_mp) (message_info[msg_type(_mp)].name)
#define msg_data(_mp) (((u1b *) (_mp)) + msg_header_size(_mp))
Defines message_info, msg_data, msg_header_size, msg_name, msg_takes_data (links are to index).
Control submessages are stored within control messages. These are the types of
control submessages.
<NETBLT message definitions>+= (U->) [<-D->]
# define go_submessage_type 0
# define ok_submessage_type 1
# define resend_submessage_type 2
The common header for control submessages, and the go control submessage.
<NETBLT message definitions>+= (U->) [<-D->]
typedef struct {
u1b type;
u1b padding;
u2b sequence_number;
buffer_number buffernumber;
} common_submessage_header,
go_msg, * go_msgp;
typedef common_submessage_header * submsgp;
#define submsg_type(_smp) (((submsgp) (_smp))->type)
#define is_submsg_type(_mp, _t) (submsg_type(_mp) == _t ## _submessage_type)
#define sm_buffer_number(_mp) (((submsgp) (_mp))->buffernumber)
Defines common_submessage_header, go_msg, go_msgp, is_submsg_type, sm_buffer_number, submsgp, submsg_type (links are to index).
The ok control submessage.
<NETBLT message definitions>+= (U->) [<-D->]
typedef struct {
common_submessage_header csh;
u2b burst_size;
millisecs burst_rate;
millisecs control_timer;
u2b padding;
} ok_msg, * ok_msgp;
Defines ok_msg, ok_msgp (links are to index).
The resend control submessage.
<NETBLT message definitions>+= (U->) [<-D->]
typedef struct {
common_submessage_header csh;
u2b pkt_cnt;
u2b padding;
} resend_msg, * resend_msgp;
#define resend_pkt_count(_m) (((resend_msgp) (_m))->pkt_cnt)
#define resend_pkt_start(_m) ((u2b *) (((u1b *) (_m)) + sizeof(resend_msg)))
Defines resend_msg, resend_msgp, resend_pkt_count, resend_pkt_start (links are to index).
Some routines associated with control submessages.
<NETBLT message definitions>+= (U->) [<-D]
extern void
control_msg_iter(acqr_msgp cmp, bool (* f)(submsgp, void *), void *),
dump_submessage(FILE *, submsgp),
dump_message(FILE *, msgp);
extern u2b
smsg_length(submsgp);
extern bool
has_control_msg(msgp, int, buffer_number);
The dot h file.
<msg.h>=
#ifndef _msg_h_defined_
#define _msg_h_defined_
#include "defs.h"
<NETBLT message definitions>
#endif
Defines "msg.h" (links are to index).
make_msg() returns a pointer to a new NETBLT message, which was created
by getting one off either the free list or the heap.
<message code>= (U->) [D->]
msgp make_msg(void) {
msgp mp;
if (m_free_list) {
mp = m_free_list;
m_free_list = *((msgp *) mp);
}
else {
if (!kl) kl = init_kl();
mp = (msgp) malloc(max_packet_size);
assert(mp);
}
reset_msg(mp);
add_kl(kl, mp);
return mp;
}
Defines make_msg (links are to index).
free_msg() frees message pointed to by mp by hanging it on the free
list.
<message code>+= (U->) [<-D->]
void free_msg(msgp mp) {
drop_kl(kl, mp);
*((msgp *) mp) = m_free_list;
m_free_list = mp;
}
Defines free_msg (links are to index).
reset_msg() sets the message pointed to by mp to an initial state.
<message code>+= (U->) [<-D->]
void reset_msg(msgp mp) {
msg_length(mp) = 0;
mp->version = netblt_version;
}
Defines reset_msg (links are to index).
send_msg() sends the message pointed to by mp over the NETBLT endpoint
nbid.
<message code>+= (U->) [<-D->]
void send_msg(netblt_id nbid, msgp mp) {
mp->version = netblt_version;
mp->remote_port = nbid->r_port;
mp->local_port = port(nbid->skt);
<set the message length>
set_checksum(mp, nbid->nnp.checksum_data);
send_pkt(nbid, nbid->r_host, nbid->r_port, (u1b *) mp, msg_length(mp));
}
Defines send_msg (links are to index).
If the message has non-zero length, assume it's been set elsewhere (in
particular, assume it's been set by the routine that stores user data in a
message). Otherwise, there's no user data and the message size is just the
header size.
<set the message length>= (U->)
if (msg_length(mp) == 0)
msg_length(mp) = msg_header_size(mp);
if (msg_length(mp) & 3) {
fprintf(stderr, "%s msg has length = %d.\n", msg_name(mp), msg_length(mp));
exit(1);
}
Send the message pkt of size pkt_size bytes over the NETBLT endpoint
nbid to the corresponding NETBLT endpoint on host host with UDP port
port.
<message code>+= (U->) [<-D]
void send_pkt(netblt_id nbid, u4b host, u2b port, u1b * pkt, u2b pkt_size) {
int e;
struct sockaddr_in sa;
assert(pkt_size > 0);
bzero((char *) &sa, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(host);
sa.sin_port = htons(port);
debugp((stderr, "Send %s to %s:%d.\n", msg_name(name), ina2str(host), port));
e = sendto(nbid->skt, (char *) pkt, pkt_size, 0,
(struct sockaddr *) &sa, sizeof(sa));
check(e < 0, "sendto()");
}
Defines send_pkt (links are to index).
The checksum is the usual one's-compliment arithmatic over 16-bit words.
<message checksum code>= (U->) [D->]
static u2b checksum(u2b * bp, u2b size) {
u2b cs = 0;
u2b i;
for (i = 0; i < size; i++) cs ^= *bp++;
return ~cs;
}
Defines checksum (links are to index).
Specialize checksum() to compute message header and message data checksums.
<message checksum code>+= (U->) [<-D->]
#define hdr_checksum(_mp) \
checksum((u2b *) (_mp), msg_header_size(_mp)/2)
#define data_checksum(_mp) \
checksum(((u2b *) (_mp)) + msg_header_size(_mp)/2, \
(msg_length(_mp) - msg_header_size(_mp)/2))
Defines data_checksum, hdr_checksum (links are to index).
Given mp, a pointer to a message, compute the message's header checksum.
If data_cs is true and the message is a data or ldata message, also compute
the data checksum.
<message checksum code>+= (U->) [<-D->]
void set_checksum(msgp mp, bool data_cs) {
if (is_msg_type(mp, data) || is_msg_type(mp, ldata)){
((dl_msgp) mp)->data_checksum = 0;
if (data_cs) ((dl_msgp) mp)->data_checksum = data_checksum(mp);
}
mp->checksum = 0;
mp->checksum = hdr_checksum(mp);
}
Defines set_checksum (links are to index).
Given mp, a pointer to a message, return true if and only if the message's
header checksum is correct. If data_cs is true and the message is a data
or ldata message, return true if and only if both the header and data checksums
are correct.
<message checksum code>+= (U->) [<-D]
bool checksum_ok(msgp mp, bool data_cs) {
u2b cs = mp->checksum;
mp->checksum = 0;
if (cs != hdr_checksum(mp)) {
fprintf(stderr, "checksum failure: %x != %x.\n", cs , hdr_checksum(mp));
return false;
}
if ((is_msg_type(mp, data) || is_msg_type(mp, ldata)) && data_cs)
if (((dl_msgp) mp)->data_checksum != data_checksum(mp)) return false;
return true;
}
Defines checksum_ok (links are to index).
Given mp, a pointer to a message, and data, a pointer to data_size
bytes of data, put_msg_data() stores the data in the message, setting the
messages length field.
<miscellaneous message code>= (U->) [D->]
void put_msg_data(msgp mp, const void * data, u2b data_size) {
char * cp;
assert(msg_takes_data(mp));
assert(msg_length(mp) == 0);
assert(data_size > 0);
cp = (char *) msg_data(mp);
bzero(cp + ((data_size - 1) & 0xfffc), 4);
byte_copy((char *) cp, (const char *) data, data_size);
msg_length(mp) =
msg_header_size(mp) + data_size + ((4 - (data_size & 0x03)) & 0x03);
}
Defines put_msg_data (links are to index).
Given smp, a pointer to a control sub-message, dump_submessage() writes
a description of smp to the file out.
<miscellaneous message code>+= (U->) [<-D->]
void dump_submessage(FILE * out, submsgp smp) {
fprintf(out, "type = %d (%s); sequence number = %d; buffer number = %ld\n",
submsg_type(smp),
(!submsg_type(smp) ? "go" :
(submsg_type(smp) == 1 ? "ok" : "resend")),
smp->sequence_number, sm_buffer_number(smp));
switch (submsg_type(smp)) {
case go_submessage_type:
break;
case ok_submessage_type: {
ok_msgp omp = (ok_msgp) smp;
fprintf(out, "burst rate = %d; burst size = %d; control timer = %d\n",
omp->burst_rate, omp->burst_size, omp->control_timer);
break;
}
case resend_submessage_type: {
u2b * pnop;
u1b i;
resend_msgp rmp = (resend_msgp) smp;
fprintf(out, "missing packet count = %d\nmissing packets: ",
rmp->pkt_cnt);
pnop = resend_pkt_start(rmp);
for (i = 0; i < rmp->pkt_cnt; i++) fprintf(out, " %d", *pnop++);
fprintf(out, "\n");
break;
}
default:
fprintf(stderr, "Unrecognized sub-message %d in dump_submessage().\n",
submsg_type(smp));
exit(1);
}
}
Defines dump_submessage (links are to index).
Given mp, a pointer to a message, dump_message() writes a description
of mp to the file out.
<miscellaneous message code>+= (U->) [<-D->]
void dump_message(FILE * out, msgp mp) {
fprintf(out, "type = %d (%s); version = %d; length = %d\n",
msg_type(mp), msg_name(mp), mp->version, msg_length(mp));
fprintf(out, "local port = %d; remote port = %d\n",
mp->local_port, mp->remote_port);
}
Defines dump_message (links are to index).
Given mp, a pointer to a control sub-message, smsg_length() returns the
size of *mp in bytes.
<miscellaneous message code>+= (U->) [<-D->]
u2b smsg_length(submsgp mp) {
switch (submsg_type(mp)) {
case go_submessage_type: return sizeof(go_msg);
case ok_submessage_type: return sizeof(ok_msg);
case resend_submessage_type:
return sizeof(resend_msg) +
2*(((resend_msgp) mp)->pkt_cnt + (((resend_msgp) mp)->pkt_cnt & 1));
default:
assert(!"unknown sub-message type in smsg_length()");
}
return 0;
}
Defines smsg_length (links are to index).
has_control_msg() returns true iff the message pointed to by mp
contains a sub-message of type cmt for buffer bno.
<miscellaneous message code>+= (U->) [<-D->]
bool has_control_msg(msgp mp, int cmt, buffer_number bno) {
u1b * p;
const u1b * end = ((u1b *) mp) + msg_length(mp);
if (!is_msg_type(mp, control)) return false;
for (p = msg_data(mp); p < end; p += smsg_length((submsgp) p))
if ((submsg_type(p) == cmt) && (sm_buffer_number(p) == bno)) return true;
return false;
}
Defines has_control_msg (links are to index).
control_msg_iter() runs through the control message pointed to by cmp
and calls f for each control sub-message found, passing in a pointer to the
control sub-message and the otherwise uninterpreted (by control_msg_iter())
argument a.
<miscellaneous message code>+= (U->) [<-D]
void control_msg_iter(acqr_msgp cmp, bool (* f)(submsgp, void *), void * a) {
u1b * p = msg_data(cmp);
const u1b * end = p + msg_length(cmp);
while ((p < end) && (f)((submsgp) p, a)) p += smsg_length((submsgp) p);
}
Defines control_msg_iter (links are to index).
<msg.c>=
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "msg.h"
#include "keep-list.h"
#include "netblt.h"
/* Sundries. */
static msgp m_free_list = 0;
static keep_list kl;
<message code>
<message checksum code>
<miscellaneous message code>
The NETBLT Endpoint
Each end of a NETBLT connection allocates an endpoint to represent its
side of the connection. A NETBLT endpoint is implemented by the Netblt_id
data structure; all the state needed to manage the local end of a NETBLT
connection is stored in the Netblt_id data structure. The fields of
Netblt_id are
-
buffers_per_transfer. The total number of buffers that will be
used in the transfer.
-
current_bno. The number of the buffer being sent or expected
next; in the case of multi-buffer transfers, it's the smallest buffer number in
the sequence.
-
last_payload_size. The amount of data stored in the last
packet sent in the transfer.
-
next_seq_no. On the sending side of the connection, it's the
next sequence number to send; on the receiving side of the connection, it's the
next sequence number to expect.
-
nnp. The paramater values negotiated by both ends of the
connection.
-
nullack_mhid, death_mhid, heartbeat_mhid. The ids for
the vita that acknowledge null messages, check for death time-outs, and issue
keepalive messages, respectively.
-
packets_per_buffer. The number of packets needed to transfer a
full buffer.
-
payload_size. The amount of data transfered in a full packet
(this differs from the packet size due to header overhead).
-
r_host. The IP address of the other (remote) end of the
connection.
-
r_port. The UDP port number of the other (remote) end of the
connection.
-
skt. The UDP socket used to send and receive data.
With the exception of nnp, a NETBLT client should treat these fields as
read only.
<The NETBLT endpoint data structure>= (U->)
typedef struct Netblt_id {
int skt;
u4b r_host;
u2b r_port;
netblt_negotiated_parameters nnp;
u2b buffers_per_transfer;
u2b packets_per_buffer;
u2b payload_size;
u2b last_payload_size;
buffer_number current_bno;
sequence_number next_seq_no;
mhandler_id nullack_mhid, death_mhid, heartbeat_mhid;
} Netblt_id;
Defines Netblt_id (links are to index).
The NETBLT protocol lets both sides of a connection negotiate values for
various parameters affecting the data transfer between the sides. The
parameters available for negotiation are
-
buffer_size. The buffer size in bytes.
-
burst_interval. The length of a packet-sending burst, in
milliseconds.
-
checksum_data. Indicate if data being send should be
checksummed.
-
death_timer_interval. How long the other side is going to wait
without getting a message before it declares this side to be dead. Not a
negotiated value; this side uses this value to set its own heartbeat timer to
insure the other side won't assume an untimley death.
-
max_concurrent buffers. The number of buffers being sent
during a single transfer.
-
packet_size. The packet size in bytes.
-
packets_per_burst. The number of packets sent in a
packet-sending burst.
-
transfer_size. The transfer size in bytes. It's not really
negotiable, the receiver can either accept or reject.
These fields are initialized to default values, which may be changed by the
NETBLT client before instigating a connection; once the connection is made,
however, changing these values has no affect on the data transfer.
<the NETBLT negotiated parameters>= (U->)
typedef struct {
u4b transfer_size;
u4b buffer_size;
u2b packet_size;
u2b packets_per_burst;
millisecs burst_interval;
u2b death_timer_interval;
bool checksum_data;
u2b max_concurrent_buffers;
} netblt_negotiated_parameters, * netblt_negotiated_parametersp;
Defines netblt_negotiated_parameters, netblt_negotiated_parametersp (links are to index).
open_netblt() creates a NETBLT endpoint; close_netblt() deletes a
NETBLT endpoint.
<netblt.h>=
#include "m-handler.h"
#ifndef _netblt_h_defined_
#define _netblt_h_defined_
#define netblt_version 1
<the NETBLT negotiated parameters>
<The NETBLT endpoint data structure>
extern netblt_id open_netblt(u4b, u2b);
extern void close_netblt(netblt_id);
#include "connect.h"
#include "sender.h"
#include "receiver.h"
#endif
Defines "netblt.h" (links are to index).
Creating a NETBLT endpoint assigns a UDP port to it and initializes the
internal parameters.
<netblt.c>= [D->]
#include <sys/types.h>
#include <sys/socket.h>
#include "netblt.h"
#include "msg.h"
#include "scheduler.h"
#include "system.h"
netblt_id open_netblt(u4b host, u2b port) {
netblt_id nbid = (netblt_id) malloc(sizeof(Netblt_id));
extern int socket(int, int, int);
assert(nbid);
nbid->skt = socket(PF_INET, SOCK_DGRAM, 0);
check(nbid->skt < 0, "socket()");
bind_skt(nbid->skt, host, port);
nbid->r_host = nbid->r_port = 0;
<set default values for negotiated parameters>
nbid->nullack_mhid = nbid->death_mhid = nbid->heartbeat_mhid = 0;
nbid->current_bno = 0;
nbid->next_seq_no = 1;
init_scheduler(nbid->skt);
return nbid;
}
Defines open_netblt (links are to index).
The only value the NETBLT client must set is transfer_size. The burst
values give a transfer rate of
1400 byt/pkt*((5 pkt/bst)/(45 msec/bst))*(1000 msec/sec) = 1.2 mbit/sec
<set default values for negotiated parameters>= (<-U)
nbid->nnp.transfer_size = 0;
nbid->nnp.buffer_size = 100000;
nbid->nnp.packet_size = 1400; /* must be zero mod 4 */
nbid->nnp.packets_per_burst = 5;
nbid->nnp.burst_interval = 45;
nbid->nnp.death_timer_interval = 30;
nbid->nnp.checksum_data = false;
nbid->nnp.max_concurrent_buffers = 1;
To delete a NETBLT endpoint, cancel the null-ack message handler if it exists,
and close the UDP socket associated with the endpoint.
<netblt.c>+= [<-D->]
void close_netblt(netblt_id nbid) {
if (nbid->nullack_mhid) cancel_mhandler(nbid->nullack_mhid);
term_scheduler();
check(close(nbid->skt), "close()");
free((char *) nbid);
}
Defines close_netblt (links are to index).
If all packets in a buffer have been received, the receiving end of a NETBLT
connction will respond with a null-ack message to update the sending side's
sequence numbers. The null-ack message will also contain the new values for
burst-interval length and packets per burst computed by the receiving side
during the last buffer transfer.
The sending-side nullack_handler() deals with null-ack messages from the
receiver.
<netblt.c>+= [<-D]
void nullack_handler(void * ap, void * iap) {
netblt_id nbid = (netblt_id) ap;
n_msgp nmp = (n_msgp) (((msg_bundlep) iap)->mp);
nbid->nnp.packets_per_burst =
minimum(nbid->nnp.packets_per_burst, nmp->burst_size);
nbid->nnp.burst_interval =
minimum(nbid->nnp.burst_interval, nmp->burst_rate);
}
Defines nullack_handler (links are to index).
NETBLT Connection Management
Once an NETBLT endpoint is created, it must be connected to a peer before data
can flow. Each side of a connection uses connect_netblt() to add its
endpoint to the connection.
<connect.h>=
#ifndef _connect_h_defined_
#define _connect_h_defined_
#include "netblt.h"
extern void connect_netblt(netblt_id, char, u4b, u2b);
#endif
Defines "connect.h" (links are to index).
connect_netblt() accepts the unconnected NETBLT endpoint nbid and an
indication if this endpoint is on the 's'ending or 'r'eceiving end. If
nbid is the sending endpoint, host and port are the IP address and
UDP port of the receiving end; if nbid is the receiving endpoint, host
and port are the IP address and UDP port of an acceptable sending end.
The work of making the connections falls to the routines sconnect() and
rconnect(); connect_netblt() itself just initializes some read-only
parameters in the NETBLT endpoint.
<connect_netblt()>= (U->)
void connect_netblt(netblt_id nbid, char what, u4b host, u2b port) {
(what == 'r' ? rconnect : sconnect)(nbid, host, port);
nbid->payload_size = nbid->nnp.packet_size - sizeof(dl_msg);
nbid->buffers_per_transfer =
ceiling(nbid->nnp.transfer_size, nbid->nnp.buffer_size);
nbid->packets_per_buffer =
ceiling(nbid->nnp.buffer_size, nbid->payload_size);
nbid->last_payload_size = nbid->nnp.buffer_size -
((nbid->packets_per_buffer - 1)*(nbid->payload_size));
}
Defines connect_netblt (links are to index).
rconnect() attempts to establish nbid as the receiving-end of a NETBLT
connection; host and port give the IP address and UDP port number of
acceptable peers, or are 0 if any peer is acceptable.
On the receiving side of a connection, rconnect() schedules a handler for
connection requests, and then waits for the handler to terminate. At
termination, rconnect() checks to see if a connection has been established
and, if it has, schedules a handler for control messages back to the sender and
a handler for null acknowledgement messages.
<rconnect()>= (U->)
static void rconnect(netblt_id nbid, u4b host, u2b port) {
mhandler_id mhid;
extern void nullack_handler(void *, void *);
ca.nbid = nbid;
ca.host = host;
ca.port = port;
ca.done = false;
mhid = schedule_mhandler(set_msgtype(0, open), receive_open, &ca);
run_scheduler(rtf);
cancel_mhandler(mhid);
if (nbid->r_host) {
init_ctlsdr(nbid);
nbid->nullack_mhid = schedule_mhandler(set_msgtype(0, nullack),
nullack_handler, nbid);
}
}
Defines rconnect (links are to index).
sconnect() attempts to establish nbid as the sending-end of a NETBLT
connection; the receiving end is supposed to reside at the UDP port prt on
the host with IP address host.
<sconnect()>= (U->)
static void sconnect(netblt_id nbid, u4b host, u2b prt) {
mhandler_id mhid;
const u1b z[1] = {0};
msgp mp;
<sconnect() code>
}
Defines sconnect (links are to index).
First construct an NETBLT open message to send to the other end; the message
sends no user data.
<sconnect() code>= (U->) [D->]
sca.omp = (or_msgp) make_msg();
msg_type(sca.omp) = open_msg_type;
sca.omp->transfer_size = nbid->nnp.transfer_size;
sca.omp->buffer_size = nbid->nnp.buffer_size;
sca.omp->packet_size = nbid->nnp.packet_size;
sca.omp->burst_size = nbid->nnp.packets_per_burst;
sca.omp->burst_rate = nbid->nnp.burst_interval;
sca.omp->max_concurrent_buffers = nbid->nnp.max_concurrent_buffers;
sca.omp->death_timer_interval = nbid->nnp.death_timer_interval;
sca.omp->reserved = 0;
if (nbid->nnp.checksum_data) set_data_checksum_bit(sca.omp);
mp = (msgp) sca.omp;
mp->version = netblt_version;
mp->remote_port = prt;
mp->local_port = port(nbid->skt);
put_msg_data(mp, z, 1);
set_checksum(mp, nbid->nnp.checksum_data);
When the open message is ready, sconnect() schedules two handlers:
receive_response() deals with the response received from the other end of
the connection, and send_open() is responsible for sending the open message
to the other end.
<sconnect() code>+= (U->) [<-D]
sca.retries_left = 10;
sca.response_timeout = 1000;
sca.nbid = nbid;
sca.r_host = host;
sca.r_port = prt;
sca.done = false;
mhid = schedule_mhandler(set_msgtype(set_msgtype(0, response), refused),
receive_response, &sca);
send_open(&sca, 0);
run_scheduler(stf);
cancel_mhandler(mhid);
free_msg((msgp) sca.omp);
The send_open() handler is responsible for sending an open message.
NETBLT allows for time-outs and a fixed number of retries on opens; if
send_open() has retries left, it send the open message and schedules itself
to send it again at the end of the next time-out interval. If there are no
retries left, send_open() terminates itself.
<send_open()>= (U->)
static void send_open(void * ap, void * iap) {
sconnect_argsp scap = (sconnect_argsp) ap;
if (scap->retries_left == 0) {
scap->done = true;
return;
}
send_pkt(scap->nbid, scap->r_host, scap->r_port,
(u1b *) scap->omp, msg_length(scap->omp));
scap->tid = schedule_timer(scap->response_timeout*1000, send_open, ap);
scap->retries_left--;
}
Defines send_open (links are to index).
receive_response() handles the message pointed to by iap using the
state pointed to by iap.
<receive_response()>= (U->)
static void receive_response(void * ap, void * iap) {
sconnect_argsp scap = (sconnect_argsp) ap;
msg_bundlep mbp = (msg_bundlep) iap;
or_msgp rmp = (or_msgp) mbp->mp;
<receive_response() code>
}
Defines receive_response (links are to index).
If the message isn't from the expected peer, ignore it.
<receive_response() code>= (U->) [D->]
if ((mbp->host != scap->r_host) || (mbp->mp->local_port != scap->r_port))
return;
If the message is a response, tuck away the negotiated parameters; otherwise,
the message better be a rejection.
<receive_response() code>+= (U->) [<-D->]
if (is_msg_type(rmp, response)) {
assert(scap->nbid->nnp.transfer_size == rmp->transfer_size);
scap->nbid->nnp.buffer_size = rmp->buffer_size;
scap->nbid->nnp.packet_size = rmp->packet_size;
scap->nbid->nnp.packets_per_burst = rmp->burst_size;
scap->nbid->nnp.burst_interval = rmp->burst_rate;
scap->nbid->nnp.max_concurrent_buffers = rmp->max_concurrent_buffers;
scap->nbid->nnp.death_timer_interval = rmp->death_timer_interval;
scap->nbid->nnp.checksum_data = get_data_checksum_bit(rmp);
scap->nbid->r_host = scap->r_host;
scap->nbid->r_port = scap->r_port;
}
else
assert(is_msg_type(rmp, refused));
In either case, receive_response() cancels itself and indicates it's done.
<receive_response() code>+= (U->) [<-D]
cancel_timer(scap->tid);
scap->done = true;
The receive_open() procedure accepts the connection information pointed to
by ap and the open message pointed to by ip and returns an accept
message to the sender. The parameter negotiation consists of setting each para
meter to the lower of the two acceptable values.
<receive_open()>= (U->)
static void receive_open(void * ap, void * ip) {
connect_argsp cap = (connect_argsp) ap;
msg_bundlep mbp = (msg_bundlep) ip;
or_msgp omp = (or_msgp) mbp->mp;
u2b dti = cap->nbid->nnp.death_timer_interval;
<negotiate transfer parameters>
msg_type(omp) = response_msg_type;
msg_length(omp) = 0;
cap->nbid->r_port = ((msgp) omp)->local_port;
cap->nbid->r_host = mbp->host;
send_msg(cap->nbid, (msgp) omp);
cap->done = true;
}
Defines receive_open (links are to index).
Negotation just takes the smaller of the values expected by this endpoint and
offered by the other endpoint.
<negotiate transfer prameters>=
cap->nbid->nnp.transfer_size = omp->transfer_size;
omp->buffer_size = cap->nbid->nnp.buffer_size =
minimum(cap->nbid->nnp.buffer_size, omp->buffer_size);
omp->packet_size = cap->nbid->nnp.packet_size =
minimum(cap->nbid->nnp.packet_size, omp->packet_size);
omp->burst_size = cap->nbid->nnp.packets_per_burst =
minimum(cap->nbid->nnp.packets_per_burst, omp->burst_size);
omp->burst_rate = cap->nbid->nnp.burst_interval =
minimum(cap->nbid->nnp.burst_interval, omp->burst_rate);
omp->max_concurrent_buffers = cap->nbid->nnp.max_concurrent_buffers =
minimum(cap->nbid->nnp.max_concurrent_buffers,omp->max_concurrent_buffers);
cap->nbid->nnp.death_timer_interval = omp->death_timer_interval;
omp->death_timer_interval = dti;
cap->nbid->nnp.checksum_data =
cap->nbid->nnp.checksum_data || get_data_checksum_bit(omp);
if (cap->nbid->nnp.checksum_data) set_data_checksum_bit(omp);
To operate properly, the connection routines need to know
-
done True if and only if connection management has been
completed (successfully or otherwise).
-
host The local host's IP address.
-
nbid A pointer to the end-point on whose behalf this connection
is being made.
-
port The end-point's associated UDP port.
<connection state>= (U->) [D->]
typedef struct {
netblt_id nbid;
u4b host;
u2b port;
bool done;
} connect_args, * connect_argsp;
Defines connect_args, connect_argsp (links are to index).
The connection manager on the sending side needs to know a bit more than does
the connection manager on the receiving side.
-
done True if and only if connection management has been
completed (successfully or otherwise).
-
nbid A pointer to the NETBLT end-point on whose behalf this
connection is being made.
-
omp A pointer to the open message being sent.
-
r_host The IP address associated with the receiving NETBLT
end-point.
-
r_port The UDP port associated with the receiving NETBLT
end-point.
-
response_timeout The number of milliseconds that should elapse
without receiving a response from the other side before the open message is
retransmitted.
-
retries_left The number of times the open message should be
sent.
-
tid The retransmission timer's id.
<connection state>+= (U->) [<-D]
typedef struct {
or_msgp omp;
u1b retries_left;
millisecs response_timeout;
bool done;
timer_id tid;
u4b r_host;
u2b r_port;
netblt_id nbid;
} sconnect_args, * sconnect_argsp;
Defines sconnect_args, sconnect_argsp (links are to index).
The scheduler terimation functions tell the scheduler when it should return to
the sending end (stf) or receiving end (rtf).
<scheduler termination functions>= (U->)
static bool rtf(void) {
return ca.done;
}
static bool stf(void) {
return sca.done;
}
Defines rtf, stf (links are to index).
<connect.c>=
#include "connect.h"
#include "m-handler.h"
#include "timer.h"
#include "scheduler.h"
#include "control-sender.h"
<connection state>
/* Sundries. */
static connect_args ca;
static sconnect_args sca;
<scheduler termination functions>
<receive_open()>
<receive_response()>
<send_open()>
<rconnect()>
<sconnect()>
<connect_netblt()>
Sending and Receiving
<sender.h>=
#ifndef _sender_h_defined_
#define _sender_h_defined_
#include "netblt.h"
extern void send_netblt(netblt_id, u1b *);
#endif
Defines "sender.h" (links are to index).
The data needed to resend packets are:
-
missing_pkt_cnt: the number of missing packets.
-
missing_pkts: an array of packet numbers; packet p is
missing if and only if missing_pkts[i] = p for some 0 <= i <
missing_pkt_cnt.
<missing packet data>= (U->)
typedef struct {
u2b * missing_pkts;
u2b missing_pkt_cnt;
} resend_data, * resend_datap;
Defines resend_data, resend_datap (links are to index).
<sender.c>= [D->]
#include "sender.h"
#include "m-handler.h"
#include "timer.h"
#include "scheduler.h"
#include "msg.h"
/* What the buffer sending routines need to know. */
<missing packet data>
typedef struct {
u1b * buffer;
buffer_number bno;
u2b packets_remaining;
bool done;
netblt_id nbid;
mhandler_id go_mhid, ok_mhid, resend_mhid, done_mhid;
u2b reserved;
millisecs control_ack_timeout;
bool last_buffer;
} sb_args, * sb_argsp;
/* Sundries. */
static sb_args sba;
static void get_resend(void *, void *), get_ok(void *, void *);
# define has_controlm(_m, _t, _a) \
has_ctl_msg((acqr_msgp) (_m), _t ## _submessage_type, _a)
static bool do_ok_msg(msgp, sb_argsp);
static void get_done(void *, void *);
Defines has_controlm, sb_args, sb_argsp (links are to index).
Given cmp, a pointer to a control message, mt, a sub-message type, and
sbap, a pointer to sending buffer information, had_ctl_msg() returns a
zero if cmp does not contain a proper sub-message of type mt; otherwise
it returns a pointer to the sub-message.
A sub-message is proper if it contains the expected next sequence number
and it refers to the buffer currently being sent. Note this code doesn't
optimize things by accepting sub-messages with sequence numbers ahead of the
expected next sequence number.
<sender.c>+= [<-D->]
static submsgp has_ctl_msg(acqr_msgp cmp, int mt, sb_argsp sbap) {
u1b * p;
const u1b * end = ((u1b *) cmp) + msg_length(cmp);
for (p = msg_data(cmp); p < end; p += smsg_length((submsgp) p)) {
submsgp smp = (submsgp) p;
if ((smp->sequence_number == sbap->nbid->next_seq_no) &&
(sm_buffer_number(smp) == sbap->bno) &&
(smp->type == mt)) {
sbap->nbid->next_seq_no++;
return smp;
}
}
return 0;
}
Defines has_ctl_msg (links are to index).
<sender.c>+= [<-D->]
static void get_done(void * ap, void * iap) {
sb_argsp sbap = (sb_argsp) ap;
msgp mp = (msgp) (((msg_bundlep) iap)->mp);
if (do_ok_msg(mp, sbap)) return;
if (is_msg_type(mp, done)) {
sbap->done = true;
return;
}
assert(!"unexpected message type in get_done");
}
Defines get_done (links are to index).
Given mp, a pointer to a message, and sbap, a pointer to sending-buffer
information, do_ok_msg() returns true if the message pointed to by mp
contains an ok sub-message. In addition, if do_ok_msg() finds a valid ok
sub-message, it will send a null-acknowledgement message back to the receiving
end and, if necessary, schedule a handler for a done message.
<sender.c>+= [<-D->]
static bool do_ok_msg(msgp mp, sb_argsp sbap) {
ok_msgp omp;
n_msg nm;
<do_ok_msg() code>
return true;
}
Defines do_ok_msg (links are to index).
If the message isn't a control message, or the control message doesn't contain
a valid ok sub-message, return false.
<do_ok_msg() code>= (<-U) [D->]
if (!is_msg_type(mp, control)) return false;
omp = (ok_msgp) has_controlm(mp, ok, sbap);
if (!omp) return false;
If the current sending buffer is the last one, schedule a message handler to
receive the done message from the receiving side.
<do_ok_msg() code>+= (<-U) [<-D->]
if (sbap->last_buffer)
sbap->done_mhid =
schedule_mhandler(set_msgtype(set_msgtype(0, control), done),
get_done, sbap);
Acknowledge the ok sub-message. The new negotiated values are accepted as is.
<do_ok_msg() code>+= (<-U) [<-D]
msg_type(&nm) = nullack_msg_type;
msg_length(&nm) = 0;
nm.max_sequence = sbap->nbid->next_seq_no - 1;
sbap->nbid->nnp.packets_per_burst = nm.burst_size = omp->burst_size;
sbap->nbid->nnp.burst_interval = nm.burst_rate = omp->burst_rate;
sbap->control_ack_timeout = omp->control_timer;
send_msg(sbap->nbid, (msgp) &nm);
The procedure send_pkts() sends the next burst of packets; ap is a
pointer to the sending-buffer information (because send_pkts() reacts to
timer events, iap is 0).
<sender.c>+= [<-D->]
static void send_pkts(void * ap, void * iap) {
sb_argsp sbap = (sb_argsp) ap;
u1b packet[max_packet_size];
dl_msgp dmp = (dl_msgp) packet;
msgp mp = (msgp) packet;
u1b pkts;
int j;
u1b * bp;
<send the next packet burst>
}
Defines send_pkts (links are to index).
Fill in some data-message boilerplate.
<send the next packet burst>= (<-U) [D->]
msg_type(mp) = data_msg_type;
dmp->packet_number =
sbap->nbid->packets_per_buffer - sbap->packets_remaining + 1;
dmp->buffernumber = sbap->bno;
dmp->max_sequence = sbap->nbid->next_seq_no - 1;
dmp->reserved = sbap->reserved;
bp = sbap->buffer + sbap->nbid->payload_size*(dmp->packet_number - 1);
Figure out how many packets to send in this burst (because send_pkts() had
been scheduled to execute, there must be at least one packet available to
send). The last packet in the buffer is treated differently from all the other
packets in the buffer; otherwise, send_pkts() schedules itself to send the
next burst of packets.
<send the next packet burst>+= (<-U) [<-D->]
assert(sbap->packets_remaining > 0);
pkts = minimum(sbap->nbid->nnp.packets_per_burst, sbap->packets_remaining);
sbap->packets_remaining -= pkts;
if (sbap->packets_remaining == 0) pkts--;
else (void) schedule_timer(sbap->nbid->nnp.burst_interval, send_pkts, sbap);
Send 'em off.
<send the next packet burst>+= (<-U) [<-D->]
for (j = 0; j < pkts; j++) {
msg_length(mp) = 0;
put_msg_data(mp, bp, sbap->nbid->payload_size);
send_msg(sbap->nbid, (msgp) packet);
bp += sbap->nbid->payload_size;
dmp->packet_number++;
}
In addition to sending the last packet in the buffer, schedule handlers for
resend and ok sub-messages from the receiver.
<send the next packet burst>+= (<-U) [<-D]
if (sbap->packets_remaining == 0) {
sbap->resend_mhid =
schedule_mhandler(set_msgtype(0, control), get_resend, ap);
sbap->ok_mhid =
schedule_mhandler(set_msgtype(0, control), get_ok, ap);
msg_type(mp) = ldata_msg_type;
msg_length(mp) = 0;
put_msg_data(mp, bp, sbap->nbid->last_payload_size);
send_msg(sbap->nbid, (msgp) packet);
}
resend_pkts() sends the next burst of retransmitted packets; ap points
to the retransmission information.
<sender.c>+= [<-D->]
static void resend_pkts(void * ap, void * iap) {
resend_datap rsdp = (resend_datap) ap;
u1b packet[max_packet_size];
dl_msgp dmp = (dl_msgp) packet;
msgp mp = (msgp) packet;
u1b pkts;
int j;
<retransmit lost packets>
}
Defines resend_pkts (links are to index).
Set pkts to the number of packets to send in this interval, which is the
smaller of the number of lost packets left to send and the number of packets
sent per interval. If there are still packets left to retransmit,
resend_pkts() schedules itself to execute in the next interval.
<retransmit lost packets>= (U->) [D->]
pkts = minimum(sba.nbid->nnp.packets_per_burst, rsdp->missing_pkt_cnt);
rsdp->missing_pkt_cnt -= pkts;
if (rsdp->missing_pkt_cnt)
(void) schedule_timer(sba.nbid->nnp.burst_interval, resend_pkts, rsdp);
Fill in some boilerplate in the retransmitted messages' header.
<retransmit lost packets>+= (U->) [<-D->]
dmp->max_sequence = sba.nbid->next_seq_no - 1;
dmp->buffernumber = sba.bno;
dmp->reserved = sba.reserved;
Retransmit the lost packets. The only tricky bit here is making sure the last
packet, if retransmitted, contains the proper amount of data (NETBLT apparently
places no constraints on packet-number ordering in retransmission requests).
<retransmit lost packets>+= (U->) [<-D->]
for (j = 0; j < pkts; j++) {
const u2b pno = rsdp->missing_pkts[rsdp->missing_pkt_cnt + j];
const bool last_pkt = pno == sba.nbid->packets_per_buffer;
dmp->packet_number = pno;
msg_length(mp) = 0;
msg_type(mp) = (last_pkt ? ldata_msg_type : data_msg_type);
put_msg_data(mp, sba.buffer + sba.nbid->payload_size*(pno - 1),
last_pkt ? sba.nbid->last_payload_size : sba.nbid->payload_size);
send_msg(sba.nbid, (msgp) packet);
}
Clean up if all packets have been retransmitted.
<retransmit lost packets>+= (U->) [<-D]
if (rsdp->missing_pkt_cnt == 0) {
free((char *) rsdp->missing_pkts);
free((char *) rsdp);
}
If the control message pointed to by iap contains a resend sub-message,
copy the missing packet numbers from the sub-message and starting
retransmission by calling resend_pkts().
<sender.c>+= [<-D->]
static void get_resend(void * ap, void * iap) {
sb_argsp sbap = (sb_argsp) ap;
acqr_msgp cmp = (acqr_msgp) (((msg_bundlep) iap)->mp);
resend_msgp rmp;
assert(is_msg_type(cmp, control));
if ((rmp = (resend_msgp) has_controlm(cmp, resend, sbap))) {
resend_datap rsdp = (resend_datap) malloc(sizeof(resend_data));
assert(rsdp);
rsdp->missing_pkts = (u2b *) malloc(sizeof(u2b)*rmp->pkt_cnt);
assert(rsdp->missing_pkts);
rsdp->missing_pkt_cnt = rmp->pkt_cnt;
byte_copy(rsdp->missing_pkts, ((u1b *) rmp) + sizeof(resend_msg),
rmp->pkt_cnt*2);
resend_pkts(rsdp, 0);
}
}
Defines get_resend (links are to index).
If the control message pointed to by iap contains an ok sub-message,
the current buffer has been successfully transfered and the current send is
over.
<sender.c>+= [<-D->]
static void get_ok(void * ap, void * iap) {
sb_argsp sbap = (sb_argsp) ap;
acqr_msgp cmp = (acqr_msgp) (((msg_bundlep) iap)->mp);
assert(is_msg_type(cmp, control));
if (do_ok_msg((msgp) cmp, sbap)) sbap->done = true;
}
Defines get_ok (links are to index).
If the control message pointed to by iap has a go sub-message, start
sending the contents of the current buffer by calling send_pkts(). Once
the go message has been discovered, there's no need for get_go() until the
next buffer, so it deletes itself.
<sender.c>+= [<-D->]
static void get_go(void * ap, void * iap) {
sb_argsp sbap = (sb_argsp) ap;
acqr_msgp cmp = (acqr_msgp) (((msg_bundlep) iap)->mp);
assert(is_msg_type(cmp, control));
if (!has_controlm(cmp, go, sbap)) return;
cancel_mhandler(sbap->go_mhid);
send_pkts(ap, 0);
}
Defines get_go (links are to index).
end_it() handles shut-down time-outs from the receive side; tf() is the
termination function for the buffer send.
<sender.c>+= [<-D->]
static void end_it(void * ap, void * iap) {
((sb_argsp) ap)->done = true;
}
static bool tf(void) {
return sba.done;
}
Defines end_it (links are to index).
Given nbid, the sending end of a NETBLT connection and buffer, a
pointer to a buffer of data, send the data in the buffer to the receiving end
of the connection.
<sender.c>+= [<-D]
void send_netblt(netblt_id nbid, u1b * buffer) {
if (nbid->current_bno == nbid->buffers_per_transfer) {
fprintf(stderr, "trying to send more data than was negotiated.\n");
exit(1);
}
<send a buffer of data>
}
Defines send_netblt (links are to index).
Set the buffer number and, if this is the last buffer of the transmission,
adjust the transfer parameters.
<send a buffer of data>= (<-U) [D->]
sba.last_buffer = (nbid->current_bno + 1) == nbid->buffers_per_transfer;
if (sba.last_buffer) {
nbid->nnp.transfer_size =
nbid->nnp.transfer_size -
(nbid->buffers_per_transfer - 1)*nbid->nnp.buffer_size;
nbid->packets_per_buffer =
ceiling(nbid->nnp.transfer_size, nbid->payload_size);
nbid->last_payload_size =
nbid->nnp.transfer_size -
(nbid->packets_per_buffer - 1)*(nbid->payload_size);
}
Set up the sending buffer information in sba.
<send a buffer of data>+= (<-U) [<-D->]
sba.nbid = nbid;
sba.buffer = buffer;
sba.bno = ++nbid->current_bno;
sba.packets_remaining = nbid->packets_per_buffer;
sba.nbid->next_seq_no = nbid->next_seq_no;
sba.reserved = (sba.last_buffer ? 1 : 0);
sba.done = false;
Schedule a handler for the go sub-message from the receiving side, then wait
for everything to die down.
<send a buffer of data>+= (<-U) [<-D->]
sba.resend_mhid = sba.ok_mhid = 0;
sba.go_mhid = schedule_mhandler(set_msgtype(0, control), get_go, &sba);
run_scheduler(tf);
All packets in the current buffer have been sent and received. Cancel any
outstanding message handlers, then wait for the done message from the other
side, scheduling a time out in case the receiving end dies.
<send a buffer of data>+= (<-U) [<-D]
if (sba.resend_mhid) cancel_mhandler(sba.resend_mhid);
if (sba.ok_mhid) cancel_mhandler(sba.ok_mhid);
if (sba.last_buffer) {
timer_id tid;
sba.done = false;
tid = schedule_timer(sba.control_ack_timeout*4*1000, end_it, &sba);
run_scheduler(tf);
cancel_mhandler(sba.done_mhid);
cancel_timer(tid);
}
receive_NETBLT() accepts a pointer to an empty buffer and returns when the
buffer's been filled.
<receiver.h>=
#ifndef _receiver_h_defined_
#define _receiver_h_defined_
#include "netblt.h"
extern void receive_netblt(netblt_id nbid, u1b *);
#endif
Defines "netblt.h" (links are to index).
<receiver.c>=
#include "m-handler.h"
#include "timer.h"
#include "bit-array.h"
#include "scheduler.h"
#include "control-sender.h"
#include "msg.h"
<buffer assembly information>
/* Sundries. */
static ab_args aba;
# define control_ack_timeout(_a) \
ceiling((_a)->nbid->packets_per_buffer, (_a)->nbid->nnp.packets_per_burst)*(_a)->nbid->nnp.burst_interval*1000*100
<receiver.c code>
Defines control_ack_timeout (links are to index).
The buffer-assembly routines need to know the following information to do their
job:
list_start()
buffer. A pointer to the buffer being filled.
bno. The number of the buffer being filled.
nbid. The NETBLT endpoint from which the buffer data is being
read.
pkts_rcvd. A bitmap indicating which packets have been
received.
rexmit_tid. The retransmitter's id.
rcv_mhid. The packet receiver's id.
rercv_mhid. The retransmitted-packet receiver's id.
done. If true, the buffer's been successfully received.
<buffer assembly information>= (<-U)
typedef struct {
u1b * buffer;
buffer_number bno;
netblt_id nbid;
bit_array pkts_rcvd;
timer_id rexmit_tid;
mhandler_id rcv_mhid, rercv_mhid;
bool done;
} ab_args, * ab_argsp;
Defines ab_args, ab_argsp (links are to index).
Given abap, a pointer to buffer-assembly information, buffer_full()
returns true if the buffer has been successfully filled, false otherwise. If
the buffer has been filled, it also cancels all the handlers and indicates
receive processing for this buffer is done.
<receiver.c code>= (<-U) [D->]
static bool buffer_full(ab_argsp abap) {
if (onescnt_ba(abap->pkts_rcvd) == abap->nbid->packets_per_buffer) {
cancel_timer(abap->rexmit_tid);
cancel_mhandler(abap->rcv_mhid);
cancel_mhandler(abap->rercv_mhid);
abap->done = true;
return true;
}
return false;
}
Defines buffer_full (links are to index).
handle_losses() wakes up after a period of time closely related to the time
it should take to fill the buffer and checks to see if the buffer is full. If
the buffer is full, handle_losses() returns. If the buffer is not full,
handle_losses() constructs a retransmission sub-message, which it hands off
to the control-message sender, and then reschedules itself.
<receiver.c code>+= (<-U) [<-D->]
static void handle_losses(void * ap, void * iap) {
ab_argsp abap = (ab_argsp) ap;
resend_msgp rmp;
u2b * pnop, i;
if (buffer_full(abap)) return;
rmp = (resend_msgp) make_msg();
submsg_type(rmp) = resend_submessage_type;
sm_buffer_number(rmp) = abap->bno;
rmp->pkt_cnt = 0;
pnop = resend_pkt_start(rmp);
for (i = 1; i <= abap->nbid->packets_per_buffer; i++)
if (!test_ba(abap->pkts_rcvd, i)) {
*pnop++ = i;
rmp->pkt_cnt++;
}
send_ctlsdr(rmp, 0);
abap->rexmit_tid =
schedule_timer(control_ack_timeout(abap)*1000, handle_losses, abap);
}
Defines handle_losses (links are to index).
copy_data() moves copy_size bytes of data from the message pointed to
by iap into the current buffer if iap points to a valid data message.
If iap doesn't point to a valid data message, copy_data() does nothing.
<receiver.c code>+= (<-U) [<-D->]
static void copy_data(ab_argsp abap, void * iap, u2b copy_size) {
dl_msgp mp = (dl_msgp) (((msg_bundlep) iap)->mp);
debugp((stderr, "Got a %6s packet, bno = %2d, pno = %2d.\n", msg_name(mp),
mp->buffernumber, mp->packet_number));
if (mp->buffernumber != abap->bno) return;
if ((mp->packet_number < 1) ||
(mp->packet_number > abap->nbid->packets_per_buffer)) {
fprintf(stderr, "bad packet number %d received.\n", mp->packet_number);
return;
}
if (test_ba(abap->pkts_rcvd, mp->packet_number)) return;
byte_copy(abap->buffer + (mp->packet_number - 1)*abap->nbid->payload_size,
msg_data(mp), copy_size);
set_ba(abap->pkts_rcvd, mp->packet_number);
}
Defines copy_data (links are to index).
receive_sends() accepts the data or ldata message pointed at by iap and
copies the message's data into the current buffer. When the last packet of
data has been copied (as determined by the message type, not by the actual
packet count), receive_sends() schedules the lost-packet handler
handle_losses().
<receiver.c code>+= (<-U) [<-D->]
static void receive_sends(void *ap, void * iap) {
const bool last_pkt = is_msg_type(((msg_bundlep) iap)->mp, ldata);
ab_argsp abap = (ab_argsp) ap;
copy_data(abap, iap, last_pkt ? abap->nbid->last_payload_size
: abap->nbid->payload_size);
if (last_pkt) handle_losses(ap, 0);
}
Defines receive_sends (links are to index).
receive_resends() accepts the retransmitted data or ldata message pointed
at by iap and copies the message's data into the current buffer.
<receiver.c code>+= (<-U) [<-D->]
static void receive_resends(void *ap, void * iap) {
ab_argsp abap = (ab_argsp) ap;
copy_data(abap, iap, is_msg_type(((msg_bundlep) iap)->mp, data)
? abap->nbid->payload_size
: abap->nbid->last_payload_size);
(void) buffer_full(abap);
}
Defines receive_resends (links are to index).
tf() is the schedule termination function used by receive_NETBLT() to
figure out when the buffer's been successfully received.
<receiver.c code>+= (<-U) [<-D->]
static bool tf(void) {
return aba.done;
}
Copy the next buffer's worth of data from the NETBLT endpoint nbid into the
buffer pointed to by buffer.
<receiver.c code>+= (<-U) [<-D]
void receive_netblt(netblt_id nbid, u1b * buffer) {
submsgp smp;
if (nbid->current_bno == nbid->buffers_per_transfer) {
fprintf(stderr, "trying to send more data than was negotiated.\n");
exit(1);
}
<receive_netblt() code>
}
Defines receive_netblt (links are to index).
If this is the last buffer, adjust the transfer parameters appropriately.
<receive_netblt() code>= (<-U) [D->]
if (nbid->current_bno == nbid->buffers_per_transfer - 1) {
nbid->nnp.transfer_size =
nbid->nnp.transfer_size -
(nbid->buffers_per_transfer - 1)*nbid->nnp.buffer_size;
nbid->packets_per_buffer =
ceiling(nbid->nnp.transfer_size, nbid->payload_size);
nbid->last_payload_size =
nbid->nnp.transfer_size -
(nbid->packets_per_buffer - 1)*(nbid->payload_size);
}
Set up the information needed to receive the next buffer.
<receive_netblt() code>+= (<-U) [<-D->]
aba.buffer = buffer;
aba.nbid = nbid;
aba.bno = ++nbid->current_bno;
aba.pkts_rcvd = init_ba(nbid->packets_per_buffer);
aba.done = false;
Schedule handlers to deal with transmitted packets, retransmitted packets, and
missing packets.
<receive_netblt() code>+= (<-U) [<-D->]
aba.rcv_mhid =
schedule_mhandler(set_msgtype(0, data), receive_sends, &aba);
aba.rercv_mhid =
schedule_mhandler(set_msgtype(0, ldata), receive_resends, &aba);
aba.rexmit_tid =
schedule_timer(control_ack_timeout(&aba)*1000, handle_losses, &aba);
Set up some message boilerplate needed by the control sub-message handler.
<receive_netblt() code>+= (<-U) [<-D->]
smp = (submsgp) make_msg();
submsg_type(smp) = go_submessage_type;
sm_buffer_number(smp) = aba.bno;
send_ctlsdr(smp, 0);
Run everything until they're done, then send an ok sub-message.
<receive_netblt() code>+= (<-U) [<-D->]
run_scheduler(tf);
smp = (submsgp) make_msg();
submsg_type(smp) = ok_submessage_type;
sm_buffer_number(smp) = aba.bno;
((ok_msgp) smp)->burst_size = nbid->nnp.packets_per_burst;
((ok_msgp) smp)->burst_rate = nbid->nnp.burst_interval;
((ok_msgp) smp)->control_timer = control_ack_timeout(&aba);
send_ctlsdr(smp, 0);
If there's no more buffers coming, schedule the done handler; in any event,
clean up and return.
<receive_netblt() code>+= (<-U) [<-D]
if (nbid->current_bno == nbid->buffers_per_transfer) {
msg m;
run_scheduler(done_ctlsdr);
msg_type(&m) = done_msg_type;
msg_length(&m) = 0;
send_msg(nbid, &m);
}
free_ba(aba.pkts_rcvd);
The NETBLT Run-Time System
The Scheduler
The scheduler is responsible for picking up the pending events and executing
the associated handlers. init_schedule() establishes the scheduler;
term_scheduler() shuts down the scheduler; and run_scheduler() takes
care of pending events by calling the associated handler.
The event handlers communicate to the scheduler via the interval data
structure, which has the following fields:
-
fun. A pointer to the function to call when the event occurs.
-
arg. An argument passed to fun() when it is called; arg
is provided at the same time fun() is given, and is uninterpreted by the
event handlers and the scheduler.
-
iarg. A pointer to information about the event that triggered
execution.
<interval.h>=
#ifndef _interval_h_defined_
#define _interval_h_defined_
/* The start of an interval. */
typedef struct {
void (* fun)(void *, void *);
void * arg;
void * iarg;
} interval, * interval_ptr;
#define put_interval(_iq, _f, _a, _ia) \
do {interval_ptr _ip = (interval_ptr) put_clist(_iq); \
_ip->fun = _f; _ip->arg = _a; _ip->iarg = _ia; } while (false)
#endif
Defines interval, "interval.h", interval_ptr, put_interval (links are to index).
The scheduler is requires explicit scheduling, which means events are handled
only when run_scheduler() is explicitly called from code. Incoming events
are automatically queued without explicit intervention through the magic of
interrupt processing, but the handlers for the queued events are executed
only when run_scheduler() is called.
<scheduler.h>=
#ifndef _scheduler_h_defined_
#define _scheduler_h_defined_
#include "defs.h"
extern void
init_scheduler(int),
run_scheduler(bool (* f)(void)),
term_scheduler(void);
#endif
The scheduler itself is essentially a circular list. The current_queue
circular list contains the pending events. When it comes time to execute
<scheduler.c>= [D->]
#include "scheduler.h"
#include "m-handler.h"
#include "timer.h"
#include "interval.h"
/* Sundries. */
static c_list old_queue, new_queue, current_queue;
To start, build the queues of waiting events and initialize the timer and
message handlers.
<scheduler.c>+= [<-D->]
void init_scheduler(int skt) {
old_queue = init_clist(sizeof(interval));
current_queue = new_queue = init_clist(sizeof(interval));
init_timer(¤t_queue);
init_mhandler(skt, ¤t_queue);
}
void term_scheduler(void) {
}
Defines init_scheduler, term_scheduler (links are to index).
Run the scheduler with the termination function done().
<scheduler.c>+= [<-D]
void run_scheduler(bool (* done)(void)) {
extern int pause(void);
loop {
<iterate through the pending events>
if ((* done)()) break;
pause();
}
}
Defines run_scheduler (links are to index).
As long as there are pending events, swap the current event queue for a new,
empty queue to avoid conflicts with newer incoming events. Then run through
the pending events, executing the handler function for each.
<iterate through the pending events>= (<-U)
while (!empty_clist(current_queue)) {
old_queue = current_queue;
current_queue = new_queue;
do {
interval_ptr ip = (interval_ptr) get_clist(old_queue);
(* ip->fun)(ip->arg, ip->iarg);
}
while (!empty_clist(old_queue));
new_queue = old_queue;
}
The Message Handler
The message handler needs to be initialized before being used and terminated
after being used. schedule_mhandler() queues up a handler waiting for a
message; cancel_mhandler() dequeues a waiting message handler.
<m-handler interface>= (U->)
extern void
init_mhandler(int, c_list *),
term_mhandler(void),
cancel_mhandler(mhandler_id);
extern mhandler_id
schedule_mhandler(u2b msg_list, void (* f)(void *, void *), void * arg);
A message handler is called with a pointer to a message bundle containing
information about the message for which the handler is waiting. The macros
set_msgtype() and is_set_msgtype() manage a bitmap of message types for
which handlers can wait.
<m-handler.h>=
#include "c-list.h"
#include "msg.h"
#ifndef _m_handler_h_defined_
#define _m_handler_h_defined_
typedef u4b mhandler_id;
#define set_msgtype(_n, _t) ((_n) | (1 << _t ## _msg_type))
#define is_set_msgtype(_n, _t) ((_n) & (1 << _t ## _msg_type))
typedef struct {
u4b host;
u2b port;
msgp mp;
} msg_bundle, * msg_bundlep;
<m-handler interface>
#endif
Defines is_set_msgtype, mhandler_id, msg_bundle, msg_bundlep, "msg.h", set_msgtype (links are to index).
Allocate a new message bundle, either by pulling one off the free list or
creating one from the heap.
<m-handler code>= (U->) [D->]
static msg_bundlep new_mbundle(void) {
msg_bundlep mbp;
if (mb_free_list) {
mbp = (msg_bundlep) mb_free_list;
mb_free_list = (msg_bundlep) mbp->host;
}
else {
mbp = (msg_bundlep) malloc(sizeof(msg_bundle));
assert(mbp);
mbp->mp = make_msg();
}
reset_msg(mbp->mp);
return mbp;
}
Defines new_mbundle (links are to index).
Free up a message bundle by hanging it on the free list.
<m-handler code>+= (U->) [<-D->]
static void free_mbundle(msg_bundlep mbp) {
mbp->host = (u4b) mb_free_list;
mb_free_list = mbp;
}
static void fm(void * arg, void * iarg) {
free_mbundle((msg_bundlep) iarg);
}
Defines free_mbundle (links are to index).
Read a message from socket skt, fill out a message bundle, and return a
pointer to the new bundle. The reaction to a bad message checksum is a bit
draconian, but useful for now.
<m-handler code>+= (U->) [<-D->]
static msg_bundlep read_pkt(int skt) {
msg_bundlep mbp = new_mbundle();
int i = sizeof(struct sockaddr_in);
struct sockaddr_in sa;
int e;
e = recvfrom(skt, (char *) mbp->mp, max_packet_size, 0,
(struct sockaddr *) &sa, &i);
check(e < 0, "recvfrom()");
mbp->host = ntohl(sa.sin_addr.s_addr);
mbp->port = ntohs(sa.sin_port);
debugp((stderr, "Receive %s from %s:%d.\n", message_name(mbp->mp),
ina2str(mbp->host), mbp->port));
if (!checksum_ok(mbp->mp, false)) {
fprintf(stderr, "NETBLT message checksum failed.\n");
exit(1);
}
return mbp;
}
Defines read_pkt (links are to index).
On an interrupt from the socket skt, read as many messages as are queued
up. For each message, find the handlers interested in the message and queue
them on the interval queue; the last handler queued is fm() a
pseudo-handler that deletes the message bundle. If there are no handlers
waiting for the message, delete it.
<m-handler code>+= (U->) [<-D->]
static void m_handler(int s) {
while (input_ready(skt)) {
msg_bundlep mbp;
mh_info_ptr mhip;
mbp = read_pkt(skt);
for (mhip = waiters[msg_type(mbp->mp)]; mhip; mhip = mhip->next)
put_interval(*event_queuep, mhip->f, mhip->arg, mbp);
if (waiters[msg_type(mbp->mp)])
put_interval(*event_queuep, fm, 0, mbp);
else {
free_mbundle(mbp);
debugp((stderr, "Message passed without notice.\n"));
}
}
}
init_mhandler() initializes the message handler with s, the socket over
which messages are being read, and eqp, the interval queue on which
satisfied message handlers are hung.
term_mhandler() doesn't do anything at the moment, which is incorrect.
<m-handler code>+= (U->) [<-D->]
void init_mhandler(int s, c_list * eqp) {
extern void (*sigset (int sig, void (*disp)(int)))(int);
assert(!event_queuep);
event_queuep = eqp;
skt = s;
mb_free_list = 0;
free_list = 0;
set_io_handler(m_handler);
set_io_int(skt);
}
void term_mhandler(void) {
}
Defines init_mhandler, term_mhandler (links are to index).
schedule_mhandler() takes a message handler f(), a pointer to an
uninterpreted (by schedule_mhandler() argument arg, and a list of
message types msg_set and queues f() so it will be paired with the next
occurence of a message in msg_set.
Each message type has a queue; f() and its argument arg get hung on the
queue for every message specified in msg_set. Once queued f() is
refered to by a mhandler_id, which is a combination of a counter and the
message set specified by msg_set.
<m-handler code>+= (U->) [<-D->]
mhandler_id schedule_mhandler(
u2b msg_set, void (* f)(void *, void *), void * arg) {
static u2b id = 0;
mhandler_id mid = (((u4b) id++) << 16) | (msg_set & 0xffff);
u1b i;
assert(msg_set);
mask_interrupts();
for (i = 0; i < msg_type_count; i++)
if (msg_set & (1 << i)) {
mh_info_ptr mhip;
if (free_list) {
mhip = free_list;
free_list = mhip->next;
}
else {
mhip = (mh_info_ptr) malloc(sizeof(mh_info));
assert(mhip);
}
mhip->f = f;
mhip->arg = arg;
mhip->mid = mid;
mhip->next = waiters[i];
waiters[i] = mhip;
}
unmask_interrupts();
return mid;
}
Defines schedule_mhandler (links are to index).
When a new message arrives, a message handler (if any) has to be cleared from
all waiting queues, not just the one associated with the new message.
free_mhi() accepts mhip, a pointer to a queue of waiting handlers, and
mid, a message handler id, and removes all occurrences of the message
handler with the identifier mid from the queue, returning a pointer to the
cleaned-up queue.
<m-handler code>+= (U->) [<-D->]
static mh_info_ptr free_mhi(mh_info_ptr mhip, mhandler_id mid) {
mh_info h;
mh_info_ptr this, last;
last = &h;
this = last->next = mhip;
while (this) {
if (this->mid == mid) {
last->next = this->next;
this->next = free_list;
free_list = this;
}
else
last = this;
this = last->next;
}
return h.next;
}
Defines free_mhi (links are to index).
Given the message-handler identifier mid, cancel_mhandler() runs
through the waiting queues removing all occurrences of the handler associated
with mid.
<m-handler code>+= (U->) [<-D]
void cancel_mhandler(mhandler_id mid) {
u2b msg_set = (u2b) mid;
u1b i;
mask_interrupts();
for (i = 0; i < msg_type_count; i++)
if (msg_set & (1 << i)) waiters[i] = free_mhi(waiters[i], mid);
unmask_interrupts();
}
Defines cancel_mhandler (links are to index).
<m-handler.c>=
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "m-handler.h"
#include "interval.h"
#include "system.h"
/* The message-handler data structure. */
typedef struct Mh_info {
microsecs wake_up;
void (* f)(void *, void *);
void * arg;
mhandler_id mid;
struct Mh_info * next;
} mh_info, * mh_info_ptr;
static mh_info_ptr waiters[msg_type_count];
/* Sundires */
static mh_info_ptr free_list;
static int skt;
static c_list * event_queuep;
static msg_bundlep mb_free_list;
<m-handler code>
Defines mh_info, mh_info_ptr (links are to index).
The Timer
The timer needs to be initialized before being used and terminated
after being used. schedule_timer() queues up a handler waiting for a
time-out; cancel_timer() dequeues a waiting time-out handler.
<timer.h>=
#ifndef _timer_h_defined_
#define _timer_h_defined_
#include "c-list.h"
typedef struct Timer_id * timer_id;
extern void
init_timer(c_list * eqp),
cancel_timer(timer_id),
term_timer(void);
extern timer_id
schedule_timer(microsecs, void (* f)(void *, void *), void * arg);
#endif
Defines "timer.h", timer_id (links are to index).
schedule_next() pulls the shortest time-out interval from the queue and
sets the time to it.
<timer code>= (U->) [D->]
static void schedule_next(void) {
if (pending.next) set_timer(pending.next->wake_up);
}
Defines schedule_next (links are to index).
Return the timer's current value.
<timer code>+= (U->) [<-D->]
static microsecs get_timer(void) {
struct itimerval t;
if (getitimer(ITIMER_REAL, &t) == -1) {
perror("getitimer() failed");
exit(1);
}
return (t.it_value.tv_sec*1000000 + t.it_value.tv_usec);
}
Defines get_timer (links are to index).
timer_handler() deals with a time-out interrupt by subtracting the shortest
time-out interval from all the pending time-out intervals. Time-out handlers
associated with zero-length intervals are placed on the pending interval queue,
and the timer is set to the new shortest time-out interval (if any).
<timer code>+= (U->) [<-D->]
static void timer_handler(int s) {
timer_id tid;
mask_interrupts();
for (tid = pending.next; tid; tid = tid->next)
tid->wake_up -= pending.next->wake_up;
while (pending.next && (pending.next->wake_up == 0)) {
interval_ptr ip = (interval_ptr) put_clist(*event_queuep);
ip->fun = pending.next->f;
ip->arg = pending.next->arg;
ip->iarg = 0;
tid = pending.next->next;
pending.next->next = free_list;
free_list = pending.next;
pending.next = tid;
}
schedule_next();
unmask_interrupts();
}
Defines timer_handler (links are to index).
init_timer() initializes the time-out interrupt handler; eqp is the
interval queue, upon which enabled time-out handlers are queued for execution.
<timer code>+= (U->) [<-D->]
void init_timer(c_list * eqp) {
extern void (*sigset (int sig, void (*disp)(int)))(int);
assert(!event_queuep);
event_queuep = eqp;
set_timer_handler(timer_handler);
}
void term_timer(void) {
}
Defines init_timer, term_timer (links are to index).
schedule_timer() takes a time-out handler f(), a pointer to an
uninterpreted (by schedule_timer() argument arg, and a time-out
interval wake_up and queues f() so it will be enabled for execution no
earlier than the the time interval given by wake_up.
<timer code>+= (U->) [<-D->]
timer_id schedule_timer(
microsecs wake_up, void (* f)(void *, void *), void * arg) {
timer_id tid, next, prev;
mask_interrupts();
<insert the new time-out interval>
<reschedule the timer if necessary>
unmask_interrupts();
return tid;
}
Defines schedule_timer (links are to index).
Allocating a new pending interval-queue element and insert it in the pending
interval queue in increasing interval length order.
<insert the new time-out interval>= (U->)
if (free_list) {
tid = free_list;
free_list = free_list->next;
}
else {
tid = (timer_id) malloc(sizeof(Timer_id));
assert(tid);
}
tid->wake_up = wake_up;
tid->f = f;
tid->arg = arg;
prev = &pending;
next = prev->next;
while (next && (tid->wake_up >= next->wake_up)) {
prev = next;
next = next->next;
}
tid->next = next;
prev->next = tid;
If the new time-out interval is the shortest and there is a now longer time-out
scheduled, reschedule the timer with the new shortest time-out interval.
Rescheduling involves subtracting the elapsed time from all existing time-out
intervals except the new one, and then scheduling the new one.
<reschedule the timer if necessary>= (U->)
if (pending.next == tid) {
if (tid->next && (tid->wake_up != tid->next->wake_up)) {
microsecs spent = get_timer();
timer_id tid2;
set_timer(0);
for (tid2 = tid->next; tid2; tid2 = tid2->next) tid2->wake_up -= spent;
}
schedule_next();
}
cancel_timer() removes the time-out with timer id tid from the pending
time-out queue. cancel_timer() does nothing if the time-out is not in the
queue (in particular, if the time-out's already occured, the associated
time-out handler will not be removed from the pending execution queue).
<timer code>+= (U->) [<-D]
void cancel_timer(timer_id tid) {
timer_id next, prev;
mask_interrupts();
prev = &pending;
next = prev->next;
while (next && (tid != next)) {
prev = next;
next = next->next;
}
if (next) {
prev->next = tid->next;
<if necessary, reschedule the timer>
tid->next = free_list;
free_list = tid;
}
unmask_interrupts();
}
Defines cancel_timer (links are to index).
If the shortest time-out interval was canceled, and there are other pending
time outs, reschedule the timer.
<if necessary, reschedule the timer>= (U->)
if (prev == &pending) {
microsecs spent = get_timer();
set_timer(0);
if (tid->next && (tid->wake_up != tid->next->wake_up)) {
timer_id tid2;
for (tid2 = pending.next; tid2; tid2 = tid2->next)
tid2->wake_up -= spent;
schedule_next();
}
}
<timer.c>=
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include "timer.h"
#include "interval.h"
#include "system.h"
/* The timer data structure. */
typedef struct Timer_id {
microsecs wake_up;
void (* f)(void *, void *);
void * arg;
timer_id next;
} Timer_id;
static Timer_id pending;
/* Sundires */
static timer_id free_list;
static c_list * event_queuep;
<timer code>
Defines Timer_id (links are to index).
The Control Sub-Message Sender
Control sub-messages are sent from the receiving end to the sending end of a
NETBLT connection.
<control-sender.h>=
#ifndef _control_sender_h_defined_
#define _control_sender_h_defined_
#include "netblt.h"
extern void
init_ctlsdr(netblt_id),
send_ctlsdr(void *, void *),
term_ctlsdr(void);
extern bool
done_ctlsdr(void);
#endif
%define "control-sender.h"
seqno_handler() accepts null-ack, data, or last-data messages and updates
the maximum consecutive-received sequence number on the receiving side of the
conneciton. If the new maximum sequence number is larger than the current one,
clear acknowledged control messages from the pending queue.
<control sender code>= (U->) [D->]
static void seqno_handler(void * ap, void * iap) {
msgp mp = (msgp) (((msg_bundlep) iap)->mp);
sequence_number sno;
sno = (is_msg_type(mp, nullack) ? ((n_msgp) mp)->max_sequence
: ((dl_msgp) mp)->max_sequence);
if (max_rcvd_sno < sno) {
max_rcvd_sno = sno;
<clear acknowledged control messages>
}
}
Defines seqno_handler (links are to index).
Drop all acknowledged control messages from the unacknowledged queue. If there
are no more unacknowledged control messages, cancel the control-message
sender.
<clear acknowledged control messages>= (<-U)
do {
submsgp smp = *((submsgp *) head_clist(pending_ctl_msgs));
if (smp->sequence_number > sno) break;
free_msg((msgp) (*((submsgp *) get_clist(pending_ctl_msgs))));
}
while (!empty_clist(pending_ctl_msgs));
if (empty_clist(pending_ctl_msgs)) {
cancel_timer(tid);
tid = 0;
}
Initialize the control sending code.
<control sender code>+= (U->) [<-D->]
void init_ctlsdr(netblt_id n) {
max_sent_sno = max_rcvd_sno = 0;
nbid = n;
pending_ctl_msgs = init_clist(sizeof(submsgp));
timer_interval = 10000000;
mhid = schedule_mhandler(
set_msgtype(set_msgtype(set_msgtype(0, data), ldata), nullack),
seqno_handler, 0);
}
Defines init_ctlsdr (links are to index).
send_ctlsdr() adds the new control sub-message ap to the unacknowledged
message queue and then resends all unacknowledged control sub-messages to the
sending side of the NETBLT connection. If ap is zero, then
send_cltsdr() was called because of a timer expiry, in which case
send_cltsdr() just resends all unacknowledged control sub-messages.
<control sender code>+= (U->) [<-D->]
void send_ctlsdr(void * ap, void * iap) {
submsgp smp = (submsgp) ap;
submsgp * smpp; /* must zero test before dereference. */
u1b * bp, * end;
msgp mp = make_msg();
if (smp) {
smp->sequence_number = ++max_sent_sno;
*((submsgp *) put_clist(pending_ctl_msgs)) = smp;
if (tid) cancel_timer(tid);
}
msg_type(mp) = control_msg_type;
bp = msg_data(mp);
end = ((u1b *) mp) + max_packet_size;
initi_clist(pending_ctl_msgs);
while ((smpp = (submsgp *) nexti_clist(pending_ctl_msgs))) {
const u2b l = smsg_length(*smpp);
if (bp + l > end) break;
byte_copy(bp, *smpp, l);
bp += l;
}
msg_length(mp) = bp - msg_data(mp) + msg_header_size(mp);
send_msg(nbid, mp);
free_msg(mp);
tid = schedule_timer(timer_interval, send_ctlsdr, 0);
}
Defines send_ctlsdr (links are to index).
done_ctlsdr() returns true if and only if the sending side has received all
outstanding control sub-messages.
<control sender code>+= (U->) [<-D->]
bool done_ctlsdr(void) {
return max_rcvd_sno == max_sent_sno;
}
Defines done_ctlsdr (links are to index).
term_ctlsdr() hangs up the control sub-message sender.
<control sender code>+= (U->) [<-D]
void term_ctlsdr(void) {
if (tid) cancel_timer(tid);
tid = 0;
}
Defines term_ctlsdr (links are to index).
<control-sender.c>=
#include "control-sender.h"
#include "timer.h"
#include "m-handler.h"
#include "msg.h"
/* Sundries. */
static timer_id tid;
static mhandler_id mhid;
static netblt_id nbid;
static sequence_number max_rcvd_sno;
static sequence_number max_sent_sno;
static c_list pending_ctl_msgs;
static microsecs timer_interval;
<control sender code>
Utilities and Miscellany
System Dependencies
This code currently runs on three different operating systems: SunOS 4.1.*,
Solaris 2.*, and Linux. Wrappers help keep the peace among the differing
systems. There are wrappers to
Set an interrupt on socket connections (set_io_int()).
Mask interrupts (mask_interrupts()).
Enable interrupts (unmask_interrupts()).
Set a handler for input interrupts (set_io_handler().
Set a handler for timer interrupts (set_timer_handler().
Set the timer (set_timer().
<system.h>=
#ifndef _set_io_int_h_
#define _set_io_int_h_
#include "defs.h"
extern void set_io_int(int), mask_interrupts(void), unmask_interrupts(void),
set_io_handler(), set_timer_handler(), set_timer(microsecs),
bind_skt(int, u4b, u2b);
extern bool input_ready(int);
extern int get_host_name(char *, u2b);
#endif
Defines "system.h" (links are to index).
Code for SunOS 4.1.* systems.
<sunos.c>=
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <errno.h>
#include "defs.h"
#include "system.h"
extern int sigblock(int);
void set_io_int(int fd) {
check(fcntl(fd, F_SETOWN, getpgrp()), "fcntl(f_setown)");
check(fcntl(fd, F_SETFL, FASYNC), "ioctl(f_setfl, fasync)");
}
bool input_ready(int fd) {
struct timeval t;
fd_set r;
int e;
extern int select(int, fd_set *, fd_set *, fd_set *, struct timeval *);
if (fd < 0) return false;
bzero((char *) &t, sizeof(t));
FD_ZERO(&r);
FD_SET(fd, &r);
e = select(fd + 1, &r, NULL, NULL, &t);
if ((e < 0) && (errno == EBADF)) return 0;
check(e < 0, "select()");
return e;
}
void mask_interrupts() {
(void) sigblock(sigmask(SIGIO) | sigmask(SIGALRM));
}
void unmask_interrupts () {
extern int sigsetmask(int);
(void) sigsetmask(sigblock(0) & ~(sigmask(SIGIO) | sigmask(SIGALRM)));
}
void set_io_handler(void (* h)(int)) {
check(signal(SIGIO, h) == SIG_ERR, "sigset(sigio)");
}
void set_timer_handler(void (* h)(int)) {
check(signal(SIGALRM, h) == SIG_ERR, "sigset(sigalrm)");
}
void set_timer(microsecs i) {
extern int ualarm(int, int);
ualarm(i, 0);
}
int get_host_name(char * hostname, u2b hostname_size) {
return (gethostname(hostname, (int) hostname_size) ? errno : 0;
}
void bind_skt(int skt, u4b host, u2b port) {
struct sockaddr_in sa;
const int sas = sizeof(struct sockaddr_in);
extern int bind(int, struct sockaddr *, int);
bzero((char *) &sa, sas);
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(host);
sa.sin_port = htons(port);
check(bind(skt, (struct sockaddr *) &sa, sas), "bind()");
}
Code for Solaris systems.
<solaris.c>=
#include <sys/types.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include "defs.h"
#include "system.h"
extern int sighold(int), sigrelse(int);
extern void (*sigset (int sig, void (*disp)(int)))(int);
void set_io_int(int fd) {
check(fcntl(fd, F_SETOWN, getpgrp()), "fcntl(f_setown)");
check(ioctl(fd, I_SETSIG, S_RDNORM), "ioctl(i_setsig, s_rdnorm)");
}
bool input_ready(int fd) {
int e, i;
check((e = ioctl(fd, I_NREAD, &i)) < 0, "ioctl(I_NREAD)");
return e;
}
void mask_interrupts() {
check(sighold(SIGPOLL), "sighold(POLL)");
check(sighold(SIGALRM), "sighold(ALRM)");
}
void unmask_interrupts () {
check(sigrelse(SIGALRM), "sigrelse(ALRM)");
check(sigrelse(SIGPOLL), "sigrelse(POLL)");
}
void set_io_handler(void (* h)(int)) {
check(sigset(SIGIO, h) == SIG_ERR, "sigset(sigio)");
}
void set_timer_handler(void (* h)(int)) {
check(sigset(SIGALRM, h) == SIG_ERR, "sigset(sigalrm)");
}
void set_timer(microsecs i) {
ualarm(i, 0);
}
int get_host_name(char * hostname, u2b hostname_size) {
return (gethostname(hostname, (int) hostname_size) ? errno : 0);
}
void bind_skt(int skt, u4b host, u2b port) {
struct sockaddr_in sa;
const int sas = sizeof(struct sockaddr_in);
bzero((char *) &sa, sas);
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(host);
sa.sin_port = htons(port);
check(bind(skt, (struct sockaddr *) &sa, sas), "bind()");
}
Circular Lists
Your basic implementation of a circular list; there are routines to initialize
and delete circular lists, routines to put and get things from the circular
list, as well as return the head of the list; and routines to iterate through
the list (initi_clist() and nexti_clist()).
<c-list.h>=
#ifndef _c_list_h_defined_
#define _c_list_h_defined_
#include "defs.h"
typedef struct C_list * c_list;
extern c_list init_clist(u2b);
extern void
term_clist(c_list),
* head_clist(c_list),
* put_clist(c_list),
* get_clist(c_list),
initi_clist(c_list),
* nexti_clist(c_list);
extern bool empty_clist(c_list);
#endif
Defines c_list, "c-list.h" (links are to index).
The C_list data structure is the handle for a circular list. The fields of
C_list are:
-
head. A pointer to the head of the list.
-
tail. A pointer to the tail of the list.
-
ring_size. The total number of elements in the list.
-
size. The number of elements in use; the size of the list.
-
item_size. The size of a queue item element, in bytes.
-
cursor. A pointer to the next queue element to be traversed
during an iteration.
-
icnt. The number of queue elements traversed during the current
iteration.
<c-list data structures>= (U->) [D->]
typedef struct C_list {
u1b * head, * tail;
u2b ring_size, size, item_size;
u1b * cursor;
u2b icnt;
} C_list;
Defines C_list (links are to index).
Circular-list elements are stored in a byte vector. The first four bytes of a
vector are a pointer to the next circular-list element; the second four bytes
of the vector are a pointer to the previous circular-list element; the
remainder of the vector contains the element being stored in the list.
<c-list data structures>+= (U->) [<-D]
# define next_b(_b) ((u1b **) (_b))[0]
# define prev_b(_b) ((u1b **) (_b))[1]
# define data_o(_b) ((_b) + 8)
Defines data_o, next_b, prev_b (links are to index).
Given a pointer to the circular list cl, grow() adds a new element to
cl.
<c-list code>= (U->) [D->]
static void grow(c_list cl) {
u1b * i = (u1b *) malloc(cl->item_size + 8);
assert(i);
prev_b(i) = prev_b(cl->tail);
next_b(prev_b(cl->tail)) = i;
next_b(i) = cl->tail;
prev_b(cl->tail) = i;
cl->tail = i;
cl->ring_size++;
}
Defines grow (links are to index).
init_clist() creates and returns a new circular list; each list element
will be item_size bytes big.
<c-list code>+= (U->) [<-D->]
c_list init_clist(u2b item_size) {
c_list cl;
assert(item_size > 0);
cl = (c_list) malloc(sizeof(C_list));
assert(cl);
cl->head = cl->tail = (u1b *) malloc(item_size + 8);
prev_b(cl->head) = cl->head;
next_b(cl->head) = cl->head;
cl->ring_size = 1;
cl->item_size = item_size;
cl->size = 0;
return cl;
}
Defines init_clist (links are to index).
Dispose of the circular list cl; this isn't very neat at the moment.
<c-list code>+= (U->) [<-D->]
void term_clist(c_list cl) {
free((char *) cl);
}
Defines term_clist (links are to index).
Put a new element on the circular list cl and return a pointer to the new
element.
<c-list code>+= (U->) [<-D->]
void * put_clist(c_list cl) {
u1b * i;
if (cl->size == cl->ring_size) grow(cl);
i = data_o(cl->tail);
cl->tail = next_b(cl->tail);
cl->size++;
return (void *) i;
}
Defines put_clist (links are to index).
Return a pointer to the head of the circular list cl; this also removes the
head from the list. The list must not be empty.
<c-list code>+= (U->) [<-D->]
void * get_clist(c_list cl) {
u1b * i;
if (cl->size == 0) {
fprintf(stderr, "Doing a get on an empty c-list.\n");
exit(1);
}
i = data_o(cl->head);
cl->head = next_b(cl->head);
cl->size--;
return i;
}
Defines get_clist (links are to index).
Return a pointer to the head of the circular list cl without changing
cl. The list must not be empty.
<c-list code>+= (U->) [<-D->]
void * head_clist(c_list cl) {
if (cl->size == 0) {
fprintf(stderr, "Getting the head of an empty c-list.\n");
exit(1);
}
return data_o(cl->head);
}
Defines head_clist (links are to index).
Return true if and only if the circular list cl is empty.
<c-list code>+= (U->) [<-D->]
bool empty_clist(c_list cl) {
return (cl->size == 0);
}
Defines empty_clist (links are to index).
Initialize an iteration through the circular list cl.
<c-list code>+= (U->) [<-D->]
void initi_clist(c_list cl) {
cl->cursor = cl->head;
cl->icnt = 0;
}
Defines initi_clist (links are to index).
Return a pointer to the next element of the current iteration through the
circular list cl; if the iteration is finished, return 0.
<c-list code>+= (U->) [<-D]
void * nexti_clist(c_list cl) {
if (cl->icnt == cl->size) return 0;
cl->cursor = next_b(cl->cursor);
cl->icnt++;
return data_o(prev_b(cl->cursor));
}
Defines nexti_clist (links are to index).
<c-list.c>=
#include "c-list.h"
<c-list data structures>
<c-list code>
Bit Arrays
Bit arrays are used by the NETBLT receiving side to keep track of which packets
have been received by a buffer. The implementation is standard with one
exception: the receiving side needs to know the indices of the zero bits in the
array. Zero bits represent missing packets, and the packt numbers, related to
the zero-bit indices, need to be sent back to the sender for retransmission.
init_ba() creates a bit array; free_ba() deletes a bit array. set_ba()
sets a bit in an bit array to one; clear_ba() sets a bit in a bit array to
zero; test_ba() determines the value to which a bit in a bit array is set.
onescnt_ba() returns the number of one bits in a bit array; zeros_ba()
returns a list of indices for which the bits are zero in a bit array.
size_ba() returns the capacity of a bit array in bits.
<bit array external definitions>= (U->)
typedef struct Bit_array * bit_array;
extern bit_array init_ba(u2b);
extern void
free_ba(bit_array),
set_ba(bit_array, u2b),
clear_ba(bit_array, u2b),
zeros_ba(bit_array, u2b *, u2b);
extern bool test_ba(bit_array, u2b);
extern u2b
onescnt_ba(bit_array ba),
size_ba(bit_array);
Defines bit_array (links are to index).
The bits in the bit array ba are stored in ba.bits, a vector of
ba.byte_count unsigned bytes; ba.bits has a maximum capacity of
ba.bit_count bits. The number of one bits in ba.bits is given in
ba.ones_count.
<bit-array.c>= [D->]
#include "bit-array.h"
struct Bit_array {
u2b byte_count;
u2b bit_count;
u1b * bits;
u2b ones_count;
};
Defines Bit_array (links are to index).
Create and return a new bit array containing size > 0 bits; die if a new bit
array can't be created. The extra bits in bits are set to one to simplify
zeros_ba() and onescnt_ba() (in the latter case, the number of ones
equals the total number of bits minus the number of zeros).
<bit-array.c>+= [<-D->]
bit_array init_ba(u2b size) {
bit_array ba = (bit_array) malloc(sizeof(struct Bit_array));
assert(ba);
assert(size > 0);
ba->bit_count = size;
ba->byte_count = ceiling(size, 8);
ba->ones_count = 0;
ba->bits = (u1b *) malloc(sizeof(u1b)*ba->byte_count);
assert(ba->bits);
bzero((char *) (ba->bits), ba->byte_count);
ba->bits[ba->byte_count - 1] = 0xff << (8 - (ba->byte_count*8 - size));
return ba;
}
Defines init_ba (links are to index).
Free the bit array ba.
<bit-array.c>+= [<-D->]
void free_ba(bit_array ba) {
free((char *) ba->bits);
free((char *) ba);
}
Defines free_ba (links are to index).
Return the number of bits accessable in the bit array ba. Valid bit
numbers for ba run from 0 to size_ba(ba) - 1.
<bit-array.c>+= [<-D->]
u2b size_ba(bit_array ba) {
return ba->bit_count;
}
Defines size_ba (links are to index).
Make sure bit number _bno is in range for the bit array _ba.
<bit-array.c>+= [<-D->]
#define check_bno(_ba, _bno, _w) \
do if ((0 == (_bno)) || (_ba->bit_count < (_bno))) { \
fprintf(stderr,"bit number %d in %s_ba() not in [1..%d]\n",\
(_bno), #_w, _ba->bit_count); \
exit(1); } while (false)
Defines check_bno (links are to index).
Set bit number bno in bit array ba to one.
<bit-array.c>+= [<-D->]
void set_ba(bit_array ba, u2b bno) {
check_bno(ba, bno, set);
if (!test_ba(ba, bno)) ba->ones_count++;
ba->bits[(bno - 1) >> 3] |= (1 << ((bno - 1) & 7));
}
Defines set_ba (links are to index).
Set bit number bno in bit array ba to zero.
<bit-array.c>+= [<-D->]
void clear_ba(bit_array ba, u2b bno) {
check_bno(ba, bno, clear);
if (test_ba(ba, bno)) ba->ones_count--;
ba->bits[(bno - 1) >> 3] &= ~(1 << ((bno - 1) & 7));
}
Defines clear_ba (links are to index).
Return true if bit number bno in bit array ba is one; return false if
it is zero.
<bit-array.c>+= [<-D->]
bool test_ba(bit_array ba, u2b bno) {
check_bno(ba, bno, test);
return (ba->bits[(bno - 1) >> 3] & (1 << ((bno - 1) & 7)));
}
Defines test_ba (links are to index).
Return the number of 1 bits in the bit array ba.
<bit-array.c>+= [<-D->]
u2b onescnt_ba(bit_array ba) {
return ba->ones_count;
}
Defines onescnt_ba (links are to index).
<bit-array.c>+= [<-D->]
static void put_zeros(u1b bits, u2b ** datap, u2b * sizep, u2b offset) {
u1b i;
for (i = 0; (i < 8) && (*sizep > 0); i++) {
if (!(bits & 1)) {
*(*datap++) = offset + i;
(void) (*sizep--);
}
bits >>= 1;
}
}
Defines put_zeros (links are to index).
<bit-array.c>+= [<-D]
void zeros_ba(bit_array ba, u2b * data, u2b size) {
u1b offset = 1;
u1b i;
for (i = 0; (i < ba->byte_count) && (size > 0); i++) {
if (ba->bits[i] != 0xff) put_zeros(ba->bits[i], &data, &size, offset);
offset += 8;
}
}
Defines zeros_ba (links are to index).
bit-array.h boilerplate.
<bit-array.h>=
#ifndef _bit_array_h_defined_
#define _bit_array_h_defined_
#include "defs.h"
<bit array external definitions>
#endif
Defines "bit-array.h" (links are to index).
Stop Watches
A stop watch is an object for measuring elapsed time. init_sw()
creates a new stop watch; term-sw() deletes a stop watch. Calling
start_sw() starts a stop watch running; calling stop_sw() stops a running
stop watch and returns the amount of time elapsed between the stop watch being
started and stopped.
<stop-watch external definitions>= (U->)
typedef struct stop_watch * stop_watch;
extern stop_watch init_sw(void);
extern void term_sw(stop_watch);
extern void start_sw(stop_watch);
extern microsecs stop_sw(stop_watch);
The stop watch, revealed. Also create a convinent interface to the Unix system
call gettimeofday().
<stop-watch.c>= [D->]
#include "stop-watch.h"
#include <sys/time.h>
struct stop_watch {
struct timeval start;
};
#define get_time(_t) \
if (gettimeofday(&(_t), NULL)) perror("gettimeofday() failed")
Defines get_time, stop_watch (links are to index).
Create and return a new stop watch; die if it can't be created.
<stop-watch.c>+= [<-D->]
stop_watch init_sw(void) {
stop_watch sw = (stop_watch) malloc(sizeof(struct stop_watch));
assert(sw);
return sw;
}
Defines init_sw (links are to index).
Delete the stop watch sw.
<stop-watch.c>+= [<-D->]
void term_sw(stop_watch sw) {
bzero((char *) sw, sizeof(struct stop_watch));
free((char *) sw);
}
Defines term_sw (links are to index).
Start stop watch sw running.
<stop-watch.c>+= [<-D->]
void start_sw(stop_watch sw) {
get_time(sw->start);
}
Defines start_sw (links are to index).
Stop the running stop watch sw (no check is made to see if sw actually
is running) and return the elapsed time between starting and stopping in
microseconds.
<stop-watch.c>+= [<-D]
microsecs stop_sw(stop_watch sw) {
struct timeval stp;
get_time(stp);
return ((stp.tv_sec - sw->start.tv_sec)*1000000 + stp.tv_usec) - sw->start.tv_usec;
}
Defines stop_sw (links are to index).
stop-watch.h boilerplate.
<stop-watch.h>=
#ifndef _stop_watch_h_defined_
#define _stop_watch_h_defined_
#include "defs.h"
<stop-watch external definitions>
#endif
Defines "stop-watch.h" (links are to index).
The Keep List
The keep list is a data structure for debugging heap-allocated variables. A
keep list keeps track of which locations have been allocated and freed. This
is probably a job better left to debugging malloc() routines, and I'll
probably remove this in favor of a debugging malloc().
But until then, a keep list can be initialized and freed, and storage pointers
can be added and removed from a keep list.
<keep-list.h>=
#ifndef _keep_list_h_defined_
#define _keep_list_h_defined_
#include "defs.h"
typedef struct Keep_list * keep_list;
extern keep_list init_kl(void);
extern void free_kl(keep_list);
extern void add_kl(keep_list, void *);
extern void drop_kl(keep_list, void *);
#endif
Defines "keep-list.h" (links are to index).
A keep list is a linked list of elements, each of which contains a vector of
void pointers.
<keep-list.c>= [D->]
#include "keep-list.h"
struct Keep_list {
u2b next, max;
void ** list;
};
Defines Keep_list (links are to index).
To initialize a keep list, allocate a keep-list element and then a vector of
void pointers.
<keep-list.c>+= [<-D->]
keep_list init_kl(void) {
keep_list kl = (keep_list) malloc(sizeof(struct Keep_list));
kl->next = 0;
kl->max = 16;
kl->list = (void **) malloc(sizeof(void *)*kl->max);
assert(kl->list);
return kl;
}
Defines init_kl (links are to index).
<keep-list.c>+= [<-D->]
void free_kl(keep_list kl) {
free((char *) kl->list);
free((char *) kl);
}
Defines free_kl (links are to index).
Add the void pointer e to the keep list kl.
<keep-list.c>+= [<-D->]
void add_kl(keep_list kl, void * e) {
if (kl->next >= kl->max) {
void ** nl = (void **) malloc(sizeof(void *)*kl->max*2);
assert(nl);
byte_copy((char *) nl, (const char *) kl->list, sizeof(void *)*kl->max);
kl->max *=2;
free((char *) kl->list);
kl->list = nl;
}
kl->list[kl->next++] = e;
}
Defines add_kl (links are to index).
Drop the void pointer e from the keep list kl; die if e isn't in
kl.
<keep-list.c>+= [<-D]
void drop_kl(keep_list kl, void * e) {
u1b i;
for (i = 0; i < kl->next; i++)
if (kl->list[i] == e) {
kl->list[i] = kl->list[--kl->next];
return;
}
fprintf(stderr, "keep-list drop failure.\n");
exit(1);
}
Defines drop_kl (links are to index).
The usual sorts of things you need to keep everything running smoothly.
<defs.h>=
#ifndef _defs_h_defined_
#define _defs_h_defined_
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
typedef unsigned char u1b;
typedef unsigned short u2b;
typedef unsigned long u4b;
typedef u4b buffer_number;
typedef u2b sequence_number;
typedef u2b millisecs;
typedef u2b seconds;
typedef u4b microsecs;
typedef u1b bool;
#define true 1
#define false 0
#define loop while (true)
#define byte_copy(_d, _s, _c) memcpy((char *) (_d), (const char *) (_s), (_c))
#define ceiling(_a, _b) ((_a)/(_b) + (((_a)/(_b))*(_b) == (_a) ? 0 : 1))
#define minimum(_a, _b) ((_a) < (_b) ? (_a) : (_b))
#define check(_t, _w) \
do if (_t) {perror(_w " failure"); { before_exit() ; exit(1); }} while (0)
#define set_sig(_s, _sh) \
check((sigset(_s, _sh) == SIG_ERR), "sigset(" #_s ")")
#define str2ina(_s) ntohl(inet_addr(_s))
#ifdef Debugp
# define debugp(_a) fprintf _a
#else
# define debugp(_a)
#endif
extern char * malloc(int);
extern void bzero(char *, int);
extern void free(char *), before_exit(void);
extern u2b port(int);
extern u4b skt_addr(int, char *), host_addr(char *);
extern char * ina2str(u4b);
extern int fprintf(FILE *, const char *, ...);
extern int printf(const char *, ...);
extern void perror(const char *);
extern int sscanf(const char *, const char *, ...);
extern int close(int);
typedef struct Netblt_id * netblt_id;
#endif
Defines bool, buffer_number, byte_copy, ceiling, check, debugp, false, loop, microsecs, millisecs, minimum, netblt_id, seconds, sequence_number, set_sig, true, u1b, u2b, u4b (links are to index).
And the code that goes with some of the above.
Given the Internet address ina in host-byte order, return a string
containing the dot-quad representation of ina. This representation is
valid up until the next call to ina2str().
<utility code>= (U->) [D->]
char * ina2str(u4b ina) {
struct in_addr ia;
ia.s_addr = htonl(ina);
return inet_ntoa(ia);
}
Defines ina2str (links are to index).
Given the socket skt, return the Internet address associated with skt
or zero if there's an error. If buff is non-zero, store the dot-quad
representation of skt's Internet address in the area pointed to by
buff; the contents of the area pointed to by buff are undefined in case
of error.
<utility code>+= (U->) [<-D->]
u4b skt_addr(int skt, char * buff) {
int sa = sizeof(struct sockaddr_in);
struct sockaddr_in a;
if (getsockname(skt, (struct sockaddr *) &a, &sa)) return 0;
if (buff) strcpy(buff, ina2str(a.sin_addr.s_addr));
return a.sin_addr.s_addr;
}
Defines skt_addr (links are to index).
Return an Internet address associated with the host executing host_addr()
or zero on error; the address is returned in host-byte order. If buff is
non-zero, store the dot-quad representation of the host's Internet address in
the area pointed to by buff; the contents of the area pointed to by
buff are undefined in case of error.
<utility code>+= (U->) [<-D->]
u4b host_addr(char * buff) {
u4b hst;
char name[100];
struct hostent * h;
if (get_host_name(name, sizeof(name))) return 0;
h = gethostbyname(name);
if (!h) return 0;
hst = ntohl(*((u4b *) (h->h_addr)));
if (buff) strcpy(buff, ina2str(hst));
return hst;
}
Defines host_addr (links are to index).
Return the port associated with the socket skt or zero on error.
<utility code>+= (U->) [<-D]
u2b port(int skt) {
int sa = sizeof(struct sockaddr_in);
struct sockaddr_in a;
if (getsockname(skt, (struct sockaddr *) &a, &sa)) return 0;
return ntohs(a.sin_port);
}
Defines port (links are to index).
The boilerplate. before_exit() is a routine the debugger can hook into
when an error occurs.
<utils.c>=
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "defs.h"
#include "system.h"
<utility code>
void before_exit(void) {
}
Defines before_exit (links are to index).
|