General Requirement:
Developing a C program to support half-duplex layered communication. You are provided with the interface protocols (C function prototypes) for all the layers. You need to implement these layers, including the development of peer-to-peer protocols. You are responsible for implementing the C functions in layers 2, 3, 4 and 5 (described below). You do not need to implement layer 1 code - I've included a sample layer 1 code along with a sample main program. When testing your submission we will use our own layer 1 that may be very different from the one included here - make sure your code does not depend on any specific layer 1 implementation!
Important links: sample code.
Layer 1: read/write a single byte:
Layer 1 interface protocol is specified here, but you don't need to write this layer - just use it. A sample implementation is provided that will support testing of your code - the sample layer will support half-duplex communication using a pipe.
In layer 1 APIs, each function returns an int that indicates the number of bytes read/written, in this case the return value should be 1 if it is successful, -1 indicates errors.
int l1_write(char b);
Function: Writes the byte specified by b.
Returns: 1 on success; -1 on error.
int l1_read(char *b);
Function: Reads one byte and copies the byte to the address specified by
b.
Returns: 1 on success, -1 on error.
IMPORTANT: Layer 1 does maintain ordering, so that the receiver will receive the first byte sent, before the second byte sent. You can (and should) assume that any layer 1 implementation maintains the order of individual bytes.
Layer 2: read/write a chunk
Layer 2 provides transmission/reception of a chunk. A chunk is defined as a sequence of bytes whose length is not longer than 10 bytes. Each of the bytes in a chunk can have any value, the only restriction is that there are no more than 10 bytes. It is valid to send/receive a chunk of size 0!
The important issue in layer 2 is that there must be some agreement
(protocol) between
the sender and the receiver as to what constitutes the chunk (how long
is the chunk? where is the end?). You must come up with a peer-to-peer protocol
and implement the protocol here. The only way for
l2_write to send
anything is by using l1_write, and the only way
l2_read can retrieve a byte from the sender is by calling
l1_read.
The return value of each layer 2 function indicates the length of the chunk sent or received (in bytes) upon success, or the value -1 to indicate failure. This return value should reflect the number of bytes in the original chunk, it doesn't count any extra bytes (header) your layer 2 might use as part of the peer-to-peer protocol.
IMPORTANT: The only assumption you can make about chunks is that the length is no longer than 10 bytes. The content of a chunk can be anything, the individual bytes can be anything in a chunk.int l2_write(char *chunk, int len);
Function: Sends a chunk starting at the address specified by the first parameter (
chunk) and having lengthlen.
Returns: Returnslenon success, -1 means an error occurred.
Note: You need to handle all errors that can be detected here, this means you need to check for valid values oflen, and that you check the return value each time you calll1_write.
int l2_read(char *chunk, int max);
Function: Reads a chunk and stores the incoming byes starting at the address specified by the pointer
chunk. No more thanmaxbytes will be put in to memory, somaxlimits the size of the chunk read.
Returns: If a chunk is received byl2_readthat would require more thanmaxbytes,-1 is returned(error). Upon successful reception of a chunk, the size of the chunk is returned.
Note: Make sure that yourl2_readdoes not allow the sender to overflow the bufferchunk!
Layer 3: read/write a message.
Layer 3 provides the capability to send and receive messages, where a message is defined as any sequence of bytes. There is no length limitation at layer 3, so a sender can ask layer 3 to send any size message. The general idea is that your layer 3 implementation needs to use layer 2 to send and receive small chunks, perhaps many times in order to transmit/receive a complete message.
The main issue at layer 3 is creation of a peer-to-peer protocol that uses
chunks to communicate, and the receiver needs to know when it has reached the
end of a message. Your
l3_write can only call
l2_write to send data (not
l1_write), and your
l3_read can only call
l2_read.
int l3_write(char *msg, int len);
Function: Sends a message of length
len. The bytes sent are specified by the pointermsg.
Returns: The number of bytes of sent on success (should be len), -1 means an error.
int l3_read(char *msg, int max);
Function: Reads a message and stores in memory starting at the address specified by the pointer
msg. No more thanmaxbytes will be put in to memory, somaxmust limit the size of the message read no matter how large the message was sent by l3_write.
Returns: Return value is the size of the message received or -1 on error. If a message is received byl3_readthat would require more thanmaxbytes,l3_readshould return -1 (error).
Layer 4: read/write a message with error detection
Layer 4 adds simple error detection to the services provided by layer 3. The function prototypes (which define the interface protocols) looks the same as the layer 3 prototypes, the only difference is that the layer 4 read function should return a -1 (error) if it detects an error in the received message. The errors we are looking for here involve transmission errors, we want to try to make sure that the message received is the same as the message that was sent.
Error Detection: The simplest approach is to use a checksum. To use a checksum you simply add together all the bytes of the original message (just treat each byte as a number) and the checksum is the sum modulo 256 (to fit in a single byte). The result is a single byte that can be sent along with the message data, and the receiving end can go through the same steps (computing the checksum) and then compare the received checksum to the computed checksum. If they don't agree - there was an error. If the checksums do agree, it is likely there was no error (but it's not a certainty). Many network protocols use checksums to detect errors, although in general they use checksums larger than a single byte to improve the accuracy of the error detection. Feel free to use whatever you want to detect errors, a single byte checksum is just the easiest to implement. When testing your code we will use a buggy layer 1 that does introduce errors, so make sure your error detection works!
int l4_write(char *msg, int len);
Function: Sends a message of length
len. The bytes sent are specified by the pointermsg.
Returns: Returns the number of bytes of the message sent on success (should be len), -1 means an error.
int l4_read(char *msg, int max);
Function:Reads a message in to memory starting at the address specified by the pointermsg. No more thanmaxbytes will be put in to memory, somaxmust limit the size of the message read.
Returns: If a message is received byl4_readthat would require more thanmaxbytes,l4_readshould return -1 (error). Some error detection mechanism is used to detect transmission errors. If such an error is detected byl4_read, a -1 will be returned. Upon successful reception of a message, the size of the message (the number of bytes stored inmsg) is returned.
Layer 5: read/write a named value
Layer 5 will provide higher layers with a mechanism for sending and receiving values that have an associated name. The idea is that we could build an application-level protocol that uses layer 5 to send named values between peer processes. For example, a telephone directory service could be built by having clients send a request that could be either a person's name, or phone number; the server could do the appropriate lookup and send back the appropriate result (as a named value).
Your layer 5 implementation should use layer 4 for sending and receiving
messages (nothing else!). Once again, you need to come up with a peer-to-peer
protocol so that your layer 5 functions know what to expect and can work
together. It doesn't matter what your peer-to-peer protocol is, as long as your
l5_write works with your
l5_read.
int l5_write(char *name, int namelen, char *value, int
valuelen);
Function:Sends the named value to the receiver.
Returns: Returns a 1 on success, -1 means an error.
int l5_read(char *name, int *namelenptr, char *value, int
*valuelenptr);
Function: l5_readusesl4_readto read a named value and on success returns the value 1, and puts the name of the received value atnameand the value atvalue. The pointersnamelenptrandvaluelenptrare pointers to the sizes of the two buffers whenl5_readis initially called, you must use these values to make sure you don't overflow the buffers.
Returns: Upon successful return,*namelenptrshould indicate the number of bytes in thenamereceived and*valuelenptrshould indicate the number of bytes in thevaluereceived.l5_readmust return a -1 on error.
Sample Code and Testing
The program
hw2test.c can be used to test your layer 5 code (which
should use your layer 4 code, which, in turn, should use your layer 3 code, ... ).
IMPORTANT: Although the sample code only accesses the layer 5 functions directly, it is necessary that your layers are independent and can work with any other implementation of other layers. For example, we might use a different layer 2 implementation with your layer 3 and 4 code - everything should work correctly! Also keep in mind that when we test your code we will generate error conditions (including introducing some simulated transmission errors by providing a buggy layer 1).
The test code might be used as part of an application that uses layer 5 to send and receive a name/value pair where both the name and value are C strings (null terminated sequences of bytes). Don't assume that layer 5 will always be used this way, your layer 5 should not care whether or not there is a terminating null!
The main program calls layer 5 to send or receive these messages depending on
whether it finds command line arguments present. If there are 2 command line
arguments the program assumes it is a sender and sends
argv[1] as a name and
argv[2] as the value. If no
command line arguments are detected, the program tries to read a name value pair
using l5_read. If everything works, the reader program will print
the name and value to STDOUT. Using the sample layer 5 code, you can
run the program as a sender and pipe the output of the program to another copy
of the program running as a receiver:
> ./hw2test phone 2766722 | ./hw2test Name: phone Value: 2766722 |
There is also a test program that can be used to test layer 3 directly (and
layer 2 indirectly). This might give you more ideas about how to test each layer
(don't rely on the hw2test.c program alone!). Both of the test programs and a
sample Makefile are available here: sample
code.
Deliverables
You must include all functions you implement for each layer in a single C program.
Since we have 4 layers (layer 2 to 5), you need to write 4 C files.
The layer 2 code must be in a file named
l2.c, the layer 3 code in
l3.c, and so on. Note that each of these files should have both a
read and write function (l2.c should have
l2_read()
and l2_write(), ...).
You must also include in your submission a file named
README
that includes your name and a brief description of your submission, including
the name of each file submitted along with a one line description of what is in
the file. You should also mention what Operating System you used to develop the
code, and any special instructions for building your code, problems you had or
anything else you think might be helpful to us. You do not need to provide any
code for layer1 or a main program, we will supply our own when testing your
code. To further demonstrate how two layers communicate through its associated interface protocol,
a sample layer 2 code is given in the sample
code. You can submit this sample code with some of your comments or change this sample code with your own implementation.
Grading
Your project will be tested to make sure it works properly - a large part of this testing will make sure that your functions generate errors (return -1) when appropriate. Here are the specifics of the grading:
|
Layer 2 |
20% (sample code is provided) |
|---|---|
|
Layer 3 |
20% |
|
Layer 4 |
20% |
|
Layer 5 |
20% |
|
Style/Code structure, etc. |
20% |
NOTE: 20% of your homework grade depends on the how well we can understand your code. For this assignment, it would be hard to create crummy/sloppy code (since you are forced to provide 8 individual functions). Feel free to write more than the 8 required functions, but keep in mind that we will test each of your layers in isolation (so each file needs to be a complete implementation of a layer).
The tests for each of your layers will be run independently, so errors in your layer 2 code will not propagate to your layer 3 code...
Submitting your files
All projects must be submitted via handin command on any unix machine in the department as follows:
> handin.360.2 2 l2.h l2.c l3.h l3.c l4.h l4.c l5.h l5.c makefile readme
Please make sure you submit all your source files not the object files. You can also zip the files using the following command first:
> tar cvzf hw2.tgz l2.h l2.c l3.h l3.c l4.h l4.c l5.h l5.c makefile readme
and submit the single zip file hw2.tgz. Before you submit this single file, please use the following command to check make sure you have everything in this zip file:
> tar tvzf hw2.tgz
Don't send compiled code!
Error report
If you find any error in this assignment, please send me an email at jzwang@cs.clemson.edu. A correct error report will be rewarded with 1 bonus point.