Programming:  Pop3 Client

The following shows the implementation of an application layer - "Sirena" - built on top of my TCP/IP stack. I won't explain the Pop3 protocol, see the its RFC for that.

Paradoxically the simpler the system is the more complex is to develop sophisticated applications. For this reason what follows requires some "background", and when i say "some"... :)

The design of a TCP/IP/PPP/SERIAL stack on PVOS must take into account its single threaded programming model. Me not being Casio(C) implies that i can't modify the system for some kind of integration, so the stack is a stand-alone system. Once the Dialer has finished its task, an ad-hoc dispatcher takes control and yields it to several layers the stack is made of. Thus it is important that each "thread" called by the scheduler cooperates yielding the control back with minimum overhead. The only programmable thread is the application thread, other threads are used by the stack.

The stack is still in its "Baby" age. It is not possible to build a library out of it without changing the philosophy of applications development. So no Lib will ever be provided. The stack was just a challenge afterall.

Here's the code for the application thread. A small number of functions MUST be implemented, other functions are application dependent.


Some system headers. DisableAppButton is provided by the framework.

#include "Sys.h"
#include "ppp.h"
#include "Tcp.h"
#include "Stack.h"
#include "Application.h"

#include "Memo.h"

extern void DisableAppButton();

Every layer on the stack is a finite state machine, it is not surprising that our Pop3 client implements one. Pop3_States defines the states. This is due to the asynchronous operation mode imposed by the single threaded model. Every state has its callback function. "Sock" lets us plug into the internet. "Pop3_User", "Pop3_Pass" and "Pop3_Server" (IP) should get their values in this module, but they're set up elsewhere.

#define POP3_PORT		110
#define POP3_ENDLIST	"\r\n.\r\n"

typedef enum 
{ 
	POP3_STARTING, 
	POP3_USER_SENT, 
	POP3_PASS_SENT, 
	POP3_LIST_SENT, 
	POP3_WORKING, 
	POP3_CLOSING

} Pop3_States;

void DataArrival(void *socket, BYTE *buf, int len);
int AddToBuffer(char *Text);
int TCPHandler(void *sock, BYTE *buf, int len, int type);
void Pop3_Starting();
void Pop3_UserSent();
void Pop3_PassSent();
void Pop3_ListSent();
void Pop3_Working();
void Pop3_Closing();
void OnConnect();

char Pop3_User[24]; 
char Pop3_Pass[24]; 
unsigned long Pop3_Server;

tcp_Socket sock;
WORD AppStarted = 0;
Pop3_States CurrentState;
int EmailCounter = 0;
int FileSize;

#define TBSIZE	(2048 + 24)
#define BUF_ERR_NONE		0x01
#define BUF_ERR_OVERFLOW	0x02
char TextBuffer[TBSIZE];

#define MAX_EMAIL_LEN		16384
char far DataStore[MAX_EMAIL_LEN + 64];

#define MAX_MESSAGES		128
int SelectedMessages[MAX_MESSAGES];
int Msg_Cnt = 0;

char Title[64];

"AddToBuffer" is the first patch to my TCP. All incoming data is unbuffered. Our PV can quickly run out of memory, so buffering is provided by the application if needed. Buffers cost!

int AddToBuffer(char *Text)
{
	if (strlen(TextBuffer) + strlen(Text) > TBSIZE - 1)
		return BUF_ERR_OVERFLOW;

	strcat(TextBuffer, Text);

	return BUF_ERR_NONE;
}

"Application_Handler" is continuously fired by the dispatcher to let the application do its stuff. PollAbort tests the abort button, PollApp tests the Pop3 button, let's say these are hard-icons, provided by the framework as well. NOTICE THAT NOTHING IS BLOCKING, these functions return immediately. When Application_Handler returns 0 it means the thread is done, when it returns 1 it will be called again. I should have started the state mangement here... ok, it will be up to the next release.

int Application_Handler()
{
	
		if (PollAbort())
		{
			AppStarted = 0;
			return 0;
		}

		if (AppStarted == 0)
			if (PollApp())	
			{
				AppStarted = 1;
				DisableAppButton();
				OnConnect();
			}

		return 1;
}

The TCP layer communicates with the Application through this handler function. Each tcp_Socket may be assigned a different handler, if you use the same handler the first parameter tells which tcp_Socket the event refers to. "type" specifies why we are called. We should handle the events correctly, but we simply print them. PrintCR prints a string followed by CR to my PVShell.

int TCPHandler(tcp_Socket *socket, BYTE *buf, int len, int type)
{
	switch (type)
	{
		case REASON_ABORT:
			PrintCR("\r\n[TCPHandler] Aborted");
		break;

		case REASON_CLOSED:
			PrintCR("\r\n[TCPHandler] Closed");
		break;

		case REASON_RESET:
			PrintCR("\r\n[TCPHandler] Reset");
		break;

		case REASON_DATA:
		{
			DataArrival(socket, buf, len);
		}
		break;

		case REASON_CONNECT:
			PrintCR("\r\n[TCPHandler] Connected");			
		break;
	}

	return 0;
}

If data is available we read it here and add it to the buffer. This function delivers data according to the current state of the machine.

void DataArrival(tcp_Socket *socket, BYTE *buf, int len)
{
	buf[len] = 0;
	AddToBuffer((char *)buf);

	switch (CurrentState)
	{
		case POP3_STARTING:
			Pop3_Starting();
			break;

		case POP3_USER_SENT:
			Pop3_UserSent();
			break;

		case POP3_PASS_SENT: 
			Pop3_PassSent();
			break;

		case POP3_LIST_SENT: 
			Pop3_ListSent();
			break;

		case POP3_WORKING:
			Pop3_Working();
			break;

		case POP3_CLOSING:
			Pop3_Closing();
			break;
	}
}

When we press the App button "Pop3", OnConnect gets called. Here we tcp_"Open" the connection. We provide a tcp_Socket handle, a local port number (yes we can choose it, pay attention!!!), a remote IP and port and we hook the Socket Event Handler we saw before.

void OnConnect() 
{
	tcp_Open(&sock, 1200, Pop3_Server, POP3_PORT, TCPHandler);
	CurrentState = POP3_STARTING;
}

When we have the list of messages, we parse it to filter out the long ones.

void ParseList(char *pBuffer)
{
	int i, n, b;
	char *pa = pBuffer, *pb;

	Msg_Cnt = 0;

	pa = strstr(pa, "\r\n") + 2;

	for (i = 0; i < MAX_MESSAGES; i++)
	{
		pb = strstr(pa, "\r\n");

		if (pb == NULL)
			break;

		*pb = 0;
		sscanf(pa, "%d%d", &n,  &b);

		if (b < MAX_EMAIL_LEN)
		{
			SelectedMessages[Msg_Cnt] = n;
			Msg_Cnt++;
		}

		pb += 2;
		if (*pb == '.')
			break;

		pa = pb;
	}
}

This tells the Pop3 server we are closing. tcp_Write send the data stream out to the server, through the open socket.

void DoQuit()
{
	char StrOut[64];

	CurrentState = POP3_CLOSING;
	Print(TextBuffer);
	sprintf(StrOut, "QUIT\r\n");
	PrintCR(StrOut);
	tcp_Write(&sock, (BYTE *)StrOut, strlen(StrOut));
	return;
}

If we have more messages to retrieve we ask for them here.

int NextEmail()
{
	char StrOut[64];

	if (EmailCounter == Msg_Cnt)
		return 0;

	FileSize = -1;
	sprintf(StrOut, "RETR %d\r\n", SelectedMessages[EmailCounter]);
	
	PrintCR(StrOut);
	tcp_Write(&sock, (BYTE *)StrOut, strlen(StrOut));
	return 1;
}

This is the first callback state function we encounter. They are all very similar. The next state is triggered when we have gathered enough info into our buffer. In this case after the first "\r\n" couple. These are just greetings. We are ready to send the username.

void Pop3_Starting()
{
	char StrOut[64];
	int le = strlen(TextBuffer);

	if (!(TextBuffer[le - 2] == '\r' && TextBuffer[le - 1] == '\n'))
		return;

	Print("\r\n"); Print(TextBuffer);
	TextBuffer[0] = 0;
	CurrentState = POP3_USER_SENT;
	sprintf(StrOut, "USER %s\r\n", Pop3_User);
	tcp_Write(&sock, (BYTE *)StrOut, strlen(StrOut));
}

Called after the username is sent. It tests for a positive response and goes on or quits.

void Pop3_UserSent()
{
	char StrOut[64];
	int le = strlen(TextBuffer);

	if (!(TextBuffer[le - 2] == '\r' && TextBuffer[le - 1] == '\n'))
		return;

	Print("\r\n"); Print(TextBuffer);

	if (TextBuffer[0] != '+')
	{
		PrintCR("\r\nUSER failed");
		/*tcp_Close(&sock); */
		DoQuit();
		return;
	}

	TextBuffer[0] = 0;
	CurrentState = POP3_PASS_SENT;
	sprintf(StrOut, "PASS %s\r\n", Pop3_Pass);
	tcp_Write(&sock, (BYTE *)StrOut, strlen(StrOut));
}

Called after password was sent. After we are validated we ask for the message list.

void Pop3_PassSent()
{
	char StrOut[64];
	int le = strlen(TextBuffer);

	if (!(TextBuffer[le - 2] == '\r' && TextBuffer[le - 1] == '\n'))
		return;

	Print("\r\n"); Print(TextBuffer);

	if (TextBuffer[0] != '+')
	{
		PrintCR("\r\nPASS failed");
		DoQuit();
		return;
	}

	TextBuffer[0] = 0;
	CurrentState = POP3_LIST_SENT;
	sprintf(StrOut, "LIST\r\n");
	tcp_Write(&sock, (BYTE *)StrOut, strlen(StrOut));
}

We're receiving the list. When done we can start retrieving the first selected message.

void Pop3_ListSent()
{
	if (!strstr(TextBuffer, POP3_ENDLIST))
		return;

	Print("\r\n"); Print(TextBuffer);

	ParseList(TextBuffer);

	if (Msg_Cnt == 0)
	{
		DoQuit();
		return;
	}

	TextBuffer[0] = 0;

	CurrentState = POP3_WORKING;

	EmailCounter = 0;
	NextEmail();
}

This is the main part of the message process. Here we receive the message in chunks and put them together. We need to trim some email header and reorder the others so that the from field comes first. Why?? ... We can also count on a PrintF to print formatted text on the PVShell, great!

void Pop3_Working()
{
	char *p, *pe;
	bool msg_done = FALSE;

	p = TextBuffer;	
	/*Print(p);*/

	/* error, quit */
	if (FileSize == -1 && TextBuffer[0] == '-')
	{
		DoQuit();
		return;
	}

	/* positive reply, set up the buffers */
	if (FileSize == -1 && TextBuffer[0] == '+')
	{		
		p = strstr(TextBuffer, "\r\n");
		p += 2;
		FileSize = 0;
		DataStore[0] = 0;
		Title[0] = 0;
	}

	/* reached the end of the message */
	if (pe = strstr(p, POP3_ENDLIST))
	{
		*pe = 0;
		msg_done = TRUE;
	}

	/* append */
	far_strcat(DataStore, p);
	FileSize += strlen(p);	
	TextBuffer[0] = 0;

	/* if not done wait for more data*/
	if (!msg_done)		
		return;

	/* extract the title */
	GetFromField(DataStore, Title);
	CutHeaders(DataStore);

	/* insert the title */
	buf_insert(DataStore, MAX_EMAIL_LEN, DataStore, strlen(Title));
	far_memcpy(DataStore, Title, strlen(Title));

	/* save the buffer */
	Print("\r\nStoring..."); Memo_Store(DataStore);  PrintCR("ok");
	DataStore[0] = 0;

	/* show the progress */
	PrintF("[%d]\r\n", FileSize);
	
	TextBuffer[0] = 0;

	EmailCounter++;
	if (NextEmail() == 0)
		DoQuit();

}

We are closing, actually waiting for the server to close.

void Pop3_Closing()
{
	if (!strstr(TextBuffer, "\r\n"))
		return;

	Print("\r\n"); Print(TextBuffer);
	TextBuffer[0] = 0;
}

Do we need this one? I don't think so, but here is tcp_Close anyway!!!!

void OnClose() 
{
	tcp_Close(&sock);
}