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);
}
|