Request
The client first requests the server to read or write some data. At well known port 69
.
Request
frame containing the filename, transfer mode and optionally any negotiated option. (We will not discuss those options in this tutorial)
#![allow(unused)] fn main() { pub struct Resource { pub filename: Text, pub mode: Text, } }
Text
is nul-terminated modified version of ascii string.
Data Transfer
Based on request, The server or client sends the data frame to the other endpoint. Data frame contains a block
number and payload.
The payload is sent in a fixed length buffer of 512
bytes by default or the number specified in the block size negotiated option.
The last data block must be less than the negotiated or default blocksize (which is 512
) to signal the end of the transfer.
What happens when the last block is exact block sized ?
If that happens, the endpoint sends a data frame of 0
byte to signal the end of the transfer.
Initially, block
number is 1
. On each transfer, the block number is incremented by one.
What happens when the block number is exhausted ?
According to wikipedia, The original protocol has a transfer file size limit of 512 * 65535
blocks = 32
MB, Today most servers and clients support block number roll-over (block counter going back to 0 or 1 after 65535) which gives an essentially unlimited transfer file size.
If the response is positive, the server create a new socket and all transfers are performed using this new socket.
This significantly simplifies the implementation of overall protocol. As we don't need to track each socket.
Acknowledgement
For various reasons, there might be some packet lost. The sender detect packet loss using a timer and retransmit missing packets. This is known as acknowledgement.
Acknowledgement indicate that the data has been received.
An Endpoint must send an acknowledgement packet for each data packet received. Acknowledgement number must be the same as the block number of the data packet.
It guarantees that all old packets are received and prevents network congestion.
Positive response to a write request is an acknowledgment packet, in this special case the block number will be zero.
Error
Any errors cause termination of the connection. This packet is not acknowledged, and not retransmitted.
#![allow(unused)] fn main() { #[repr(u16)] pub enum ErrorCode { NotDefined, FileNotFound, AccessViolation, DiskFull, IllegalOperation, UnknownTransferID, FileAlreadyExists, NoSuchUser, } }
An error frame contains an error code and reason.
#![allow(unused)] fn main() { pub struct Error { pub code: ErrorCode, pub reason: Text, } }
Frame
In networking, a frame is a fundamental unit of data transmission between two endpoint.
#![allow(unused)] fn main() { #[repr(u16)] pub enum Frame { Read(Resource) = 1, Write(Resource) = 2, Data { block: u16, bytes: Bytes } = 3, Acknowledge(u16) = 4, Error(Error) = 5, } }
Here is the binary format of each packet:
The first 2 bytes of each packet type are known as the opcode
or discriminant, It is just a number that identifies the type of the frame.
Opcode | Frame Type |
---|---|
1 | Read Request |
2 | Write Request |
3 | Data |
4 | Acknowledgment |
5 | Error |