Spring 2022 IS496 Programming Assignment 2 - File Transfer Protocol (FTP)
Total Points: 100 points
Goal: Program a Prototype of a FTP application using TCP
Assigned: February 21, 2022
Due: March 7, 2022 March 9, 2022 by the end of day (11:59 pm CST). Grouping: To be completed by a group. Note: This instruction is based on C/C++. The Python version is available at here.
Background
In this programming assignment, you will implement the client and server sides of a simple File Transfer Protocol (FTP) application. The file transfer itself will take place using TCP with the client determining the operation to be performed (creating a file, deleting a file, creating a new directory, removing a directory, changing to a different directory, obtaining a directory listing, or closing the connection). The server should respond appropriately to the specific command. The specifics of the protocol are detailed in this document. Note: please refer to appendix A for the port number assigned to you (the same as PG1).
Problem:
Part 1: TCP Practice
In the first part of this assignment, you are asked to build a simple TCP server and client where the client can successfully 1) establish the connection with the server, and 2) send a string (e.g., "Hello World") to the client. The finished code for Part 1 can also be used for Part 2 of this assignment.
We have provided an optional starter skeleton code to get you started on the right track.
The starter code files (i.e., tcpserver.c and tcpclient.c) can be downloaded from here.
Part 2: Simple File Transfer Protocol
Server opens the port, and goes into "wait for connection" state.
Client connects to the server on the appropriate port.
Server goes into "wait for operation from client" state and waits for operation command from the client.
Client goes into "prompt user for operation" state and prompts user for operation.
DN: Note: Please test DN with the provided test file (i.e., TestFile.txt) discussed in the general notes.
Client sends operation (DN) to download a file from the server.
Client sends the length of the filename (short int) followed by the filename (character string).
Server decodes what it receives, and checks to see if the file exists in its local directory.
If the file exists, server returns the size of the file to the client as a 32-bit integer.
If the file does not exist, server will return a negative confirmation (32-bit integer value -1). After that, the server should return to the "wait for operation from client" state.
Client receives the 32-bit file length from server.
Client should decode the 32-bit value.
If the value is -1, user should be informed that the file does not exist on the server. Client should return to "prompt user for operation" state.
If the value is not -1, save the value as the file size for later use.
Server sends the file to client.
Client reads "file size" bytes from server.
The client saves the file to disk as "filename".
Inform user that the transfer was successful, and return to "prompt user for operation" state. The client displays throughput results for the transfer.
UP:
Client sends operation (UP) to upload a local file to a server.
Client sends the length of the filename which will be sent (short int) followed by the filename (character string). Note: you can create a local file called "upload.txt" with random strings to test the UP operation.
Server receives the above information, decodes filename size and filename, and acknowledges that it is ready to receive (it is up to you how to do the acknowledgment).
Client replies with a 32-bit value which is the size of the file (in bytes).
Server receives and decodes the 32-bit value.
Server enters the loop to receive file
Client sends file to server. Server reads "file size" bytes from client and saves them to disk as "filename".
Server computes throughput results for the transfer and sends it to the client.
Server sends the throughput results to the client. The client displays the throughput of the transfer.
RM:
Client sends operation (RM) to delete a file from the server. Note: you can create a test file called "delete.txt" in the server directory to test the RM operation.
Client sends the length of the filename which will be sent (short int) followed by the filename (character string).
Server receives the above information, decodes filename size and filename, and checks if the file to be deleted exists or not.
If the file exists, the server sends a positive confirmation (integer value 1) back to the client.
If the file does not exit, the server sends a negative confirmation (integer value -1) back to the
client.
Client receives the confirmation from the server.
If the confirmation is negative, it should inform the user that the file does not exist on the server and return to "prompt user for operation" state.
If the confirmation is positive, the client further confirms if the user wants to delete
the file: "Yes" to delete, "No" to ignore. The client then sends the user's confirmation (i.e., "Yes" or
"No") back to the server.
If the user's confirmation is "Yes", the client waits for the server to send the confirmation of file
deletion.
If the user's confirmation is "No", the client prints out "Delete abandoned by the user!" and
returns to "prompt user for operation" state.
The server waits for the delete confirmation sent by the client.
If the confirmation is "Yes", the server deletes the requested file and returns an acknowledgment to the client to indicate the success or failure of file deletion operation.
If the confirmation is "No", the server returns to "wait for operation from client" state.
LS:
Client sends operation (LS) to list the directory at the server.
Server obtains listing of it's directory, including both the permission settings and the name of each file (e.g., "-rwxr-xr-x 1 netid dip 21K Aug 22 12:58 test.txt" or simply "-rwxr-xr-x test.txt"
)
Server computes the size of directory listing and sends the size to client as a 32-bit integer.
Client receives the size, and goes into a loop to read directory listing.
Once the listing is received, client displays the listing to user.
Client and server return to "prompt user for operation" and "wait for operation from client" state respectively.
MKDIR:
Client sends operation (MKDIR) to make a directory on the server.
Client sends the length of the directory name which will be sent (short int) followed by the directory name (character string).
Server receives the above information, decodes directory name size and directory name, and checks if the directory to be created exists or not.
If the directory exists, the server sends a negative confirmation (integer value -2) back to the client.
If the directory does not exit, the server sends a positive confirmation (integer value 1) back to the client if the directory is successfully created; otherwise, the server sends a negative confirmation (integer value -1) back to the client.
Client receives the confirmation from the server.
If the confirmation is negative (-2), it prints out "The directory already exists on server" and returns to "prompt user for operation/wait for operation" state.
If the confirmation is negative (-1), it prints out "Error in making directory" and returns to "prompt user for operation/wait for operation" state.
If the confirmation is positive, the client prints out "The directory was successfully made" and returns to "prompt user for operation/wait for operation" state.
RMDIR:
Client sends operation (RMDIR) to remove a directory on the server. Note: Directory must be empty for RMDIR to work.
Client sends the length of the directory name which will be sent (short int) followed by the directory name (character string).
Server receives the above information, decodes directory name size and directory name, and checks if the directory to be deleted exists or not.
If the directory exists and is empty, the server sends a positive confirmation (integer value 1) back to the client.
If the directory does not exit, the server sends a negative confirmation (integer value -1) back to the client.
If the directory exists and is not empty, the server sends a negative confirmation (integer value -2) back to the client.
Client receives the confirmation from the server.
If the confirmation is negative (-1), it prints out "The directory does not exist on server" and returns to "prompt user for operation/wait for operation" state.
If the confirmation is negative (-2), it prints out "The directory is not empty" and returns to "prompt user for operation/wait for operation" state.
If the confirmation is positive, the client further confirms if the user wants to delete the directory: "Yes" to delete, "No" to ignore. The client then sends the user's confirmation (i.e., "Yes" or "No") back to the server.
If the user's confirmation is "Yes" the client waits for the server to send the confirmation of directory deletion.
If the user's confirmation is "No" the client prints out "Delete abandoned by the user!" and returns to prompt user for operation/wait for operation state.
The server waits for the delete confirmation sent by the client.
If the confirmation is "Yes" the server deletes the requested directory and returns an acknowledgment to the client to indicate the success or failure of file deletion operation. (Directory must be empty in order for deletion to occur).
If the acknowledgment is positive, the client prints out "Directory deleted" and returns to "prompt user for operation" state.
If the acknowledgment is negative, the client prints out "Failed to delete directory" and returns to "prompt user for operation" state.
If the confirmation is "No" the server returns to "wait for operation" state.
CD:
Client sends operation (CD) to change to a different directory on the server. Note: You can use CD followed by LS to verify that CD worked successfully.
Client sends the length of the directory name which will be sent (short int) followed by the directory name (character string).
Server receives the above information, decodes directory name size and directory name, and checks if the directory to be changed to exists or not.
If the directory exists, the server sends a positive confirmation (integer value 1) back to the client if it successfully changed directories; otherwise, the server sends a negative confirmation (integer value -1) back to the client.
If the directory does not exist, the server sends a negative confirmation (integer value -2) back to the client.
Client receives the confirmation from the server.
If the confirmation is negative (-2), it prints out "The directory does not exist on server" and returns to "prompt user for operation/wait for operation" state.
If the confirmation is negative (-1), it prints out "Error in changing directory" and returns to "prompt user for operation/wait for operation" state.
If the confirmation is positive, the client prints out "Changed current directory" and returns to "prompt user for operation/wait for operation" state.
QUIT:
Client sends operation (QUIT) to exit.
Client closes the socket, and exits.
Server closes the socket and goes back to the "wait for connection" state.
Client informs user that the session has been closed.
Note: If it is not explicitly specified, the client and server will return to "prompt user for operation" and "wait for operation from client" state respectively after a successful operation and wait for the next operation.
Important Hint: When uploading and downloading large files, make sure to take into consideration the return value from reading the socket. The system may not provide the same number of bytes as you requested to fill the buffer!
General Notes
Your code may be written in C or C++. You should create two separate directories: a server directory for the server code and a client directory for the
client code.
Each directory should contain a Makefile for building your code appropriately.
The request for a file is transmitted using a 16-bit value (short int - containing the length of the filename) followed by a string which contains the name of the file. When sending a file to the server, you need to send a 16-bit value (short int) followed by the filename to the server.
Don't forget that the Endian-ness matters. Use htons/ntohs in order to encode a 16 bit (short) value for transmission. Use htonl/ntohl for 32-bit (int) values.
You need to display the throughput for the file transfer. Throughput will be part of your grade. Most likely, you will want to use the high-resolution _timeval_ struct, which will allow you to keep track of accuracy at the microsecond level. The gettimeofday function will allow you to retrieve a timer accurate to microsecond levels.
To determine the size of files, you may want to use the fseek (or equivalent ifstream operation) function and/or the peek operation. For efficiency reasons you probably should not "roll your own".
When in doubt, add in extra printf or cout statements to assist with debugging. Make sure to add in a carriage return or do a flush on the I/O buffer to ensure that debug information is displayed before program crashes occur. Make sure to disable your debugging output before handing in the assignment.
You can connect back to localhost (127.0.0.1) which will allow you to test your code on the same machine where you have the server running. Create a separate ssh terminal session back to the server and then run your client code by connecting to localhost. But make sure you have tested your solution on two different student machines before submission.
We provided a file TestFile.txt (1KB) (download) which will be used to test the DN operation.
FTP-Server Side Design
The server is responsible for handling the connection request from a single client, processing the request,
then looping back to handle further requests. For this particular assignment, your code only needs to handle
one client at a time. The server binary should be named tcpserver.
The server should listen on the specified port number [the port number assigned to any of
your group members] that is given by command line argument. Your server should bind to the port and then listen
for incoming client connections. You may decide if you would like to allow timeouts for better
responsiveness but any sort of a timeout is purely optional. You may want to allow for port reuse for
quicker recovery after a crash.
Once a new client request arrives, your server should use the accept function to create a new client socket.
Once the file has been transmitted, return to the state where your server is waiting for a new command.
Your server should be invoked as follows: [netid@is-student00 ~] $ ./tcpserver [Port]
FTP-Client Side Design
The client is responsible for initiating a connection to a server. Once connected, the client code should
prompt the user for an operation (DN, UP, HEAD, RM, LS, MKDIR, RMDIR, CD, QUIT), and should transmit the operation to the
server. Your client binary should be named tcpclient. Your client should be invoked as follows: [netid@is-student02 ~] $ ./tcpclient [Server Name] [Port]
The first argument is the hostname of the server to connect to (this will depend on what machine you start
your server code on). The second argument is the port number.
Once a transfer completes successfully, you need to display the throughput for the transfer. You should display
information similar to the following for each file transfer (DN/UP):
99 bytes transferred in 0.029757 seconds: 0.003326 Megabytes/sec
Demo
Submission
Submit a gzipped tar file of your entire assignment package to the corresponding assignment on Canvas. One submission per group is sufficient.
The archive must include the following:
Working client code in a client subdirectory
Makefile for the client
Source code for the client in the TCP Practice (Part 1)
Source code for the FTP client (Part 2)
Appropriate comments and your name
Working server code in a server subdirectory
Makefile for the server.
Source code for the server in the TCP Practice (Part 1)
Source code for the FTP server (Part 2)
Appropriate comments and your name
README file
The name and netid of each team member
Listing of the included filenames and example commands to run your code in the archive
Evaluation Rubric
Your code will be evaluated on one of the student 00/01/02/03 machines based on the following evaluation rubric.
Part 1: 20 pts
[10 pts] Code successfully establishes the TCP connection between the server and client.
[10 pts] Code successfully sends a string from the client to the server.
Part 2: 80 pts
Client code (35 pts total)
[5 pts] Included Makefile and code compiles/runs without errors.
[4 pts] Code performs the DN operation correctly.
[4 pts] Code performs the UP operation correctly.
[4 pts] Code performs the RM operation correctly.
[3 pts each] Code performs the MKDIR, RMDIR, and CD operations correctly.
[2 pts each] Code performs the LS and QUIT operations correctly.
[4 pts] Code provides reasonable error checking.
[2 pts] Reasonable code performance (block transfers vs. single byte transfers).
[2 pts] Code is commented with appropriate header information.
Server code (35 pts total)
[5 pts] Included Makefile and code compiles/runs without errors.
[4 pts] Code performs the DN operation correctly.
[4 pts] Code performs the UP operation correctly.
[4 pts] Code performs the RM operation correctly.
[3 pts each] Code performs the MKDIR, RMDIR, and CD operations correctly.
[2 pts each] Code performs the LS and QUIT operations correctly.
[2 pts] Code is commented with appropriate header information.
Performance Metrics (5 pts total)
[5 pts] Performance metrics are reported properly by client and server.
Overall (5 pts total)
[2 pts] Code is submitted in requested form.
[3 pts] Code follows argument convention.
Extra Credits
Download Large Files: The client will be able to download large files (in several MB) from the server. In particular, when the client receives the large file from the server, it will need to enter a loop to receive the entire file in multiple rounds. In each round, the client will be able to write the data up to the buffer size (e.g., 4096). Note: You will need take into consideration the return value from reading the socket. The system may not provide the same number of bytes as you requested to fill the buffer. We provided a large files, LargeFile.mp4 (44MB) (download), which will be used to test the DN operation.
MD5 Hash for Data Integrity: You will need to calculate the MD5 hash of the transferred file for data integrity purpose. Whenever you send a file (through UP or DN), calculate the MD5 hash on both the sending and receiving ends.
Download (DN): The server will send the calculated MD5 hash to the client. The client will compare the received MD5 hash and the MD5 hash calculated from the received file. If the MD5 hash matches, the client will print a confirmation message with the MD5 hash. Otherwise, the client will display an error message.
Upload (UP): The client will send the calculated MD5 hash to the server. The server will compare the received MD5 hash and the MD5 hash calculated from the received file. If the MD5 hash matches, the server will send a confirmation message to the client. Otherwise, the server will send an error message. The client receives the message, and displays it to the user. If the MD5 hash matches, the client will also display the MD5 hash.
Note: The MD5 hash is to be calculated with the md5sum command line utility. The syntax is md5sum filename to obtain the hash as a string (in hex). You can find more information about MD5 Hash at the Wikipedia Link.
Extra Credit Rubric (20 pts)
[10 pts] Code performs the DN operation correctly for large files.
[10 pts] MD5 hash is calculated and checked correctly by client and server.