This section is designed to show how the TCP layer of the Bluetooth protocol stack was implemented in VCC. Shown below are the procedures for opening and closing a TCP connection, retrieving status information about the connection, aborting all in-progress communications, and sending and receiving data via TCP.
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data | |U|A|P|R|S|F| | | Offset| Reserved |R|C|S|S|Y|I| Window | | | |G|K|H|T|N|N| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | Urgent Pointer | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(Note. Each tick mark represents one bit position. for a full description of all of the header fields, please read the ) This packet format is implemented as the composite type "@TCP_Protocol.TCP_Packet" in the MootoothWorkspace2 workspace.
The following diagram shows a state machine, which represents the various states
in which a TCP connection can be in during its lifetime. The meaning of these
states is discussed below. Internally, each TCP block holds a datastructure called a Transmission Control
Block (TCB). This is used to represent the state that this particular TCP is
currently in, and is changed as the state of the connection is altered. The upper half of the above diagram, up to and including the 'ESTABLISHED'
state, represents the process of opening a TCP connection. A 'three-way handshaking'
procedure is used to initialize the connection. This is done partly to ensure
that both sides of the link can know whether a connection attempt has been successful
or not, and partly to gracefully handle all different connection scenarios.
For example, the opening of a connection is normally achieved by one TCP requesting
a connection and the other end responding, but the three-way handshaking procedure
also enables the procedure to work in the case where both TCPs request a connection
to each other simultaneously. The States in the 'Open' half of the diagram are as follows:Opening and Closing Connections
Open
CLOSED | This is a "fictional" state which represents the fact that there is no TCB, and therefore no connection |
LISTEN
|
Waiting for a connection request from any other remote host. |
SYN-SENT | This state represents that this TCP has sent a connection request and is now waiting for the other end to send a matching connection request. |
SYN-RECEIVED | Represents that this TCP has both sent and received a connection request, and is now waiting for an acknowledgment of its connection request from the TCP on the other end. |
ESTABLISHED | This signifies that the connection is open, and that users can freely send and receive data. TCP will remain in this state until an entity tries to close the connection. |
The connection progresses through these various states in response to certain trigger events. In the case of opening a connection, these are the user-activation of the TCP_Open and TCP_Send ports (i.e. when the application wishes to actively open a connection), and incoming packets from the remote TCP which have the SYN, or ACK flags in their headers set. These incoming packets are ordinary TCP_Packets which are used solely to negotiate connection options via the contents of their headers and contain no actual data in their "data" field.
A typical connection process would proceed in the following manner:
The lower half of the diagram above, from the "ESTABLISHED" state to the bottom "CLOSED" state represents the process of closing an active connection. A "handshaking" procedure, similar to that used when opening a connection is used, in order to ensure that each side of the connection has an accurate picture of the current state of the connection.
In TCP, a "CLOSE" request actually represents one side of the connection declaring that it has no more data to send. Since there is a potential ambiguity in how to treat the side of the connection which receives the close request, TCP treats CLOSE messages in the simplest manner, i.e. the application which closed the connection can continue to receive data from the other end of the connection until the other TCP also closes its end of the connection. In this particular implementation, the application which closes the connection will receive the whole of any message which was sent by the opposite end before the connection is fully closed on both sides.
To close a connection, an application activates the TCP_Close port on the TCP block. The connection is then closed by the two TCP blocks following the behavior described by the state machine. Again, the transitions between these states is triggered by certain events, namely: user-activation of the TCP_Close and incoming packets from the remote TCP which have the FIN, or ACK flags in their headers set. The states in the diagram represent the following:
ESTABLISHED | This signifies that the connection is open, and that users can freely send and receive data. TCP will remain in this state until an entity tries to close the connection. |
CLOSE-WAIT | Represents waiting for a connection termination request from the local user. |
FIN-WAIT-1 | Represents waiting for either a connection-termination request from the remote TCP, or for an acknowledgment of a termination request which was sent previously. |
FIN-WAIT-2 | Represents waiting for either a connection-termination request from the remote TCP. |
CLOSING | Represents waiting for a connection termination request acknowledgment from the remote TCP. |
LAST-ACK | Represents waiting for an acknowledgment of a connection termination request which was previously sent to the remote TCP |
TIME-WAIT |
This is not implemented in the current version of the Bluetooth protocol stack in VCC. This state is designed to make TCP wait for enough time to pass to be sure that the remote TCP has received the acknowledgment of its connection termination request. This is not implemented doe to the problems with timers in VCC which are outlined in the "VCC Issues" section below. |
CLOSED | This is a "fictional" state which represents the fact that there is no TCB, and therefore no connection |
In order to send data to the other side of a TCP connection, the application activates the TCP_Send port with an argument of type TCP_Send_args. This argument structure contains: the local connection name, to identify the connection to be used; a data buffer containing the actual data to be sent, which is of a fixed size (currently ~12Kb) since VCC requires to know the absolute size of any composite types at compile time; an integer byte_count field which shows how much of the data buffer is to be sent, since the message to be sent may not fill the whole data buffer; and a "PUSH" flag to tell the receiving TCP how the data should be delivered. If the PUSH flag is set, the received TCP packets are given to the application as soon as they are received, if the flag is cleared, then the data is passed as a whole to the application once all of the constituent packets have been received. It is important that the application only attempts to send data when it has received a message from the ClearToSendOut port, informing it that TCP is ready to begin sending more data across the connection.
When the TCP_Send port is activated, the TCP block examines the passed argument to determine how to proceed in sending the data. First it checks whether the local connection name which was passed corresponds to the connection which this TCP block is handling. If this is not the case, the message is simply discarded. Next, the data buffer in the argument is copied into a global 'send' buffer. If the whole buffer will fit into the 'data' field of a TCP_Packet (currently ~4Kb), the data is copied into the 'data' field of a global TCP_Packet in the Whitebox C file, the header of this packet is filled in with the correct local/foreign sockets, sequence number, etc. and is sent to the TCP block on the other side of the connection once the local TCP has received a 'ClearToSend' signal from the lower levels in the protocol stack. The local TCP then waits for an acknowledgment of receipt of this packet from the remote TCP, and once it has this and a 'ClearToSend' signal from the lower levels, sends a 'ClearToSendOut' message to the application to inform it that it is free to send more data.
In the case where the whole of the argument's data buffer will not fit into a single packet, TCP again copies the whole data buffer into a global 'send' buffer, fills a TCP_Packet with as much data as it can hold, sends it to the remote TCP. Once it receives an acknowledgment of receipt from the remote system it fills another TCP_Packet with the next TCP_Packet-sized chunk of data from the global 'send' buffer and sends it. It continues like this, until the remainder of the global 'send' buffer will fit into a single packet, and then proceeds as described in the previous paragraph. Under normal circumstances, TCP wouldn't have to wait for the acknowledgment of receipt of one packet before sending the next one, and could simply send the data as and when it pleased. The reasons for not being able to do this in the current implementation are discussed in the "VCC Issues" section below.
Data which is incoming to TCP while a connection is established can take two forms: Actual data to be passed to the application, and acknowledgments to packets which had previously been sent.
In the case of receiving an acknowledgment packet, then the sequence number of the packet is checked to see whether it is an acknowledgment of a packet which was actually sent. If it is, then this frees TCP to start sending the next packet in the sequence, as described in the previous section. The application is not informed of this type of message.
If the packet contains actual data which should be passed to the application, then the PUSH flag in the packet's header is inspected. If it is set, then the packet is immediately sent to the application. If not, then the data contained in the packet is copied into a global 'receive' buffer at the correct place according to the packet's sequence number. This 'receive' buffer is the size of the largest message which can be passed to the application. Once the packet is received, an acknowledgment packet is sent back to the sending TCP. This packet has the same sequence number as the packet which arrived, but has the ACK flag set in the header. When the whole message has been received and reconstructed, the position of the end of the data in the buffer is marked, and a datastructure of type TCP_Receive_Results is constructed and passed to the application via the TCP_Receive port of the TCP block.
This method of receiving data departs slightly from the method prescribed in the standard, which requires the user to explicitly call a receive function to receive data. It was felt, however, that it would be more in keeping with VCC's style of operation, and more convenient for the team members writing the applications if TCP simply passed the message directly to the application when it had finished receiving it.
An application can activate the "TCP_Status" port on the TCP block in order to request certain status information about the connection named in the argument. When this port is activated, a structure of type "TCP_Status" is created, the requisite information filled in, and then sent to the application via the "TCP_Status_Results" port.
In order to abort all data transmission currently in progress, the application can activate the "TCP_Abort" port with an argument of the local connection name. When this port is activated, the byte count of the global 'send' buffer is set to zero, ensuring that one final packet, containing no data, is sent to the remote TCP, thus ending the current message being sent to it. In addition to this, a RESET message is sent to the remote TCP, to inform it that it must also stop sending any messages in progress.
There are a few deviations from the TCP standard in this implementation which are due to the limitations of Whitebox C used in VCC.
The first of these is that in order for TCP to ensure reliable communication, it uses a timeout mechanism whereby if it hasn't received an acknowledgment from the remote TCP of a packet which it sent within a certain time limit, then it retransmits the packet. Thus, packets which are lost or damaged in transit will be retransmitted until they are correctly received. During the implementation of TCP, however, it proved impossible to use reliable timing information within the Whitebox C model. Two different methods for implementing timeouts were considered: first, using the current simulation time within VCC, and second, using the "Clock" block which was used in the baseband implementation which outputs a regular clock "tick". Unfortunately, using either of these methods somehow made it impossible to map the TCP block to a platform within VCC, so in order to produce a block which was 'mappable', this feature had to be removed, with the unfortunate side-effect that the current TCP implementation cannot reliably transmit data if there is a possibility of packets being completely 'lost' before they reach the other side of the connection.
The second problem is that described in the "Sending Data" section above, whereby the TCP block cannot send its next packet until it has received an acknowledgment of the previous packet from the remote TCP. This is due partly to the timer issue mentioned earlier, and partly to being unable to dynamically allocate memory within Whitebox C. In a TCP implementation written in ordinary C, as soon as the local TCP was ready to send the next packet it would 'malloc' the correct size of packet, fill in the data, and then send the packet. This isn't possible in Whitebox C, so the only alternative is to have a fixed number of "TCP_Packet" structures, which are filled in and cleared, as appropriate when TCP tries to send data. Now consider the situation where all of these packet structures have been filled in, and are waiting to be sent. The sending function in the TCP block would be unable to construct another packet to send. This is analogous to an ordinary C implementation being unable to 'malloc' enough space due to low system resources, and would in both cases normally be resolved by making the sending process 'sleep' for a short time and then trying again. Due to the above mentioned problems with mapping anything involving timers to a platform, this approach cannot be taken in the Whitebox C. It would probably be possible to work around this problem, but there are a number of awkward cases which would have to be dealt with and unfortunately could not be completed in the remaining duration of the project.
The result of this second problem is that the current TCP implementation still properly obeys the TCP standard's flow control requirements, i.e. using a sliding window to ensure that the sender does not send data too fast for the receiver to collect, but does so in a rather simple manner, by ensuring that the size of the sliding window is always '1', and therefore only one packet can be transmitted before the receiver collects and acknowledges it.