User:Lensovet/cs162

New Variables

 * Note: A "socket identifier string" is a string that reads "srcport-dstport-dstaddr".
 * Kernel Variables
 * : A  instance that facilitates   communication.
 * : A  that maps local port number integers to an   of (remote address, remote port) tuples. Entries are added to the   whenever corresponding SYN  s are received, and this structure is accessed by   calls to establish connections with remote machines.
 * : A  of   identifier strings, which is populated by   calls and accessed by the sending thread to determine which  s to send packets through.
 * : A class that contains data about packets that have been sent and have yet to be acknowledged. An  contains a   resend time, a local port, a dst port, a dst addr, and a packet sequence number.
 * : A  of , prioritized on  . Every time a packet is sent, an   with the corresponding information is added to this global structure.   is initialized to the   + 20,000. The timer thread will send all qualifying   referenced in this structure. Whenever an ACK is received, we remove the corresponding   entry from the structure.
 * : A  that maps from (local port, dst port, dst addr) to  s.


 * Process Variables
 * : A  of file descriptor integer to Socket string identifier (local port, dst port, dst addr) mappings. The methods   and   will access this   to determine from which   to receive and send data.


 * Sockets
 * : A class that contains socket data. The  contains instances of these  s, mapped by ports and remote addresses. It stores the process ID, a send-data buffer, a receive-data buffer, a receive sliding window, a send sliding window, an , and a.
 * : This  is initialized to 1. On sending of a , the   will be labeled with this integer, and this value will be incremented.
 * : An  that represents the current state of a network connection and dictates what occurs on events. The statuses are the same as those detailed in the Nachos Transport Protocol Specification: CLOSED, SYN_SENT, SYN_RCVD, ESTABLISHED, STP_SENT, STP_RCVD, and CLOSING.
 * : On writes, byte data will be queued on this  until the sending thread awakens and forms packets out of the data to be sent.
 * : On reads, byte data is returned from this, which is composed of queued data from contiguous blocks of  s, which are received periodically by the receiving thread.
 * : A  variable that   sleeps on until the receiving thread wakes it on the reception of a corresponding SYN/ACK  . At that point, the  's state is set to ESTABLISHED.
 * : The  associated with.
 * Sliding Windows
 * An array that holds 16 s. This structure keeps track of received packets. Upon packet receipt, the receiving thread adds  s to the array according to their sequence numbers.
 * All  are mapped relative to the , with.
 * Once there is a contiguous block of s starting at the beginning of the array, the receiver thread writes all of data in the contiguous block of  s to the   string.
 * is set to the highest sequence number of the copied block + 1.
 * The window then "slides" by copying over the remaining s to a new array with updated array indices.
 * A size 16 array of  tuples. This structure keeps track of  s that have not yet been acknowledged.
 * On the sending of a, that packet is added to this array in the same fashion as the.
 * On reception of an ACK packet, the receiver thread flips the  of the corresponding   to.
 * Once there is a contiguous block of acknowledged s starting at the beginning of the array,   is set to the highest sequence number of the block + 1.
 * If any packets are remaining (beyond the contiguous block), they are copied to a new array with their array indices recalculated as for the receiveSlidingWindow.
 * An attempt to send s with a sequence number exceeding the range of the , inclusive, will fail.
 * : This  is initialized to 1. This number indicates at what sequence number the send sliding window begins and limits the range of numbered  s that can be sent.
 * : This  is initialized to 1. This number indicates at what sequence number the receive sliding window begins and limits the range of numbered  s that can be received.
 * : This  is initialized to 1. This number indicates at what sequence number the send sliding window begins and limits the range of numbered  s that can be sent.
 * : This  is initialized to 1. This number indicates at what sequence number the receive sliding window begins and limits the range of numbered  s that can be received.

Implementation Details

 * Note: All packets received that are inappropriate for a particular socket state will be dealt with according to the protocol specification.
 * Swap files are uniquely named with the network link address of the Nachos node.


 * Generate random src port. If there is a  corresponding to the src port, dest address, dest port in the   which is CLOSED and has an empty read buffer, use it. Otherwise, continue picking random ports until such a socket can be found or no socket for the chosen src port, dest address, dest port exists. In the latter case, create a new Socket object with the appropriate parameters and add it to.
 * Send a SYN packet to the target machine using, adding it to  , so that the timer thread can automatically resend un-ACK'd SYNs. Set the state of the   to SYN_SENT.
 * Sleep on the  in the created/reused   object. On the reception of a SYN/ACK packet, the receive thread will find the appropriate   from   and call   on its , at which point the   is switched to the ESTABLISHED state. A new   mapping will also be added to the
 * Sleep on the  in the created/reused   object. On the reception of a SYN/ACK packet, the receive thread will find the appropriate   from   and call   on its , at which point the   is switched to the ESTABLISHED state. A new   mapping will also be added to the


 * Upong receipt of SYN s, the receive thread stores the request (local address, remote address, and remote port) in  : the local port is mapped to a list of remote addresses/ports.
 * An  call searches for a pending request on the requested local port in  . If such a request exists, it is removed from  . Otherwise,   returns -1.
 * If a pending connection was found, a new  is created with its information and is initialized with an ESTABLISHED state. A SYN/ACK   is sent directly using  . A new   mapping is added to the   hash.
 * If a pending connection was found, a new  is created with its information and is initialized with an ESTABLISHED state. A SYN/ACK   is sent directly using  . A new   mapping is added to the   hash.


 * is checked to determine if the file descriptor is a Socket, retrieving its identifier (if it is), and adding it to the  list.
 * The write buffer is copied to the  string and   returns immediately. The sending thread will take care of the actual communication of  s.
 * The following error checks will be performed at the beginning of this method. Failure of these will return -1:
 * The  maps to a.
 * The 's state is CLOSED or STP_RCVD.
 * The 's state is CLOSED or STP_RCVD.


 * will retrieve the  identifier and then the   from the.
 * If the  sees that there is some data in the , it will read as much as it can or as much as it has requested. A   is still able to read from a closed socket, as long as the receiveBuffer still contains data. It will return 0 if there was no data available. The receiving thread will take care of actual communication of  s.
 * The following error checks will be performed at the beginning of this method. Failure of these will return -1:
 * The  maps to a.
 * The  is CLOSED. If there is data in , we read it as detailed above.
 * The  is CLOSED. If there is data in , we read it as detailed above.


 * Close translates from file descriptor to socket using.
 * If the socket is in an ESTABLISHED state, it will check if the send sliding window is currently empty (all packets have been ACK'd).
 * If this is true, it will add a FIN packet to the send sliding window, and a reference entry to the FIN packet to the  structure. Then it will change the  's state to CLOSING.
 * Otherwise, the send sliding window still has unacked packets, so  will send a STP packet using   and switch the  's state to STP_SENT.
 * On the reception of more data packets by the receiving thread, if the  is in the STP_SENT state, we retransmit a STP packet. Once all packets have been ACK'd in the send sliding window, if we are in the STP_SENT state, we proceed to the FIN state as detailed earlier.
 * On the reception of a STP packet, if a  is in the ESTABLISHED state, we remove all packets from the send sliding window and the   structure and change the state to STP_RCVD.
 * On the reception of a FIN packet in the STP_RCVD state, we send a FIN/ACK packet and go to the CLOSED state. The  that receives a FIN/ACK packet in the CLOSING state will also go to the CLOSED state.
 * When a  is in the CLOSED state,   calls will not be possible, but   calls are possible as long as   still contains data.
 * If there is no more data in the  of a , we remove the file descriptor mapping from.
 * If there is no more data in the  of a , we remove the file descriptor mapping from.

Permanent threads

 * Sending Thread
 * Takes a socket identifier from  and verifies that the corresponding   is not CLOSED and that its   can accommodate more  s.
 * If these conditions are not fulfilled, the socket identifier is put back onto  and the thread will work on another request.
 * If these conditions are fulfilled, it removes  amount of bytes from the   of the chosen socket and creates a data packet out of those bytes. A   is then created with the appropriate addressing information and sent using  . The   is also added to the   at the appropriate index based on its sequence number.
 * This process is repeated until the chosen socket's sending window is filled, at which point the sending thread moves on to another request.


 * Receiving Thread
 * The receiving thread executes the actual reception and interpretation of data packets. It replaces the receiveInterrupt of the postOffice class. On reception of a packet, it verifies to which socket the packet is bound.
 * It then verifies that the  is not CLOSED and that the receive sliding window can accommodate more  s. If the conditions are not fulfilled, the packet is dropped. Otherwise, if the   received is a data packet and the   is in a valid state, the   is placed at the appropriate index of the sliding window.
 * A corresponding ACK  is sent directly with.
 * We continue to receive s in this manner until the   is filled or we drop a.
 * If at any time the sliding window has a contiguous block of s starting at index 0, we flush the data of those  s into the  ’s.
 * On the reception of specialized s, we follow the Nachos protocol specification and modify the   state accordingly.
 * On reception of ACK-type s, the corresponding   in the   is marked as ACK’d and the sliding window is adjusted accordingly.
 * On the reception of a SYN/ACK packet by the receive thread, the Socket will be found using information from the packet and wake will be called on Socket.waitForAccept, at which point the Socket is switched to the ESTABLISHED state.


 * Timer Thread
 * When the NetKernel initializes, it forks a Timer Thread that runs continuously.
 * The timer thread peeks at the top of the  structure and verifies if the  . If this is true, resend the   using information from the   corresponding to the.
 * Removethe  off the top of   and add the   entry back onto the structure with a new   value.
 * The timer thread will keep resending s as long as entries fulfill the   condition. Otherwise it will go to sleep.

Pseudocode
connect(host, port) { do { generate random src port if (src port, dest addr, dest port do not exist in socketHash) { create newSocket; add newSocket to socketHash; break; }        if (src port, dest addr, dest port refers to a closed, fully-read socket){ newSocket = existingSocket; break; }    } while (true); newSocket.state = SYN_SENT; send SYN packet and add packet information to unackedPackets and sendSlidingWindow; sleep on newSocket.waitForAccept, wait for receiving thread to wake on reception of a SYN/ACK; newSocket.state = ESTABLISHED; add fileDesc, socket mapping to descToSocket; return fileDesc; }

accept(port) { if port in keys of pendingConnects { remove one (addr,port) tuple from associated list } else { return -1; }    create newSocket; newSocket.state = ESTABLISHED; send SYN/ACK packet; add new  mapping to descToSocket; return fileDescriptor; }

write { //only networking code is shown translate from fileDesc to socket identifier using descToSocket; add socket identifier to sendRequests list; copy all data from src buffer to socket's sendBuffer string; return bytes written; // the sending thread deals with actual packet communication }

send { //"Sending Thread" while (true) { pop sendRequests; if (socket is not CLOSED && sendSlidingWindow is open) { while (sendSlidingWindow is open) { remove packetSize bytes from socket.sendBuffer; create a packet with that data; postOffice.send(packet); add packet to sendSlidingWindow; }        } else continue; } }

read { translate from fileDesc to socket identifier using descToSocket; if (socket is closed) { if (socket still has data to be read) { read available data from socket.receiveBuffer; return bytes read; } else { return -1; }     } else { read as much as needed from socket.receiveBuffer; return bytes read; //receive thread takes care of actual packet communication. } }

receive { //"Receiving Thread" while(true){ packet = postOffice.receive; if (packet is ACK type) { mark corresponding packet in sendSlidingWindow as ACK; adjust sendSlidingWindow accordingly; }                     socket = get socket from socketHash with info from packet; if (packet is SYN/ACK type) { socket.waitForAccept.wake; socket.state = ESTABLISHED; }        if (socket is not CLOSED && receiveSlidingWindow is open) { if (packet is data packet && socket.state is valid) put packet at appropriate index of receiveSlidingWindow PostOffice.send(ACK packet); } else { drop packet; break; }        if (receiveSlidingWindow is full) { break; }        packetsToFlush = []; for (index i of receiveSlidingWindow starting at 0) { if (receiveSlidingWindow[i] has packet) { add packet to packetsToFlush; remove packet from receiveSlidingWindow; } else { break; }        }       } }

timer { unackedP = unackedPackets.peek; while (currentTime >= unackedP.time) { resend unackedP.packet; unackedP = unackedPackets.pop; put unackedP in unackedPackets with time = currentTime + 20000; unackedP = unackedPackets.peek; }    sleep; }

close(fileDescriptor) { get Socket s with given fileDescriptor from descToSocket; if (s.state == ESTABLISHED) { if (s.sendSlidingWindow is empty) { add FIN packet to s.sendSlidingWindow; add reference to FIN packet to unackedPackets; s.state = CLOSING; } else { send STP packet with PostOffice.send; s.state = STP_SENT; }    } else if (s.state == STP_RCVD) { send FIN packet to s.sendSlidingWindow; add reference to FIN packet to unackedPackets; s.state = CLOSING; } else { return -1; }    return 0; }

Testing

 * Connection
 * Set up two Nachos nodes, named A and B:
 * Have A connect and B accept on the same port and verify that a connection has been made with print statements on the information from the two newly created sockets.
 * Attempt two connects from A with only 1 accept from B on the same port, verify that only one new socket is created on B, but it has a record of the second connect. A subsequent accept should create a new connection with the 2nd connect from A.
 * Create a successful connection between A and B, and close the connection. Verify that both socket states are closed.
 * After closing, verify that creation of a connection with the same port and addressing information is successful. Also verify that this same test correctly fails if there is still data to be read from the port.
 * Create a connection from Port 1 to Port 2. Create a second connection from Port 3 to Port 1.
 * Verify that both connections work properly with print statements.
 * Verify the reads and writes succeed from Ports 1 and 2 and Ports 1 and 3.


 * Reading and Writing
 * Set up two Nachos nodes, named A and B:
 * Have A run a program that writes a large amount of text to B. Have B run a program that reads text from the same port and verify that the text is the same sent from A.
 * With A and B connected, have A call write three times and have B call read only once. Close A.
 * Verify that the connection between A and B is dead.
 * Verify that writes from B to A will fail.
 * Verify that two more reads from B with retrieve the last two messages sent by A.
 * Verify that a subsequent read from B will fail.

New Variables

 * : A struct containing an, a byte array  , and an  .  Each struct also contains a pointer to the next client. Each server node thus contains a linked list of clients.
 * : A  pointer to the next   from which to read input.
 * : An  that determines the maximum size of a line.
 * : An  that stores the   returned by.
 * : An  that determines the maximum size of a line.

Implementation Details

 * The main method in  initializes a byte array   of length  . It then runs a continuous while loop and performs the following actions:
 * It checks for standard input. If there is standard input, the method breaks from the while loop and exits.
 * It calls  on port 15. If there is a chat client waiting to establish a connection, a new   node is created with the returned   and a new byte array initialized to size 1000. This new client is added to the.
 * For each client in the,   does the following:
 * It reads up to  from   into buffer.
 * It appends the data in  to
 * It reads from . If this read returns -1, we disconnect. Otherwise, we add the returned value to.
 * While  contains a newline, it does as follows:
 * It copies  up to the newline into.
 * It moves the rest of the data in  up by however many bytes were copied, subtracting this number from.
 * Finally, for each client in the, it sends out the contents of.
 * Disconnects are handled by catching -1 return values on reads and writes. Since the client has called close prior to the disconnect, the socket on the server side should reflect the closing of the connection. When a read or write failure occurs, we remove that particular client from the client list.


 * The main method in  first initializes three byte arrays:,  , and  . All these arrays are of length  . It then calls  . It runs a continuous while loop and performs the following actions:
 * It reads standard input of length  into.
 * If  is not empty, it runs the following while loop on the condition that   contains a newline:
 * It copies the data in  up to the first newline character into.
 * If the contents of  is ".", it calls   and breaks out of the while loop, ending this user's chat session.
 * It moves the rest of the data in  up by however many bytes were copied.
 * Then it write the contents of  to the server.
 * If  is empty, it reads from client's socket into   a length of   byte, before running a while loop on the condition that   contains a newline:
 * It copies the data in  up to the first newline character into.
 * It moves the rest of the data in  up by however many bytes were copied.
 * Finally, it prints the contents of.

Pseudocode
chatserver.main { byte tmp[MAX_LINE_SIZE]; while (true) { if there is standard input { break; } if (newClient = accept(15)) add newClient to clientList; for (currentClient in clientList) { read up to MAX_LINE_SIZE from currentClient.socket into tmp; append the data in tmp to currentClient's buffer currentClient.bufferLen += read(currentClient.socket,                    currentClient.buffer + currentClient.bufferLen,                     MAX_LINE_SIZE - currentClient.bufferLen) while (currentClient.buffer contains a newline) { copy currentClient.buffer up to newline into tmp; move the rest of currentClient.buffer left by however many bytes were copied; currentClient.bufferLen -= bytes copied; for (client in clientList) { clientList.socket.write(tmp); }            }         }     } }

chat.main(int addr) { byte currentMsg[MAX_LINE_SIZE], tmp[MAX_LINE_SIZE], server[MAX_LINE_SIZE]; socket = connect(addr, 15); while (true) { read(stdin, currentMsg + length of currentMsg, MAX_LINE_SIZE - length of currentMsg) if (currentMsg is not empty) { while (currentMsg contains a newline) { copy currentMsg up to newline into tmp; if (tmp == ".") { close; break; }                move the rest of currentMsg left by however many bytes were copied; socket.write(tmp); }        } else { read(socket, server + length of server, MAX_LINE_SIZE - length of server) while (server contains a newline) { copy server up to newline into tmp; move the rest of server left by however many bytes were copied; print(tmp); }        }     } }

Test cases

 * Two users:
 * One instance of Nachos runs chatserver.c, while Users A and B run chat.c on other separate instances.
 * Have A send a message and confirm that both A and B receive the same message from the server.
 * Have B send a message and confirm that both A and B receive the same message from the server.


 * Three users:
 * One instance of Nachos runs chatserver.c, while Users A, B, and C run chat.c on other separate instances.
 * Execute the same tests used in the situation with two users.
 * Have A send 2 messages, B send 1, and C send 2. Verify that all these messages are received by all chat clients. The two messages from both A and C should have been received in the order they were sent by their respective users.
 * Have C disconnect from the chat server, then reconnect. Verify that C can still receive and send messages to A and B as before.


 * Four or more users:
 * Perform the same tests used for three users.
 * Run tests with more messages and more complex ordering of sent messages.
 * Have users connect to the server after the chat session has already begun and verify that they can interact with the other users normally.
 * Have various users disconnect and reconnect to the server and verify that they can still interact with the other users as before.