Ethernet TCP/IP Source Code Driver Project
Section 09. How The Driver Works

09. How The Driver Works

a) How The Basic Driver Works

Note – this section of the manual is for information only. You do not need to read and understand this large and in depth section to use the driver! However you may want to if you wish to gain an understanding of how each of the driver components works.

The eth-main.c and eth-main.h files are the central files of the TCP/IP driver. The central stack operations and definitions are provided in these files. If you want to get a feel for how the stack works at the basic level have a quick scan through the tcp_ip_process_stack() function.

The tcp_ip_initialise() function initialises the driver and automatically calls any other driver initialisation functions required by the selected driver / stack components. This function also calls the nic_initialise() function to initialise the nic (network interface controller) IC, provide it with its MAC address and enable the receiving of packets.

The user application then calls the tcp_ip_process_stack() function regularly (typically as part of an applications main loop) and this function checks for packets received by the nic, for responses that need to be sent and calls any background task functions that need to carry out periodic tasks.

The stack is designed as a single thread and no stack component is permitted to halt operation in wait states. Every operation that may require a delay or wait is handled by simple state machines so that completion of tasks or delays is checked for and handled each time the tcp_ip_process_stack() function is called. Therefore the stack cannot cause the user application to become halted.

When a packet is received the tcp_ip_process_stack() function starts the reading of the packet data from the nic, reading the Ethernet header which contains the senders and destination MAC address and the packet type code. It then branches depending on the packet type, and if the packet is IP then branching again for the IP packet type. At this stage this function passes the packet for processing to the individual driver / stack components that can handle it, or discards it if there is no facility to process it. Once the packet has been passed it is then down to the relevant driver component to continue processing the packet and finally discard it.

In addition to checking for received packets the tcp_ip_process_stack() function then calls each of the driver / stack components being used, allowing them to process packets passed to them and carry out any other operations, such as background tasks and transmitting packets.

b) How The IP Driver Works

IP is the primary protocol in the Internet Layer of the Internet Protocol Suite. Its header contains the source and destination IP addresses of the packet, the protocol of the data within the IP packet (e.g. UDP, TCP, ICMP), the time to live for the packet (a value that is read and modified by individual routers to allow them to dump the packet if it takes too many hops to reach its destination) ,the packets length and a few other less important fields. This header contains all the information required by routers to deliver the packet to its destination irrelevant of what is contained within the data area of the IP packet. Although the header includes a checksum, this is only a checksum of the IP header itself and not the whole packet, allowing routers and devices to verify the integrity of the header information without wasting time checksumming the entire packet. Typically the protocol being transported by the IP packet (e.g. UDP) will use its own separate header which will be verified by the device the packet is being sent to, but is not important to all the devices the packet may pass through on its way.

This driver uses Internet Protocol Version 4 (IPv4) which is the dominant protocol of the Internet. Although its successor Internet Protocol Version 6 (IPv6) is now being actively deployed the abundance of IPv4 devices means that it isn’t going away anytime soon, if ever.

As a TCP/IP packet is received by the driver it looks at the start of the packet to determine the packet type. If the packet is IP it then looks within the IP header to determine which protocol the IP packet contains and passes it on to the specific protocols driver to handle. The IP part of the overall TCP/IP driver is relatively simple. It provides IP headers for outgoing packets and reads the IP headers of incoming packets. Each packet is then created or processed by the other individual stack components

For most users there will be no need to directly use IP (Internet Protocol), as each of the TCP/IP stack components sits on top of the IP layer and therefore sends and receives the IP packets automatically (UDP, TCP and ICMP packets are actually contained within an enclosing IP packet). However the following usage example is given for users who want to send and receive their own IP packets, for instance if implementing a special protocol.


	//----- TRANSMIT AN IP PACKET -----
DEVICE_INFO *remote_device_info

	//setup transmit
	if (!nic_ok_to_do_tx())	//Exit if nic is not currently able to send a
return(0);		//new packet

	if (!nic_setup_tx())	//Setup the nic ready to tx a new packet
		return(0);		//Nic is not ready currently to tranmit a new packet

	//WRITE THE IP HEADER
	ip_write_header(remote_device_info, ip_protocol);

	//WRITE THE DATA
	//Write each byte of the packet using:
	nic_write_next_byte(0x00);
	//or
	nic_write_array (my_array, sizeof(my_array))

	//TRANSMIT THE PACKET
	ip_tx_packet();

When transmitting a packet the total packet length does not need to be known as the length is automatically calculated by each of these functions and written when the ip_tx_packet() function is called.


	//----- TO RECEIVE AN IP PACKET -----

	//AsReception of IP packets is automatically dealt with by the stack, with the different packet types then handled as required, to receive bespoke packet you need to add your own handler to the following function in eth-main.c:
	tcp_ip_process_stack()

	//in the following section of its state machine:
	case SM_ETH_STACK_IP:

	//The contents of the received packet may be read using the following functions:
	nic_read_next_byte()
	nic_read_array()

	//And finally the following function needs to be called to discard the packet and allow the TCP/IP driver to continue its other processes:
	nic_rx_dump_packet()

	//The packet may be dumped without reading all of its contents if desired.

c) How The UDP Driver Works

UDP, which is defined in RFC768, does about as little as a transport protocol can. Apart from adding the concept of local and remote ‘ports’, from and to which packets are sent, and optional checksumming of the UDP data it doesn’t add anything to IP. When using UDP your application is able to transmit and receive packets in a completely raw fashion, in much the same way as sending and receiving packets of data over a serial link such as RS232. If error checking is enabled then packets will be automatically checksummed to detect errors, but no connection checking or automatic re-sending is undertaken. You send a packet and hopefully the device or devices you want to receive it will actually receive it. Your application needs to provide acknowledgements etc if you need to know you are connected or data has been received.

UDP’s great advantages over TCP are that you don’t ever have to wait on the stack, you can transmit to multiple devices at a time if you wish and its much lower processing overhead. In time critical applications TCP’s automatic retry can effectively temporarily lock up your socket if a packet gets lost. With UDP you decide how to handle lost packets if you need to. TCP’s great advantage over UDP is that connections are automatically made and lost packets are automatically re-transmitted.

UDP, like all of the stack process can only do one thing at once. When a UDP packet is received the application process that is using the socket must process and dump the packet, responding with a UDP tx packet if it wishes. Only 1 UDP packet may be received at any one time (other packets may be queued behind it in the nic receive buffer) and when transmitting a UDP packet it must be sent as a single process. This isn’t as limiting as it sounds, it simply means that any application process that has opened a socket must deal with packets received to that socket as soon as they arrive and when transmitting a packet must transmit a packet in one go. Multiple sockets may all have different processes running concurrently and the nic’s (network interface controller) receive buffers will automatically queue received packets for each socket.

UDP sockets are used with each assigned to a unique local port number. This allows multiple UDP sockets to be opened (total number available is defined in eth-udp.h) by the application and each socket used either for a brief exchange and then closed or just left open waiting for any UDP received to that socket (set with a broadcast IP address if required to receive packets from anyone).

d) How The TCP Driver Works

TCP (specified in RFC 793) is the most complex of all of the components of this driver. It is a connection based protocol, whereby you create one or more client or server connections to a single other device through which you send and receive data. The protocol automatically handles acknowledging of data transferred and resending of data should packets be lost. From an embedded point of view this is quite a challenge as apart from requiring relatively large amounts of code space you can potentially require large ram buffers also.

This driver takes a simplistic and lowest ‘cost’ view of providing TCP functionality, to allow TCP to be provided using limited resource processors / microcontrollers or to avoid large amounts of resources having to be provided to the driver. However the driver is also designed to provide very versatile TCP communications with multiple client and server sockets able to opened and used all at the same time. In order to allow the TCP driver to be used as needed for each specific end users application quite a low level interface is provided when creating and using client or server sockets.

TCP, like all of the stack process can only do one thing at once. When a TCP packet is received the application process that is using the socket must process and dump the packet, responding with a TCP tx packet if it wishes. Only 1 TCP packet may be received at any one time (other packets may be queued behind it in the nic’s (network interface controller) receive buffer but won’t be read until the previous packet is processed and dumped). When transmitting a TCP packet it must be sent as a single process. This isn’t as limiting as it sounds, it simply means that any application process that has opened a socket must deal with packets received to that socket as soon as they arrive and when transmitting a packet must transmit a packet in one go. Multiple sockets may all have different processes going on concurrently the nic’s receive buffers will automatically queue received packets for each socket.

TCP sockets are used to allow multiple (user defined in eth-tcp.h) sockets to be opened by application processes. A socket may act as a server waiting for an incoming connection, or as a client to connect to a remote device.

Sockets are opened on a specified local TCP port, and multiple sockets may be opened on the same port if required to allow connections to that port from multiple remote devices.

This TCP driver deals with much of the TCP background tasks automatically. TCP is a connection based protocol with automatic acknowledgement of communications and automatic timeout re-tries. The driver has one significant limitation however which is intentional to allow the driver to be used with limited resource processors or in applications where you don’t want to designate large buffers of memory to the driver. When a TCP packet is sent to a remote device but not received by the remote device for some reason, it needs to be resent by the TCP driver. However a copy of the transmitted packet is not automatically saved by the TCP driver. This approach allows this situation to be dealt with on a per application basis. In applications with insufficient memory to buffer every packet sent until an acknowledge is received the application processes using each socket can maintain a method of re-generating the last packet sent on a particular socket (i.e. do exactly what it did to send the packet the first time again), or decide to simply dump the last packet sent in this situation and allow for this in the

applications communication protocol. Alternatively if the application does have sufficient memory then it can copy each packet sent to memory ready to be sent again should it be needed (this would require a buffer of 1460 bytes per TCP socket). This drivers approach allows either method to be used.

A third approach could have potentially been used whereby the memory of the nic (network interface controller IC) could have been used to keep a copy of previously sent packets until an acknowledge was received from the remote device. However nic’s don’t tend to have very large memory buffers and with potentially long timeout times for each socket, especially if communicating over the internet, this approach could effectively lock up the Ethernet interface due to a lack of available memory, potentially resulting in lost received packets and long wait times for outgoing packets on other sockets. Therefore this approach was deemed unsuitable for this TCP driver.

Detailed TCP Driver Notes

Sequence Number

‘Sequence Number’ or ‘SEQ’ is the position (start address) of the current data block in the packet being sent within the overall data stream. If the value is 0 for the first packet which has 10 TCP data bytes then the sequence number for the next packet would be 10. The sequence number does not necessarily start from zero though and the 32bit value is used compared to the last value received to calculate the difference and therefore the position of the new data packet. The value wraps around when it reaches 0xFFFFFFFF but as this equals 4.3GB of data this is not an issue. This is why the sequence number is dealt with as a relative value (i.e. from last checkpoint) rather than absolute (i.e. from start of transfer).

The Sequence Number is sent by a device (server or client) to tell the other device the relative position of the packet of data being sent. Each device has its own Sequence Number and each device maintains a record of the last value received from the other device.

If a packet needs to be re-transmitted then the packet must be sent with the same Sequence Number as before.

Acknowledgement Number

‘Acknowledgement Number’ or ‘ACK’ indicates the total amount of data received. It is sent by a device (server or client) to tell the other device to total amount of data it has received from it. Each device has its own Acknowledgement Number and each device maintains a record of the new value to be sent to the other device. The Acknowledgement Number is valid when the ACK flag is set.

If the sequence number of a received packet is 200 and 10 bytes of data we’re received in the packet then if the device was immediately responding it would return an Acknowledgement Number of 210.

TCP connection

To open a TCP connection a TCP packet is sent with the SYN flag set. The packet does not contain any data. However TCP counts the SYN marker as one byte so the Sequence Number is incremented by one. To close a TCP connection a TCP packet is sent with the FIN flag set. The packet does not contain any data. However TCP counts the FIN marker as one byte so the Sequence Number is also incremented by one. Packets are acknowledged by the other device, including SYN and FIN packets, with the return of a packet at some point with the correct Acknowledgement Number value.

A packet may be sent at any stage of a transfer whether any data has been received or not, but a packet with its Acknowledgement Number must be sent when the quantity of unacknowledged data approaches the Window size or if it appears that no more data will be received for a while. An Acknowledgement packet may also contain data if the acknowledging device has data to send to the other device.

Window Size

This is the limit specified by each device as to how much data the other device may send before an acknowledgement is returned. In this driver it is set to the size of 1 Ethernet packet to effectively remove the problems of receiving multiple packets out of order and having to re-assemble them. This slows down the transfer of bulk data, but greatly simplifies the driver and the need for large hardware memory resources or complex application receive functions.

e) How The HTTP Server Driver Works

An HTTP server (RFC 2616 defines HTTP/1.1 which is the version of HTTP in common use) is provided as part of the TCP/IP driver / stack for inclusion if your device needs to provide a web interface. The HTTP server in its basic form is relatively straightforward. However a useful HTTP server needs to be able to receive input from users and generate dynamic content as pages are served and the functionality to provide this makes the driver quite complex in places.

HTTP works using TCP. The HTTP server opens one or more TCP server ports on TCP port 80 and waits for clients to connect. When a client connects, by a user entering the IP address or NetBIOS name into their browser, the TCP driver acknowledges the connection and the HTTP driver waits for the client to send a request. There are three basic HTTP requests and this driver supports each of them:- GET, HEAD and POST. Whilst the HTTP specification includes other requests types, these are the three that provide most of the functionality for browsing the web, with other request types often not well supported by browsers or servers and not required to be implemented.

Once the TCP connection is made the client browser then typically sends a GET request to the HTTP server. This is simply a TCP packet containing ASCII text formatted as defined by the HTTP specification. If you use Wireshark to capture the HTTP packets sent and received they are all human readable text, for instance a typical GET request looks like:

GET /index.htm HTTP/1.0 <CR><LF>

User-Agent:Mozilla/4.5

Host: www.embedded-code.com<CR><LF>

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<CR><LF>

Accept-Language: en-gb,en;q=0.5<CR><LF>

<and so on…>

The headers section must end with a blank line, so the last 4 bytes will always be:

<CR><LF><CR><LF> (<CR><LF> means the two bytes 0x0D, 0x0A)

After the message header is an optional message data section.

This driver takes a lean approach processing of HTTP request headers and will ignore many of the general headers. As it is generally safe to assume that the server will be used to send files that are compatible with all popular browsers the reading and storing of most header data is unnecessary.

When the HTTP driver receives the request, it looks to see if the file being requested is actually available and if it is it returns it. For an .htm HTML file this again is an ASCII text file that the HTTP server sends using one or more TCP packets. Typically an HTML file will typically include references to other files, such as images, that the clients browser then needs to also retrieve to add to the page that will be displayed on the browser. So the browser sends new requests to retrieve each of these and if they are available the HTTP driver also sends them in one of more TCP packets. In order to improve speed browsers will often attempt to open more than one TCP connection to a HTTP server when retrieving web pages so that multiple files may be transferred over the internet at the same time. If you define the HTTP driver to have multiple TCP sockets (in eth-http.h) it will allow the client to connect to as many of them as it wants and this can help improve overall delivery speed. If a browser tries to open more TCP connections than the HTTP server has the TCP driver will simply refuse the request and the browser will wait to download the additional files once already in progress files have completed downloading.

As the HTTP driver is sending HTML files it checks each byte looking for the special dynamic data marker described in the earlier HTML sections of this manual. Each time it finds this it reads the variable name and then calls the user application, allowing it to provide a dynamically generated string to be sent in place of the variable.

In addition to making requests the browser is also able to send inputs to the HTTP server, for instance in response to a user submitting a form or selecting an option. Responses may be sent as part of a normal GET request or using a POST request. In both instances the client browser specifies a file to be retrieved or activated but with data attached to the request to be processed first. The HTTP driver deals with these inputs in turn, passing each one fully decoded to the user application for it to store or process as desired. In the case of multipart form data and file uploads the data is passed to the user application byte by byte to allow for large data sizes and files.

Error Responses

If the HTTP server receives an invalid request or cannot return the information requested it returns a standard HTML error response, which in its basic form (and as implemented in this driver) is simply plain ASCII text indicating the error type.

HTTP Versions & RFC’s

HTTP version 1.1 is specified in RFC2616. RFC1945 specifies the previous versions HTTP 1.0 and HTTP 0.9.

Version 1.1 adds capabilities for conserving network bandwidth, improving security and error notification plus some other more minor things. Typically embedded systems serve small, simple web pages and therefore gain little benefit from supporting HTTP 1.1. They therefore may use HTTP 1.0 for simplicity. A browser that supports HTTP 1.1 would have no trouble communicating with an HTTP server that supports HTTP 1.0. However one of the advantages of HTTP 1.1 is that it allows persistent connections. With HTTP 1.0 each request requires a new connection, so if a client requests a web page that has several links to images it has to be make a new connection for every page and every image, with all the handshaking overhead required by this. HTTP 1.1 on the other hand has a default behaviour of persistent connections where a TCP connection is left open until either the client or server decides that the connection is complete or the server chooses to close it after a period of no activity.

This driver supports HTTP 1.1 with support for the following methods:-

GET, HEAD, POST.

HTTP 1.1 includes some other less used methods but these are not required to be supported by a HTTP 1.1 server (only GET and HEAD must be supported).

f) How The ARP Driver Works

ARP (specified in RFC 826) is incorporated as part of the basic TCP/IP driver. ARP packets are specified by the ARP type code in an Ethernet packet header. Whenever the driver receives an ARP packet it automatically checks to see if the target IP address is the same as the drivers IP address. If it is and it is an ARP request the driver automatically sends an ARP response. If it is a response the driver stores the responding devices MAC address ready for the driver or user application function that requested ARP.

To trigger an ARP request the arp_resolve_ip_address() function is called with the IP address to be resolved. The calling function should then call the arp_is_resolve_complete() function periodically to see if the response has been received, implementing its own timeout should no response be received.

g) How The DHCP Driver Works

DHCP (specified in RFC 2131) allows the driver to automatically obtain the settings it needs on a network from a DHCP server. Large networks may include one or more dedicated servers that provide DHCP. Small home and many simple business networks will typically have DHCP provided by a broadband DSL router or by a PC that is providing internet connection sharing. Using DHCP in your embedded device allows it to be connected to a network and without any user configuration to obtain its settings.

DHCP operates automatically and the only intervention the user application needs to make is to set it as active or not on power up (and at any time later). If disabled the user application instead specifies the manual IP address, subnet mask and gateway address to be used, allowing the products user interface to provide the option of automatic or manual network settings if desired. DHCP may be enabled or disabled at any time and the driver will respond accordingly.

When enabled the DHCP driver will attempt to contact a DHCP server on the local network by broadcasting a DHCP discover packet periodically using UDP. If there is a DHCP server on the network it will respond to the discover packet with a DHCP offer packet, broadcasting the response using UDP (it can’t sent the response directly as the driver has not yet been issued with an IP address). The offer packet will contain various fields, of which the following are read by the driver:

Client IP address, which is the IP address being offered by the DHCP server

Lease Time, which is the time period the driver may use this IP address for before it has to contact the DHCP server again with a new request.

Subnet Mask

Gateway, which is the IP address of the device on the local network that provides a connection onward to the next network (for instance the internet or the next level up of a corporate network). In the case of a router that is providing the DHCP server service this will be the same address as the DHCP server.

The driver stores all of these fields and the DHCP servers IP address and responds with a DHCP Request packet, again sent using a broadcast UDP packet. If defined the driver will also include an ASCII name field within the DHCP Request packet, which some DHCP servers will store for their name services.

The DHCP server finally confirms the process by returning a DHCP ACK packet, to the now assigned client IP address.

The DHCP driver has nothing else to do so sits idle, except for checking two timers that we’re started when the DHCP offer was last received. The eth_dhcp_1sec_renewal_timer variable is set to expire half way through the DHCP servers issued lease time. Once this timer expires (typically after several hours or days) the DHCP driver starts attempting to contact the DHCP server again to repeat the original process and renew is settings. If for some reason the DHCP server doesn’t respond after the many attempts that will have been made to contact it and the eth_dhcp_1sec_lease_timer variable expires, DHCP will change the status of the our_ip_address_is_valid variable to indicate that the IP settings are no longer valid and the TCP/IP driver must therefore remain offline until the DHCP server responds to the DHCP requests (that will continue to be sent reguarly).

h) How The NetBIOS Driver Works

The NetBIOS protocol (specified in RFC 1001 and RFC 1002) provides a simple method of searching for device by its name (up to 15 characters) on a local network. NetBIOS requests for the address of a queried name are sent as a broadcast UDP packet, and are therefore not passed through routers (routers do not forward broadcast packets).

The NetBIOS driver simply opens a UDP port and listens for broadcast packets received from anyone to its local port (the NetBIOS Nameservice Client Port 137). When a request packet is received the driver checks the query section to see if it contains a name which matches the name that has been set as the device name. If it does it replies to the sender with a NetBIOS response which includes the IP address.

i) How The DNS Driver Works

DNS is specified RFC 1034 and 1035. RFC 1034 is more conceptual in nature and RFC 1035 is the one for programmers to read to understand the format of DNS communications. Its basic function is to translate domain names meaningful to humans into numerical IP addresses. The Domain Name System also stores other types of information, such as the list of mail servers that accept email for a given domain.

A DNS query is started by calling the do_dns_query() function with the domain name requiring resolving and the dns type (host or MX mailserver). As long as the DNS driver is not busy carrying out a prior DNS operation it will setup the DNS state machine to send a DNS request.

The driver sends an ARP request to retrieve the MAC address of the network gateway device (which will have been learnt via DHCP or manually set by the user). Once the MAC address is retrieved the driver then attempts to open a UDP socket and send a DNS request packet.

The request specifies the domain name that is to be resolved and includes the recursion desired bit to indicate to the gateway device that it needs carry out a recursive query, where it will fully answer the query rather than simply return a partial answer.

The DNS driver then waits for a response (checking for a possible timeout error). As the DNS response can use compression (compression of names by repeated names or sections of names being re-used later on in the packet) the driver doesn’t check the response names (as this is unnecessary and would involve a great deal of complexity and ram space). Instead it checks the answer and additional sections of the response looking for the IP address.

DNS may return several IP addresses (for instance for large sites) and in this case the driver selects the first one.

The function that requested the DNS resolve should periodically call the check_dns_response() function to determine if DNS has completed (or failed).

j) How The POP3 Driver Works

The POP3 protocol (specified in RFC 1939) is a simple text-based protocol which is designed to support users with intermittent connections as well as permanent connections, so it is a perfect choice for many embedded devices requiring the ability to receive email. POP3 works over a TCP/IP connection using TCP server port 110.

When the process of retrieving email is started, by calling email_start_receive(), the POP3 state machine is setup to first take the URL of the POP3 server (defined as either a constant or variable string) and perform a DNS query, using the DNS driver.

Once the IP address of the POP3 server is returned the driver then sends an ARP request to retrieve the MAC address to communicate with. As a POP3 server will typically be on the internet and connected to via a gateway router of some form, the MAC address returned by the ARP driver will typically be the MAC address of the gateway, which will deal with forwarding communications on.

Once the MAC address is retrieved the driver then attempts to open a TCP connection with the POP3 server. This may take some time, if for instance the server is very busy.

Once the TCP connection is opened the server sends a greeting message. This is a text string that must start with “+OK” to be valid (the remainder of it can be anything). For example:

“+OK Hello there”

The driver looks for the leading “+” and if its there it continues on. In order to tell the POP3 server which mailbox is to be accessed and to start the authentication process the driver sends the “USER” command followed by a space character and then the username (which is typically the email address of the mailbox). The line is terminated by a carriage return and line feed. For example:

“USER me@yahoo.com<CR><LF>”

The server should then respond with a string that starts with “+OK” to indicate the mailbox is valid. For example:

“+OK Password required. “

The driver looks for the leading “+” and if its there it continues on. The password command is now sent with the mailbox password. For example:

“PASS mypassword <CR><LF>”

The server should again respond with a string that starts with “+OK” to indicate the password is accepted. For example:

“+OK logged in. “

The driver looks for the leading “+” and if its there it continues on. Next the driver uses the STAT command to retrieve the number of emails that are in the mailbox. For example:

“STAT<CR><LF>”

The server should respond with a fixed format string starting with “+OK”, then a single space, then the number of messages in the mailbox, a single space and finally the size of the mailbox in bytes. There may be additional characters after this but these are optional and are not required to follow a set format. For example:

“+OK 2 3530″

The driver looks for the leading “+OK ” and then stores the following number of messages value. Each message is now retrieved as follows:-

The driver sends a RETR command with the message number, for instance:

“RETR 1<CR><LF>”

The server will then respond with the requested message. This will consist of one or more TCP packets which will contain a continuous block of data which starts with a block of message headers, a blank line and then the email body. The headers contain all sorts of information of which only 2 fields are of particular interest – the email sender (the reply address) and the email subject. When the subject is encountered the driver calls a user application function (defined by POP3_PROCESS_RECEIVED_EMAIL_LINE_FUNCTION), sending it the subject string (and also the senders email address). This indicates to the user application that this is the start of a new message being retrieved. The driver continues reading each of the headers and once it reaches the blank line that marks the transition from headers to email body it then starts calling the user application function with each line of the email body retrieved, for the user application to parse and process as required. Note that whilst the body may be plain text, much email is actually MIME formatted. If decoding of MIME formatting or reception of MIME encoded file attachments (which are included in the email body) is desired by the user application this must be provided by it. This driver simply passes each line as it is retrieved. Often for an embedded application all a user wants to do is pass simple information such as configuration settings or commands and if the email is sent as plain text, even though it may still actually be MIME formatted, each line of the email text will be passed to the user application and can be parsed as desired.

The end of the email is indicated by:

“<CR><LF>.<CR><LF>”

(This 5 byte sequence is guaranteed not to appear anywhere within the contents of the email). One final call to the user application function is made by the driver indicating the end of the message. The return value from the function allows the user application to indicate to the driver if the message should be deleted or not.

If the message is to be deleted the driver sends the DELE command with the message number, for example:

“DELE 1<CR><LF>”

The driver looks for the leading “+OK ” in the response and then loops back to retrieve the next message.

Once all of the messages have been retrieved and optionally deleted the driver sends the QUIT command as shown:

“QUIT<CR><LF>”

It waits for the “+OK” response before then closing the TCP connection to the POP3 server and returning the POP3 driver state machine to the idle state.

Should an error occur at any point, for instance due to a timeout or unexpected response, the TCP connection is closed and the POP3 driver state machine is returned to the idle state.

If the user application wants to follow the progress of the POP3 process it can either monitor the state of the sm_pop3 variable or an optional string pointer is provided (selected using the DO_POP3_PROGRESS_STRING define) which is loaded with a user changeable string at each stage of the email receive process. This may be used for example to display progress on a devices screen.

k) How The SMTP Driver Works

The SMTP protocol (first specified in RFC 821, most recently in SMTP 5321) is a simple text-based protocol which is designed to transfer email across IP networks. Although used by servers to send and receive messages, client devices typically only use SMTP for sending messages and use POP3 or IMAP to retrieve messages from a mail server.

SMTP works over a TCP/IP connection using TCP typically on server port 25.

When the process of sending email is started, by calling email_start_send(), the SMTP state machine is setup to first take the URL of the SMTP server (defined as either a constant or variable string) and perform a DNS query, using the DNS driver.

Once the IP address of the SMTP server is returned the driver then sends an ARP request to retrieve the MAC address to communicate with. As a SMTP server will typically be on the internet and connected to via a gateway router of some form the MAC address returned by the ARP driver will typically be the MAC address of the gateway, which will deal with forwarding communications on.

Once the MAC address is retrieved the driver then attempts to open a TCP connection with the SMTP server. This may take some time, if for instance the server is very busy.

Once the TCP connection is opened the server sends a greeting message. This is a text string that must start with 3 digits to be valid (the remainder of it can be anything). For example:

“220 some.server.name ESMTP”

The leading 2 character is the important one and it indicates the server is happy (3 is used for data transfers, 4 & 5 indicate an error). Once received the driver sends one of two commands, followed by a domain identifier string and then followed by a carriage return and line feed as shown:

“HELO EMB<CR><LF>”

or

“EHLO EMB<CR><LF>”

HELO is the normal command.

EHLO is the alternative command that requests an authenticated login. You may select either when calling the email_start_send() function, with an authenticated login generally required when using a SMTP server that is located remotely from the local internet connection (to protect against spam use). The domain identifier string can be anything.

The server should respond with a string starting with a 3 digit value. The first character should be “2” to indicate that the server is happy. If the 3 digit number is followed by a hyphen “-“ character then there are more lines to be read, each of which will start with the 3 digit number. Each line will be indicating a capability of the server and the driver reads each line until the final line, indicated by no trailing “-“ is read. For example:

“250-the.server.name<CR><LF>”

“250-AUTH=LOGIN CRAM-MD5 PLAIN<CR><LF>”

“250 8BITMIME<CR><LF>”

The driver will now jump over the following login, username and password stages if an authenticated login has not been selected, or if it has it will proceed as follows.

The driver first sends the AUTH LOGIN command, as shown:

“AUTH LOGIN<CR><LF>”

It waits to receive the response string, which should start with a “3” to indicate the server is in data transfer mode (4 & 5 indicate an error). For example:

“334 VXNlcm5hbWU7<CR><LF>”

The driver then sends the username encoded using BASE64. For example:

“ZGV2ZWxALnVrWHX<CR><LF>”

It waits to receive the response string, which should start with a “3” to indicate the server is still in data transfer mode and happy. For example:

“334 UGFzc3YAq8<CR><LF>”

The driver then sends the password encoded using BASE64. For example:

“C2VJlNkj3FuQ<CR><LF>”

It waits to receive the response string, which should start with a “2” to indicate the server is happy. For example:

“235 go ahead<CR><LF>”

Now that the login has been completed the driver continues on in the same way if either authenticated login was select or not. The driver starts the send process with the MAIL FROM command, followed by a space character and then the email address the email is being sent from enclosed in angle brackets, for example:

“MAIL FROM: <embedded-device1@my-domain.com><CR><LF>”

It waits to receive the response string, which should start with a “2” to indicate the server is happy. For example:

“235 ok<CR><LF>”

The driver now sends the RCPT TO command, followed by a space character and then the destination email address enclosed in angle brackets, for example:

“RCPT TO: <john@yahoo.com><CR><LF>”

It waits to receive the response string, which should start with a “2” to indicate the server is happy. For instance:

“235 ok<CR><LF>”

The driver now sends the DATA command:

“DATA<CR><LF>”

It waits to receive the response string, which should start with a “3” to indicate the server is now in data transfer mode. For example:

“354 go ahead<CR><LF>”

The email message is now transferred to the server as a continuous block of data until it is complete. No response is given by the server until the transfer is complete (apart from TCP acknowledgements).

The first section of the message is the headers, which includes the FROM email address, which is the senders email address again (these fields will be read and used by the receiver of the email), the TO email address, which is the recipients email address again, and the email subject.

After this the email body is sent, the start of which is indicated by a blank line. As this driver allows a file to be attached emails are sent MIME encoded. Therefore first of all a constant MIME header block is sent to indicate the start of the text portion of the email. Once this has been sent a special user application function is called to request each byte of the email text. As TCP packets may need to be resent should a packet be lost, a variable is passed to the user application which is normally zero, but if not indicates that the application needs to be move back the specified number of bytes as the driver is resending the previous packet. Once the user application has passed all of the bytes of the email text it returns a value to indicate that it is complete.

If the user flagged that the email would include a file attachment when email_start_send() was originally called the driver now sends a MIME header for the file attachment portion of the email body. Once this has been sent the special user function is now called to request each byte of the file. Any type of file may be sent and the driver automatically BASE64 encodes the file so that binary data is suitable for sending via SMTP. Again, as TCP packets may need to be resent should a packet be lost, a variable is passed to the user application which is normally zero, but if not indicates that the application needs to be move back the specified number of bytes as the driver is resending the previous packet of the file data. Once the user application has passed all of the bytes of the file it returns a value to indicate that it is complete.

The driver sends the final MIME block and completes the email with the <CR><LF>.<CR><LF> marker.

It waits to receive the response string, which should start with a “2” to indicate the server is happy. For instance:

“250 ok<CR><LF>”

Finally the driver sends the QUIT command:

“QUIT<CR><LF>”

It waits for the response before then closing the TCP connection to the SMTP server and returning the SMTP driver state machine to the idle state.

Should an error occur at any point, for instance due to a timeout or unexpected response the TCP connection is closed and the SMTP driver state machine is returned to the idle state.

If the user application wants to follow the progress of the SMTP process it can either monitor the state of the sm_smtp variable or an optional string pointer is provided (selected using the DO_SMTP_PROGRESS_STRING define) which is loaded with a user changeable string at each stage of the email send process. This may be used for example to display progress on a user screen.

l) How The ICMP Driver Works

ICMP (which is specified in RFC 792) is a core protocol of the Internet Protocol Suite. Whilst the protocol provides such functions as error reporting, for an embedded device the only functionality that is typically required is responding to ping ICMP Echo requests. ICMP packets are marked with their own IP protocol value and when one is received the driver checks to see if it is an Echo request sent to its IP address. If it is the driver automatically responds with an ICMP reply, returning the same block of data received.

m) How The SNTP Driver Works

SNTP (which is specified in RFC 4330) is a simple protocol which allows devices to request the current time from a public NTP server. It is a less complex form of NTP and does not require the storing of information about previous communications so is ideal for embedded devices in applications where high resolution / accuracy timing is not required.

The response from a SNTP / NTP server is a 32 bit value which is the number of seconds since 00:00:00 on 1 January 1900. This value can then be used by the application and a typical example of usage is given in the ‘Adding SNTP Functionality’ section earlier in this manual.

To send a SNTP request the user application simply needs to call the sntp_get_time() function. The SNTP driver will then automatically use DNS to lookup the IP address of the SNTP server. If you are using a NTP service such as pool.ntp.org DNS will often retrieve a different IP address on successive calls, as these public NTP services typically distribute requests around a pool of NTP servers. Once the IP address is retrieved the driver will send an ARP request to obtain the MAC address for the server, which will typically return the MAC address of the local network gateway device that will forward the communications on to the actual SNTP server.

The driver then opens a UDP socket and transmits a SNTP request packet, followed by an immediate call of the user application function defined by SNTP_USER_AP_FUNCTION in eth-sntp.h. This marks the exact moment the request is sent to the SNTP server and allows the user application to start a timeout timer if desired so that should the response not be received quickly enough, due to internet delays, it can determine this. The driver then waits for the response and as soon as it is received it again calls the SNTP_USER_AP_FUNCTION function with the received time value.

The SNTP driver incorporates timeout timers for each of the steps of the SNTP process and the user application function is notified if a timeout should occur and the SNTP driver has given up.

If higher accuracy timing is required then it is a good idea to select a SNTP / NTP server to send the request to based on geographical location, so that less time is taken for the request and response to travel across the internet.