diff options
Diffstat (limited to 'Libraries/WiShield/WiServer.cpp')
| -rw-r--r-- | Libraries/WiShield/WiServer.cpp | 737 |
1 files changed, 737 insertions, 0 deletions
diff --git a/Libraries/WiShield/WiServer.cpp b/Libraries/WiShield/WiServer.cpp new file mode 100644 index 0000000..b3c4018 --- /dev/null +++ b/Libraries/WiShield/WiServer.cpp @@ -0,0 +1,737 @@ +/****************************************************************************** + + 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. + + + *****************************************************************************/ + + +#include "WProgram.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 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[]; + + + +/* 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; + +/* Enables basic log messages via Serial */ +boolean verbose = false; + + +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 = true; + Serial.println("WiServer init called"); +#endif // DEBUG +} + +#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); +} + + +void Server::enableVerboseMode(boolean enable) { + verbose = enable; +} + + + +/******* Generic printing and sending functions ********/ + + +void Server::write_P(const char data[], int len) { + while (len-- > 0) { + this->write(pgm_read_byte(data++)); + } +} + + +void Server::print_P(const char s[]) { + char c = pgm_read_byte(s); + while (c) { + this->print(c); + c = pgm_read_byte(++s); + } +} + + +void Server::println_P(const char c[]) { + this->print_P(c); + this->println(); +} + + + +void Server::printTime(long t) { + + long secs = t / 1000; + int mins = (int)(secs / 60); + int hours = mins / 60; + + hours %= 24; + this->print(hours / 10); + this->print(hours % 10); + this->print(':'); + + mins %= 60; + this->print(mins / 10); + this->print(mins % 10); + this->print(':'); + + secs %= 60; + this->print(secs / 10); + this->print(secs % 10); +} + + +/* + * Writes a byte to the virtual buffer for the current connection + */ +void 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; + } + } +} + + +/* + * 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; + + if (verbose) { + Serial.print("TX "); + Serial.print(len); + Serial.println(" bytes"); + } + +#ifdef DEBUG + Serial.print(app->ackedCount); + Serial.print(" - "); + Serial.print(app->ackedCount + len - 1); + Serial.print(" of "); + Serial.println((int)app->cursor); +#endif // DEBUG + + // 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(data, "GET /", 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; + // Compute length of the URL including the NULL + int len = data - start; + // Allocate space for the URL and copy the contents + uip_conn->appstate.request = malloc(len); + memcpy(uip_conn->appstate.request, start, len); + 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 + Serial.println("URL Not Found"); +#endif // DEBUG + } + // 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()) { + + if (verbose) { + Serial.println("Server connected"); + } + + // 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) { + if (verbose) { + Serial.print("Processing request for "); + Serial.println((char*)app->request); + } + 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) { + if (verbose) { + Serial.println("Server connection closed"); + } + + // Free RAM and clear the pointer + 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()) { + + if (verbose) { + Serial.print("Connected to "); + Serial.println(req->hostName); + } + 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); + + if (verbose) { + Serial.print("RX "); + Serial.print(uip_datalen()); + Serial.print(" bytes from "); + Serial.println(req->hostName); + } + + // 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) { + if (verbose) { + Serial.print("Ended connection with "); + Serial.println(req->hostName); + } + + 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 + Serial.print("Got connection for "); + Serial.println(queue->hostName); +#endif // DEBUG + + // 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 */ |
