please read the attachments in the zipped file:
The word doc is where the requirements are, the rest are the resources.
BUMP implementation in Java x
The project is to implement the BUMP client in java, with
window size 1. Here is an
overview of the three WUMP protocols
(BUMP, HUMP, and CHUMP). Here are the files
wumppkt.java
, containing the packet format classes, and
wclient.java
, which contains an outline of the actual program. Only the latter file should be modified; you should
not have to make changes to wumppkt.java.
What you are to do is the following, by modifying and extending the wclient.java outline file:
· Implement the basic transfer
· Add all appropriate packet sanity checks: timeouts, host/port, size, opcode, and block number
· Generate output. The transferred file is to be written to System.out. A status message about every packet (listing size and block number) is to be written to System.err. Do
not confuse these!
· Terminate after a packet of size less than 512 is received
· Implement an appropriate “dallying” strategy
· send an ERROR packet if it receives a packet from the wrong port. The appropriate ERRCODE in this case is EBADPORT.
An outline of the program main loop is attached
recommended that you implement this in phases, as follows.
1. Latch on to the new port: save the port number from Data[1], and make sure all ACKs get sent to this port. This will mean that the transfer completes. You should also make sure the client stops when a packet with less than 512 bytes of data is received. Unless you properly record the source port for Data[1], you have no place to which to send ACK[1]!
2. For each data packet received, write the data to System.out. All status messages should go to System.err, so the two data streams are separate if
stdout is redirected. To write to System.out, use System.out.write:
System.out.write(byte[] buf, int offset, int length);
For your program, offset will be 0, buf will typically be dpacket.data(), where dpacket is of type DATA (wumppkt.DATA). The length will be dpacket.size() – wumppkt.DHEADERSIZE (or, equivalently, dg.getLength() – wumppkt.DHEADERSIZE, where dg is a DatagramPacket object).
3. Add sanity checks, for (in order) host/port, packet size, opcode, and block number.
4. Handle timeouts, by retransmitting the most recently sent packet when the elapsed time exceeds a certain amount (4 seconds?). One way to do this is to keep a DatagramPacket variable LastSent, which can either be reqDG or ackDG, and just resend LastSent. Note that the response to an InterruptedIOException, a “true” timeout, will simply be to continue the loop again.
5. Add support for an dallying and error packets. After the client has received the file, dallying means to wait 2.0 – 3.0 timeout intervals (or more) to see if the final data packet is retransmitted. If it is, it means that the final ACK was lost. The dally period gives the client an opportunity to resend the final ACK. Error packets are to be sent to any sender of an apparent data packet that comes from the wrong port.
vanilla Normal transfer
lose Lose everything after the first windowful (min 3). It will be retransmitted when you retransmit the previous ACK.
spray Constant barrage of data[1]. Implies LOSE too. In this case, no timeout events will occur; you
must check for elapsed time.
delay Delays sending packet 1, prompting a duplicate REQ and thus results in multiple server instances on multiple ports.
reorder Sends the first windowful in the wrong order. You’re probably not implementing windows.
dupdata2
DATA[2] is sent twice
losedata2
DATA[2] is lost on initial send, until you resend ACK[1]
marspacket
A packet from the wrong port (a “martian” port) arrives
badopcode
a packet arrives with an incorrect opcode
nofile
you get an error packet with a NO FILE error code.
At this point, the only ones that work on port 4716 are
vanilla,
lose2 (losedata2) and
dup2 (dupdata2).
wumppkt.java
wumppkt.java
// The following implements the packet formats for BUMP and HUMP.
// Each individual packet type derives from class BASE, containing
// protocol and opcode fields only; we don’t really use this inheritance
// hierarchy though.
// packets can be constructed with the applicable constructors;
// each ctor requires parameters for the necessary fields.
// when possible, there is a “convenience” ctor setting proto = BUMPPROTO.
// The “raw” packet format, as sent and received via DatagramSocket,
// is byte[]. Packets (at least those that one might *receive*)
// can be constructed from a byte[]. For DATA packets, we also need
// to specify the length of the packet, not necessarily the same as
// the length of the byte[] buffer.
// All packet classes also have a write() member function that
// writes out the packet fields into a byte[], for sending.
//import java.lang.*; //pld
//import java.net.*; //pld
//import java.lang.System.*;
import
java
.
io
.
*
;
public
class
wumppkt
{
public
static
final
short
BUMPPROTO
=
1
;
public
static
final
short
HUMPPROTO
=
2
;
public
static
final
short
CHUMPPROTO
=
3
;
public
static
final
short
REQop
=
1
;
public
static
final
short
DATAop
=
2
;
public
static
final
short
ACKop
=
3
;
public
static
final
short
ERRORop
=
4
;
public
static
final
short
HANDOFFop
=
5
;
public
static
final
short
SERVERPORT
=
4715
;
public
static
final
short
SAMEPORT
=
4716
;
public
static
final
int
INITTIMEOUT
=
3000
;
// milliseconds
public
static
final
int
SHORTSIZE
=
2
;
// in bytes
public
static
final
int
INTSIZE
=
4
;
public
static
final
int
BASESIZE
=
2
;
public
static
final
int
MAXDATASIZE
=
512
;
public
static
final
int
DHEADERSIZE
=
BASESIZE
+
SHORTSIZE
+
INTSIZE
;
// DATA header size
public
static
final
int
MAXSIZE
=
DHEADERSIZE
+
MAXDATASIZE
;
public
static
final
int
EBADPORT
=
1
;
/* packet from wrong port */
public
static
final
int
EBADPROTO
=
2
;
/* unknown protocol */
public
static
final
int
EBADOPCODE
=
3
;
/* unknown opcode */
public
static
final
int
ENOFILE
=
4
;
/* REQ for nonexistent file */
public
static
final
int
ENOPERM
=
5
;
/* REQ for file with wrong permissions */
static
int
proto
(
byte
[]
buf
)
{
return
buf
[
0
];
}
static
int
opcode
(
byte
[]
buf
)
{
return
buf
[
1
];
}
public
static
void
w_assert
(
boolean
cond
,
String
s
)
{
if
(
cond
)
return
;
System
.
err
.
println
(
“assertion failed: ”
+
s
);
java
.
lang
.
System
.
exit
(
1
);
}
//************************************************************************
public
class
BASE
{
//implements Externalizable {
// don’t construct these unless the buffer has length >=4!
// the data:
private
byte
_proto
;
private
byte
_opcode
;
//———————————
public
BASE
(
int
proto
,
int
opcode
)
{
//super(); // call to base ctor
_proto
=
(
byte
)
proto
;
_opcode
=
(
byte
)
opcode
;
}
public
BASE
(
byte
[]
buf
)
{
// constructs pkt out of packetbuf
}
public
BASE
()
{}
// packet ctors do all the work!
public
byte
[]
write
()
{
// not used
return
null
;
}
public
int
size
()
{
return
BASESIZE
;
}
public
int
proto
()
{
return
_proto
;}
public
int
opcode
()
{
return
_opcode
;}
}
//*******************
public
class
REQ
extends
BASE
{
private
short
_winsize
;
private
String
_filename
;
//———————————
public
REQ
(
int
proto
,
int
winsize
,
String
filename
)
{
super
(
proto
,
REQop
);
_winsize
=
(
short
)
winsize
;
_filename
=
filename
;
}
public
REQ
(
int
winsize
,
String
filename
)
{
this
(
BUMPPROTO
,
winsize
,
filename
);
}
public
REQ
(
byte
[]
buf
)
{
// not complete but not needed
//super(BUMPPROTO, REQop);
}
public
byte
[]
write
()
{
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
(
size
());
DataOutputStream
dos
=
new
DataOutputStream
(
baos
);
try
{
//writeExternal(dos);
dos
.
writeByte
(
super
.
proto
());
dos
.
writeByte
(
super
.
opcode
());
dos
.
writeShort
(
_winsize
);
dos
.
writeBytes
(
_filename
);
dos
.
writeByte
(
0
);
return
baos
.
toByteArray
();
}
catch
(
IOException
ioe
)
{
System
.
err
.
println
(
“BASE packet output conversion failed”
);
return
null
;
}
//return null;
}
public
int
size
()
{
return
super
.
size
()
+
SHORTSIZE
+
_filename
.
length
()
+
1
;
}
public
String
filename
()
{
return
_filename
;}
}
//*******************
public
class
ACK
extends
BASE
{
private
int
_blocknum
;
//———————————
public
ACK
(
int
blocknum
)
{
this
(
BUMPPROTO
,
blocknum
);
}
public
ACK
(
short
proto
,
int
blocknum
)
{
super
(
proto
,
ACKop
);
_blocknum
=
blocknum
;
}
public
int
blocknum
()
{
return
_blocknum
;}
public
void
setblock
(
int
blocknum
)
{
_blocknum
=
blocknum
;}
public
byte
[]
write
()
{
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
(
size
());
DataOutputStream
dos
=
new
DataOutputStream
(
baos
);
try
{
//writeExternal(dos);
dos
.
writeByte
(
super
.
proto
());
dos
.
writeByte
(
super
.
opcode
());
dos
.
writeShort
(
0
);
// padding
dos
.
writeInt
(
_blocknum
);
return
baos
.
toByteArray
();
}
catch
(
IOException
ioe
)
{
System
.
err
.
println
(
“ACK packet output conversion failed”
);
return
null
;
}
}
public
int
size
()
{
return
super
.
size
()
+
SHORTSIZE
+
INTSIZE
;
}
public
ACK
(
byte
[]
buf
)
{}
// not complete but not needed
}
//*******************
public
class
DATA
extends
BASE
{
private
int
_blocknum
;
private
byte
[]
_data
;
//———————————
public
DATA
(
int
proto
,
int
blocknum
,
byte
[]
data
)
{
super
(
proto
,
DATAop
);
_blocknum
=
blocknum
;
_data
=
data
;
}
public
DATA
(
int
proto
,
int
blocknum
,
byte
[]
data
,
int
len
)
{
super
(
proto
,
DATAop
);
_blocknum
=
blocknum
;
_data
=
data
;
}
public
DATA
(
byte
[]
buf
,
int
bufsize
)
{
this
(
BUMPPROTO
,
buf
,
bufsize
);
}
// for building a DATA out of incoming buffer:
public
DATA
(
int
proto
,
byte
[]
buf
,
int
bufsize
)
{
super
(
proto
,
DATAop
);
ByteArrayInputStream
bais
=
new
ByteArrayInputStream
(
buf
,
0
,
bufsize
);
DataInputStream
dis
=
new
DataInputStream
(
bais
);
try
{
int
p
=
dis
.
readByte
();
w_assert
(
p
==
proto
,
“Expecting proto ”
+
proto
+
“, got ”
+
p
);
int
o
=
dis
.
readByte
();
w_assert
(
o
==
DATAop
,
“Expecting opcode=DATA, got ”
+
o
);
int
pad
=
dis
.
readShort
();
_blocknum
=
(
dis
.
readInt
());
_data
=
new
byte
[
dis
.
available
()];
dis
.
read
(
_data
);
}
catch
(
IOException
ioe
)
{
System
.
err
.
println
(
“DATA packet conversion failed”
);
return
;
}
}
public
DATA
(
int
proto
)
{
// for creating “empty” DATA objects
super
(
proto
,
DATAop
);
_blocknum
=
0
;
_data
=
new
byte
[
MAXDATASIZE
];
}
public
DATA
()
{
this
(
BUMPPROTO
);
}
public
int
blocknum
()
{
return
_blocknum
;}
public
byte
[]
data
()
{
return
_data
;}
public
byte
[]
write
()
{
// not complete but not needed
return
null
;
}
public
int
size
()
{
return
super
.
size
()
+
SHORTSIZE
+
INTSIZE
+
_data
.
length
;
}
}
//*******************
public
class
ERROR
extends
BASE
{
private
short
_errcode
;
//———————————
public
ERROR
(
short
proto
,
short
errcode
)
{
super
(
proto
,
ERRORop
);
_errcode
=
errcode
;
}
public
short
errcode
()
{
return
_errcode
;}
public
byte
[]
write
()
{
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
(
size
());
DataOutputStream
dos
=
new
DataOutputStream
(
baos
);
try
{
//writeExternal(dos);
dos
.
writeByte
(
super
.
proto
());
dos
.
writeByte
(
super
.
opcode
());
dos
.
writeShort
(
_errcode
);
return
baos
.
toByteArray
();
}
catch
(
IOException
ioe
)
{
System
.
err
.
println
(
“ERROR packet output conversion failed”
);
return
null
;
}
}
public
ERROR
(
byte
[]
buf
)
{
this
(
BUMPPROTO
,
buf
);}
public
ERROR
(
int
proto
,
byte
[]
buf
)
{
super
(
proto
,
DATAop
);
int
opcode
=
wumppkt
.
this
.
opcode
(
buf
);
w_assert
(
opcode
==
ERRORop
,
“Expecting opcode=ERROR, got ”
+
opcode
);
w_assert
(
proto
==
wumppkt
.
this
.
proto
(
buf
),
“Expecting proto=”
+
proto
);
w_assert
(
buf
.
length
>=
BASESIZE
+
SHORTSIZE
,
“bad ERROR pkt size of ”
+
buf
.
length
);
ByteArrayInputStream
bais
=
new
ByteArrayInputStream
(
buf
,
0
,
buf
.
length
);
DataInputStream
dis
=
new
DataInputStream
(
bais
);
try
{
int
p
=
dis
.
readByte
();
int
o
=
dis
.
readByte
();
_errcode
=
dis
.
readShort
();
}
catch
(
IOException
ioe
)
{
System
.
err
.
println
(
“ERROR packet conversion failed”
);
return
;
}
};
public
int
size
()
{
return
super
.
size
()
+
SHORTSIZE
;}
}
}
wclient.java
wclient.java
/*
WUMP (specifically BUMP) in java. starter file
*/
import
java
.
lang
.
*
;
//pld
import
java
.
net
.
*
;
//pld
import
java
.
io
.
*
;
//import wumppkt; // be sure wumppkt.java is in your current directory
//import java.io.Externalizable;
public
class
wclient
{
static
wumppkt wp
=
new
wumppkt
();
// stupid inner-class nonsense
//============================================================
//============================================================
static
public
void
main
(
String
args
[])
{
int
destport
=
wumppkt
.
SERVERPORT
;
destport
=
wumppkt
.
SAMEPORT
;
// 4716; server responds from same port
String
filename
=
“vanilla”
;
String
desthost
=
“ulam2.cs.luc.edu”
;
int
winsize
=
1
;
if
(
args
.
length
>
0
)
filename
=
args
[
0
];
if
(
args
.
length
>
1
)
winsize
=
Integer
.
parseInt
(
args
[
1
]);
if
(
args
.
length
>
2
)
desthost
=
args
[
2
];
DatagramSocket
s
;
try
{
s
=
new
DatagramSocket
();
}
catch
(
SocketException
se
)
{
System
.
err
.
println
(
“no socket available”
);
return
;
}
try
{
s
.
setSoTimeout
(
wumppkt
.
INITTIMEOUT
);
// time in milliseconds
}
catch
(
SocketException
se
)
{
System
.
err
.
println
(
“socket exception: timeout not set!”
);
}
if
(
args
.
length
!=
2
)
{
System
.
err
.
println
(
“usage: wclient filename [winsize [hostname]]”
);
//exit(1);
}
// DNS lookup
InetAddress
dest
;
System
.
err
.
print
(
“Looking up address of ”
+
desthost
+
“…”
);
try
{
dest
=
InetAddress
.
getByName
(
desthost
);
}
catch
(
UnknownHostException
uhe
)
{
System
.
err
.
println
(
“unknown host: ”
+
desthost
);
return
;
}
System
.
err
.
println
(
” got it!”
);
// build REQ & send it
wumppkt
.
REQ req
=
wp
.
new
REQ
(
wumppkt
.
BUMPPROTO
,
winsize
,
filename
);
// ctor for REQ
System
.
err
.
println
(
“req size = ”
+
req
.
size
()
+
“, filename=”
+
req
.
filename
());
DatagramPacket
reqDG
=
new
DatagramPacket
(
req
.
write
(),
req
.
size
(),
dest
,
destport
);
try
{
s
.
send
(
reqDG
);}
catch
(
IOException
ioe
)
{
System
.
err
.
println
(
“send() failed”
);
return
;
}
//============================================================
// now receive the response
DatagramPacket
replyDG
// we don’t set the address here!
=
new
DatagramPacket
(
new
byte
[
wumppkt
.
MAXSIZE
]
,
wumppkt
.
MAXSIZE
);
DatagramPacket
ackDG
=
new
DatagramPacket
(
new
byte
[
0
],
0
);
ackDG
.
setAddress
(
dest
);
ackDG
.
setPort
(
destport
);
// this is wrong for wumppkt.SERVERPORT version
int
expected_block
=
1
;
long
starttime
=
System
.
currentTimeMillis
();
long
sendtime
=
starttime
;
wumppkt
.
DATA data
=
wp
.
new
DATA
();
wumppkt
.
ACK ack
=
wp
.
new
ACK
(
0
);
int
proto
;
// for proto of incoming packets
int
opcode
;
int
length
;
//============================================================
while
(
true
)
{
// get packet
try
{
s
.
receive
(
replyDG
);
}
catch
(
SocketTimeoutException
ste
)
{
System
.
err
.
println
(
“hard timeout”
);
// what do you do here??
continue
;
}
catch
(
IOException
ioe
)
{
System
.
err
.
println
(
“receive() failed”
);
return
;
}
byte
[]
replybuf
=
replyDG
.
getData
();
proto
=
wumppkt
.
proto
(
replybuf
);
opcode
=
wumppkt
.
opcode
(
replybuf
);
length
=
replyDG
.
getLength
();
/* The new packet might not actually be a DATA packet.
* But we can still build one and see, provided:
* 1. proto = wumppkt.BUMPPROTO
* 2. opcode = wumppkt.DATAop
* 3. length >= wumppkt.DHEADERSIZE
*/
if
(
proto
==
wumppkt
.
BUMPPROTO
&&
opcode
==
wumppkt
.
DATAop
&&
length
>=
wumppkt
.
DHEADERSIZE
)
{
data
=
wp
.
new
DATA
(
replyDG
.
getData
(),
length
);
}
else
{
data
=
null
;
}
// the following seven items we can print always
System
.
err
.
print
(
“rec’d packet: len=”
+
length
);
System
.
err
.
print
(
“; proto=”
+
proto
);
System
.
err
.
print
(
“; opcode=”
+
opcode
);
System
.
err
.
print
(
“; src=(”
+
replyDG
.
getAddress
().
getHostAddress
()
+
“/”
+
replyDG
.
getPort
()
+
“)”
);
System
.
err
.
print
(
“; time=”
+
(
System
.
currentTimeMillis
()
–
starttime
));
System
.
err
.
println
();
if
(
data
==
null
)
System
.
err
.
println
(
” packet does not seem to be a data packet”
);
else
{
System
.
err
.
println
(
” DATA packet blocknum = ”
+
data
.
blocknum
());
System
.
out
.
write
(
data
.
data
(),
0
,
data
.
size
()
–
wumppkt
.
DHEADERSIZE
);
}
// The following is for you to do:
// check port, packet size, type, block, etc
// latch on to port, if block == 1
// send ack
ack
=
wp
.
new
ACK
(
wumppkt
.
BUMPPROTO
,
expected_block
);
ackDG
.
setData
(
ack
.
write
());
ackDG
.
setLength
(
ack
.
size
());
try
{
s
.
send
(
ackDG
);}
catch
(
IOException
ioe
)
{
System
.
err
.
println
(
“send() failed”
);
return
;
}
sendtime
=
System
.
currentTimeMillis
();
// if it passes all the checks:
//write data, increment expected_block
// exit if data size is < 512
}
// while
}
}
wump_client_outline.html
Outline of the wump client program main loop.
There are three states:
UNLATCHED, until we get a good DATA[1] from the new port
LATCHED, once we've latched on to the new port
DALLY, after we've received the final data packet
Here's a first pass at a pseudocode outline of the main body of the
program, as presented on June 6; while it is pseudocode, note that while(true) and continue are legitimate java. Note how the use of continue makes elses unnecessary.
There are three main problems here: dally() is unspecified, the
transition from UNLATCHED to LATCHED is unclear (and the program does
not implement it correctly, clear or not), and the timeout-event
handling is, well, incomplete. On June 6 we did discuss the transition
to LATCHED.
while (true) {
replyDG = s.receive() // possibly a timeout
if TIMEOUT:
retransmit previous packet (ACK or REQ)
continue
wrong IP addr:
continue
wrong port:
// really separate from wrong
IP-addr
send error packet
continue
wrong length:
// can't even check for DATA opcode if there aren't enough bytes!
continue
not DATA
continue
create DATA packet out of replyDG
wrong blocknum:
continue
// now we have a good packet!
write data
expected_block ++;
send ACK to destport
if (size < 512) {
dally(); // to
be discussed
break; // done
}
}
The first fix is to clarify when we become LATCHED. This occurs when we
recognize a valid DATA[1]; at that time we set expected_block >1 so
we can use this fact (expected_block > 1) as our “flag”. However, we
should check for LATCHED early in the game, and we don’t find out about
valid DATA[1] until late in the loop above. Additions are in green; in most cases we will become LATCHED during the first run through the main while loop.
while (true) {
replyDG = s.receive() // possibly a timeout
if TIMEOUT:
retransmit previous packet (ACK or REQ)
continue
wrong IP addr:
continue
wrong port:
// really separate from wrong
IP-addr
only check port if expected_block == 1
send error packet
continue
wrong length:
// can’t even check for DATA opcode if there aren’t enough bytes!
continue
not DATA
continue
create DATA packet out of replyDG
wrong blocknum:
continue
// now we have a good packet!
write data
if
expected_block == 1, set destport = replyDG.getport()
expected_block ++;
send ACK to destport
if (size < 512) {
dally(); // to
be discussed
break; // done
}
}
Now on to the timeout issue. There are two different uses of the term here. Suppose the timeout period is, say, 3000 ms.
A SocketTimeoutException, meaning that the socket received nothing at all for 3000 ms. Let's call this a "hard timeout".
It's time to resend. We are expecting Data[N] and haven't
received it for at least 3000 ms (though we may have received other
things). Let's call this a "soft timeout".
It should be clear that a hard timeout does imply a soft timeout: if
you've received nothing, then you certainly haven't received the packet
you were waiting for. However, the converse is not true!
It is possible for you to receive a steady stream of "noise" packets,
that serve to prevent a hard timeout from ever occuring, but because
none of them is the correct packet you still have to have a soft
timeout.
The only way to resolve this is to check for the elapsed time. The
current time is always available in System.currentTimeMillis(); you
will save that value each time you send a packet:
send_time = System.currentTimeMillis();
Now, the first solution to the soft-timeout problem is to check
someplace before any "continue" statements that the elapsed time has
not been exceeded. That works, but it turns out that a better solution
is to notice that if you check elapsed time for soft timeouts, you no
longer really need to do anything for hard timeouts (except restart the
loop). The hard-timeout interval becomes the clock granularity, in
effect: if the soft-timeout interval is 3000 ms and the hard-timeout
interval is 1000 ms, then in the worst case you wait until 3000+1000 =
4000 ms before actually noticing and responding to the soft timeout.
Once you have an elapsed-time check, it makes sense to shorten the
hard-timeout interval to something very small; 1000 ms or even 250 ms.
On every "hard" timeout you check the elapsed time for a "soft"
timeout. At this point, a (short) hard timeout no longer implies a
(long) soft timeout.
Note that, although the elapsed-time check is at the beginning of the
loop here, it's never executed immediately after receiving a valid
packet because after receiving a valid packet we always update
send_time, and the elapsed-time check will then fail until we've had at
least once attempt at s.receive(). In other words, you don't need a
flag or any special logic to prevent checking the elapsed time
immediately after sending: it's harmless then.
send REQ
send_time = System.currentTimeMilllis();
while (true) {
check elapsed time: if exceeded (ie if a soft timeout),
resend whatever was sent most recently
send_time = System.currentTimeMilllis();
replyDG = s.receive() // possibly a timeout
if HARD_TIMEOUT:
do nothing!
continue
wrong IP addr:
continue
wrong port:
// really separate from wrong
IP-addr
send error packet
continue
wrong length:
// can't even check for DATA opcode if there aren't enough bytes!
continue
not DATA
continue
create DATA packet out of replyDG
wrong blocknum:
continue
// now we have a good packet!
write data
expected_block ++;
send ACK to destport
send_time = System.currentTimeMilllis();
if (size < 512) {
dally(); // to
be discussed
break; // done
}
}