Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
745 lines (586 sloc) 17.4 KB
/******************************************************************************
Filename: WiSever.cpp
Description: Main library code for the WiServer library
******************************************************************************
Copyright(c) 2009 Mark A. Patel All rights reserved.
This program is free software; you can redistribute it and/or modify it
under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59
Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Contact Information:
Author Date Comment
---------------------------------------------------------------
Mark A. Patel 05/27/2009 Initial version
Mark A. Patel 06/05/2009 Revised to work with stack version 1.1,
plus added various enhancements such as
multi-pass transmission, local client
checks, activity LED support, etc.
Joe Touch 12/14/2011 Ported to arduino-1.0 compiler
*****************************************************************************/
#include "Arduino.h"
#include "WiServer.h"
extern "C" {
#include "g2100.h"
#include "spi.h"
#include "uip.h"
#include "server.h"
#include "global-conf.h"
void stack_init(void);
void stack_process(void);
}
#ifdef APP_WISERVER
#define CR 13
#define LF 10
// Strings stored in program memory (defined in strings.c)
extern const prog_char httpGET[];
extern const prog_char httpOK[];
extern const prog_char httpNotFound[];
extern const prog_char http10[];
extern const prog_char post[];
extern const prog_char get[];
extern const prog_char authBasic[];
extern const prog_char host[];
extern const prog_char userAgent[];
extern const prog_char contentTypeForm[];
extern const prog_char contentLength[];
extern const prog_char status[];
extern const prog_char base64Chars[];
/* GregEigsti - jrwifi submitted WiServer stability fix */
static char get_string_global[WISERVER_GET_STRING_MAX];
/* Application's callback function for serving pages */
pageServingFunction callbackFunc;
/* Digital output pin to indicate TX activity */
char txPin = -1;
/* Digital output pin to indicate RX activity */
char rxPin = -1;
void Server::init(pageServingFunction function) {
// WiShield init
zg_init();
#ifdef USE_DIG0_INTR
attachInterrupt(0, zg_isr, LOW);
#endif
#ifdef USE_DIG8_INTR
// set digital pin 8 on Arduino
// as ZG interrupt pin
PCICR |= (1<<PCIE0);
PCMSK0 |= (1<<PCINT0);
#endif
while(zg_get_conn_state() != 1) {
zg_drv_process();
}
// Start the stack
stack_init();
// Store the callback function for serving pages
// and start listening for connections on port 80 if
// the function is non-null
callbackFunc = function;
if (callbackFunc) {
// Listen for server requests on port 80
uip_listen(HTONS(80));
}
#ifdef DEBUG_VERBOSE
Serial.println("WiServer init called");
#endif // DEBUG_VERBOSE
}
#ifdef USE_DIG8_INTR
// PCINT0 interrupt vector
ISR(PCINT0_vect)
{
zg_isr();
}
#endif
void Server::setIndicatorPins(int tx, int rx) {
// Store the pin numbers
txPin = tx;
rxPin = rx;
// Set pin modes as needed
if (tx != -1) pinMode(tx, OUTPUT);
if (rx != -1) pinMode(rx, OUTPUT);
}
/*
* Sets the TX pin (if enabled) to the specified value (HIGH or LOW)
*/
void setTXPin(byte value) {
if (txPin != -1) digitalWrite(txPin, value);
}
/*
* Sets the RX pin (if enabled) to the specified value (HIGH or LOW)
*/
void setRXPin(byte value) {
if (rxPin != -1) digitalWrite(rxPin, value);
}
/******* Generic printing and sending functions ********/
size_t Server::write_P(const char data[], int len) {
while (len-- > 0) {
this->write(pgm_read_byte(data++));
}
return len;
}
size_t Server::print_P(const char s[]) {
char c = pgm_read_byte(s);
size_t count = 0;
while (c) {
this->print(c);
c = pgm_read_byte(++s);
count++;
}
return count;
}
size_t Server::println_P(const char c[]) {
size_t count = 0;
count += this->print_P(c);
count += this->println();
return count;
}
size_t Server::printTime(long t) {
size_t count = 0;
long secs = t / 1000;
int mins = (int)(secs / 60);
int hours = mins / 60;
hours %= 24;
count += this->print(hours / 10);
count += this->print(hours % 10);
count += this->print(':');
mins %= 60;
count += this->print(mins / 10);
count += this->print(mins % 10);
count += this->print(':');
secs %= 60;
count += this->print(secs / 10);
count += this->print(secs % 10);
return count;
}
/*
* Writes a byte to the virtual buffer for the current connection
*/
size_t Server::write(uint8_t b) {
// Make sure there's a current connection
if (uip_conn) {
// Check if the cursor is within the range that maps to the uip_appdata buffer
// (and we'll increment the cursor while we're at it)
int offset = (int)(uip_conn->appstate.cursor++) - uip_conn->appstate.ackedCount;
if ((offset >= 0) && (offset < (int)uip_conn->mss)) {
// Write the byte to the corresponding location in the buffer
*((char*)uip_appdata + offset) = b;
return 1;
}
}
return 0;
}
/*
* Sends the real data in the current connection's virtual buffer
*/
void send() {
uip_tcp_appstate_t *app = &(uip_conn->appstate);
// Find the intersection of the virtual buffer and the real uip buffer
int len = (int)app->cursor - app->ackedCount;
len = len < 0 ? 0 : len;
len = len > (int)uip_conn->mss ? (int)uip_conn->mss : len;
#ifdef DEBUG
Serial.print("TX ");
Serial.print(len);
Serial.println(" bytes");
#endif // DEBUG
#ifdef DEBUG_VERBOSE
Serial.print(app->ackedCount);
Serial.print(" - ");
Serial.print(app->ackedCount + len - 1);
Serial.print(" of ");
Serial.println((int)app->cursor);
#endif // DEBUG_VERBOSE
// Send the real bytes from the virtual buffer and record how many were sent
uip_send(uip_appdata, len);
app->sentCount = len;
setTXPin(HIGH);
}
/******* Server mode functions ********/
/*
* Processes a line of data in an HTTP request. This function looks
* for GET and saves a copy of the URL in the current connection's
* server request. It also sets the request's isValid flag if the
* URL has been saved and an empty line is found.
*/
boolean processLine(char* data, int len) {
// Check for a valid GET line
if ((uip_conn->appstate.request == NULL) && (strncmp_P(data, httpGET, 4) == 0)) {
// URL starts at the '/'
char* start = data + 4;
// Find trailing space after the URL
data = start;
char* end = data + len;
while (++data < end) {
if (' ' == *data) {
// Replace the space with a NULL to terminate it
*(data++) = '\0';
// copy the URL to the static storage
uip_conn->appstate.request = get_string_global;
strncpy(get_string_global, start, WISERVER_GET_STRING_MAX - 1);
// The following set is implicit. get_string_global is allocated in
// the BSS so it starts with a NULL last char and it stays there
// as long as we never overwrite it
// get_string_global[WISERVER_GET_STRING_MAX - 1] = '\0';
return false;
}
}
// No space, not valid
}
return (len == 0);
}
/*
* Processes a packet of data that supposedly contains an HTTP request
* This function looks for CR/LF (or just LF) and calls processLine
* with each line of data found.
*/
boolean processPacket(char* data, int len) {
// Address after the last byte of data
char* end = data + len;
// Start of current line
char* start = data;
// Scan through the bytes in the packet looking for a Line Feed character
while (data < end) {
if (*data == LF) {
// Determine the length of the line excluding the Line Feed
int lineLength = data - start;
if (*(data - 1) == CR) {
lineLength--;
}
*(start + lineLength) = 0;
// Process the line
if (processLine(start, lineLength)) {
return true;
}
// Set up for the start of the next line
start = ++data;
} else {
// Go to the next byte
data++;
}
}
return false;
}
/*
* Attempts to send the requested page for the current connection
*/
void sendPage() {
// Reset the virtual buffer cursor
uip_conn->appstate.cursor = 0;
// Start off with an HTTP OK response header and a blank line
WiServer.println_P(httpOK);
WiServer.println();
// Call the application's 'sendPage' function and ask it to
// generate the requested page content.
if (!callbackFunc((char*)uip_conn->appstate.request)) {
// The URL is not recognized by the sketch
// Reset the cursor and overwrite the HTTP OK header with a 404 message
uip_conn->appstate.cursor = 0;
WiServer.println_P(httpNotFound);
WiServer.println();
#ifdef DEBUG_VERBOSE
Serial.println("URL Not Found");
#endif // DEBUG_VERBOSE
}
// Send the 'real' bytes in the buffer
send();
}
boolean Server::sendInProgress() {
return false; // FIX ME
}
boolean Server::clientIsLocal() {
// Check if there is a current connection
if (uip_conn != NULL) {
// Check if the remote host is local to the server
uip_ipaddr_t hostaddr, mask;
// Get the server's address
uip_gethostaddr(&hostaddr);
// Get the subnet mask
uip_getnetmask(&mask);
// Compare with the client's address
return uip_ipaddr_maskcmp(&hostaddr, uip_conn->ripaddr, &mask);
}
return false;
}
/*
* Handles high-level server communications
*/
void server_task_impl() {
// Get the connection's app state
uip_tcp_appstate_t *app = &(uip_conn->appstate);
if (uip_connected()) {
#ifdef DEBUG
Serial.println("Server connected");
#endif // DEBUG
// Initialize the server request data
app->ackedCount = 0;
app->request = NULL;
}
if (uip_newdata()) {
setRXPin(HIGH);
// Process the received packet and check if a valid GET request had been received
if (processPacket((char*)uip_appdata, uip_datalen()) && app->request) {
#ifdef DEBUG
Serial.print("Processing request for ");
Serial.println((char*)app->request);
#endif // DEBUG
sendPage();
}
}
// Did we get an ack for the last packet?
if (uip_acked()) {
// Record the bytes that were successfully sent
app->ackedCount += app->sentCount;
app->sentCount = 0;
// Check if we're done or need to send more content for this
// request
if (app->ackedCount == (int)app->cursor) {
// Done with the current request and connection
uip_close();
} else {
// Generate the content again to send the next packet of data
sendPage();
}
}
// Check if we need to retransmit
if (uip_rexmit()) {
// Send the same data again (same ackedCount value)
sendPage();
}
if (uip_aborted() || uip_closed() || uip_timedout()) {
// Check if a URL was stored for this connection
if (app->request != NULL) {
#ifdef DEBUG
Serial.println("Server connection closed");
#endif // DEBUG
// Free RAM and clear the pointer
// GregEigsti - jrwifi submitted WiServer stability fix
// free(app->request);
app->request = NULL;
}
}
}
/******* Client mode functions ********/
#ifdef ENABLE_CLIENT_MODE
/* Linked list of queued client requests */
GETrequest* queue = NULL;
void Server::submitRequest(GETrequest *req) {
// Check for an empty queue
if (queue == NULL) {
// Point to the new request
queue = req;
} else {
// Find the tail of the queue
GETrequest* r = queue;
while (r->next != NULL) {
r = r->next;
}
// Append the new request
r->next = req;
}
// Set the request as being active
req->active = true;
}
/**
* Sends the request for the current connection
*/
void sendRequest() {
uip_tcp_appstate_t *app = &(uip_conn->appstate);
GETrequest *req = (GETrequest*)app->request;
// Reset the virtual buffer
app->cursor = 0;
// Indicates if this is a POST request (instead of a GET)
// Main difference is that POST requests have a body and a
// callback function to generate said body
bool isPost = req->body != NULL;
// Write out the request header
WiServer.print_P(isPost ? post : get);
WiServer.print(req->URL);
WiServer.println_P(http10);
// Host name
WiServer.print_P(host);
WiServer.println(req->hostName);
// Auth data (if applicable)
if (req->auth) {
WiServer.print_P(authBasic);
WiServer.println(req->auth);
}
// User agent (WiServer, of course!)
WiServer.println_P(userAgent);
if (isPost) {
// Since a post has a body after the blank header line, it has to include
// an accurate content length so that the server knows when it has received
// all of the body data.
char* lengthFieldPos; // Cursor position where the content length place holder starts
char* contentStart; // Start of the body
char* contentEnd; // End of the body
// Just form data for now
WiServer.println_P(contentTypeForm);
// Content length line (with 4-space placeholder for the value)
WiServer.println_P(contentLength);
// Make a note of where the place holder is so we can fill it in later
lengthFieldPos = app->cursor - 6; // 6 bytes for CR, LF, and 4 spaces
// Blank line to indicate end of header
WiServer.println();
// Body starts here
contentStart = app->cursor;
// Print the body preamble if the request has one
if (req->bodyPreamble) {
WiServer.print(req->bodyPreamble);
}
// Have the sketch provide the body for the POST
req->body();
// Body ends here
contentEnd = app->cursor;
// Move the cursor back to the content length value and write in the real length
app->cursor = lengthFieldPos;
WiServer.print((int)(contentEnd - contentStart));
// Put the cursor back at the end of the body so that all of the data gets sent
app->cursor = contentEnd;
} else {
// Blank line to indicate end of GET header
WiServer.println();
}
// Send the 'real' bytes in the buffer
send();
}
/**
* Handle client communications
*/
void client_task_impl() {
uip_tcp_appstate_t *app = &(uip_conn->appstate);
GETrequest *req = (GETrequest*)app->request;
if (uip_connected()) {
#ifdef DEBUG
Serial.print("Connected to ");
Serial.println(req->hostName);
#endif // DEBUG
app->ackedCount = 0;
sendRequest();
}
// Did we get an ack for the last packet?
if (uip_acked()) {
// Record the bytes that were successfully sent
app->ackedCount += app->sentCount;
app->sentCount = 0;
// Check if we're done or need to send more content for this
// request
if (app->ackedCount != (int)app->cursor) {
// Generate the post again to send the next packet of data
sendRequest();
}
}
if (uip_rexmit()) {
sendRequest();
}
if (uip_newdata()) {
setRXPin(HIGH);
#ifdef DEBUG
Serial.print("RX ");
Serial.print(uip_datalen());
Serial.print(" bytes from ");
Serial.println(req->hostName);
#endif // DEBUG
// Check if the sketch cares about the returned data
if ((req->returnFunc) && (uip_datalen() > 0)){
// Call the sketch's callback function
req->returnFunc((char*)uip_appdata, uip_datalen());
}
}
if (uip_aborted() || uip_timedout() || uip_closed()) {
if (req != NULL) {
#ifdef DEBUG
Serial.print("Ended connection with ");
Serial.println(req->hostName);
#endif // DEBUG
if (req->returnFunc) {
// Call the sketch's callback function with 0 bytes to indicate End Of Data
req->returnFunc((char*)uip_appdata, 0);
}
// Remove the request from the connection
app->request = NULL;
// Request is no longer active
req->active = false;
}
}
}
char getChar(int nibble) {
return pgm_read_byte(base64Chars + nibble);
}
void storeBlock(char* src, char* dest, int len) {
dest[0] = getChar(src[0] >> 2);
dest[1] = getChar(((src[0] & 0x03) << 4) | ((src[1] & 0xf0) >> 4));
dest[2] = len > 1 ? getChar(((src[1] & 0x0f) << 2) | ((src[2] & 0xc0) >> 6)) : '=';
dest[3] = len > 2 ? getChar(src[2] & 0x3f ) : '=';
}
char* Server::base64encode(char* data) {
int len = strlen(data);
int outLenPadded = ((len + 2) / 3) << 2;
char* out = (char*)malloc(outLenPadded + 1);
char* outP = out;
while (len > 0) {
storeBlock(data, outP, min(len,3));
outP += 4;
data += 3;
len -= 3;
}
*(out + outLenPadded) = 0;
return out;
}
#endif // ENABLE_CLIENT_MODE
/********* High-level WiServer functions ***********/
/*
* This function is called by uip whenever a stack event occurs
*/
void server_app_task() {
// Clear the activity pins
setTXPin(LOW);
setRXPin(LOW);
// Check for an active connection
if (uip_conn) {
// Is the connection for local port 80?
if (uip_conn->lport == HTONS(80)) {
// Server mode
server_task_impl();
} else {
#ifdef ENABLE_CLIENT_MODE
// Client mode
client_task_impl();
#endif // ENABLE_CLIENT_MODE
}
}
}
/*
* Called by the sketch's main loop
*/
void Server::server_task() {
// Run the stack state machine
stack_process();
// Run the driver
zg_drv_process();
#ifdef ENABLE_CLIENT_MODE
// Check if there is a pending client request
if (queue) {
// Attempt to connect to the server
struct uip_conn *conn = uip_connect(&(queue->ipAddr), queue->port);
if (conn != NULL) {
#ifdef DEBUG_VERBOSE
Serial.print("Got connection for ");
Serial.println(queue->hostName);
#endif // DEBUG_VERBOSE
// Attach the request object to its connection
conn->appstate.request = queue;
// Move the head of the queue to the next request in the queue
queue = queue->next;
// Clear the next pointer of the connected request
((GETrequest*)conn->appstate.request)->next = NULL;
}
}
#endif // ENABLE_CLIENT_MODE
}
// Single instance of the server
Server WiServer;
#endif /* APP_WISERVER */