New HTTP protocol and test code
authorDerek Pryor <dmpryor@users.sourceforge.net>
Fri, 11 Aug 2006 14:13:02 +0000 (14:13 +0000)
committerDerek Pryor <dmpryor@users.sourceforge.net>
Fri, 11 Aug 2006 14:13:02 +0000 (14:13 +0000)
src/include/gpxe/http.h [new file with mode: 0644]
src/net/tcp/http.c [new file with mode: 0644]
src/tests/dhcptest.c
src/tests/httptest.c [new file with mode: 0644]

diff --git a/src/include/gpxe/http.h b/src/include/gpxe/http.h
new file mode 100644 (file)
index 0000000..02c9be4
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef _GPXE_HTTP_H
+#define _GPXE_HTTP_H
+
+/** @file
+ *
+ * Hyper Text Transport Protocol
+ *
+ */
+
+#include <stdint.h>
+#include <gpxe/tcp.h>
+#include <gpxe/async.h>
+
+/** HTTP default port */
+#define HTTP_PORT 80
+
+enum http_state {
+       HTTP_INIT_CONN = 0,
+       HTTP_REQUEST_FILE,
+       HTTP_PARSE_HEADER,
+       HTTP_RECV_FILE,
+       HTTP_DONE,
+};
+
+/**
+ * A HTTP request
+ *
+ */
+struct http_request;
+
+struct http_request {
+       /** TCP connection for this request */
+       struct tcp_connection tcp;
+       /** Current state */
+       enum http_state state;
+        /** File to download */
+        const char *filename;
+        /** Size of file downloading */
+        size_t file_size;
+       /** Number of bytes recieved so far */
+       size_t file_recv;
+       /** Callback function
+        *
+        * @v http      HTTP request struct
+        * @v data      Received data
+        * @v len       Length of received data
+        *
+        * This function is called for all data received from the
+        * remote server.
+        */
+       void ( *callback ) ( struct http_request *http, char *data, size_t len );
+       /** Asynchronous operation */
+       struct async_operation aop;
+};
+
+extern struct async_operation * get_http ( struct http_request *http );
+
+#endif /* _GPXE_HTTP_H */
diff --git a/src/net/tcp/http.c b/src/net/tcp/http.c
new file mode 100644 (file)
index 0000000..56e15af
--- /dev/null
@@ -0,0 +1,201 @@
+#include <stddef.h>
+#include <string.h>
+#include <vsprintf.h>
+#include <assert.h>
+#include <gpxe/async.h>
+#include <gpxe/http.h>
+
+/** @file
+ *
+ * The Hyper Text Transfer Protocol (HTTP)
+ *
+ * This file implements the TCP-based HTTP protocol. It connects to the
+ * server specified in http_request::tcp and transmit an HTTP GET request
+ * for the file specified in http_request::filename. It then decoded the
+ * HTTP header, determining file status and file size. Then sends the file
+ * to the callback function at http_request::callback().
+ * **NOTE**: still working on correcting the closing of the tcp connection
+ *
+ * To use this code, do something like:
+ *
+ * @code
+ *
+ *   static void my_callback ( struct http_request *http, char *data, size_t len ) {
+ *     ... process data ...
+ *   }
+ *
+ *   struct http_request http = {
+ *     .filename = "path/to/file",
+ *     .callback = my_callback,
+ *   };
+ *
+ *   ... assign http.tcp.server ...
+ *
+ *   rc = async_wait ( get_http ( &http ) );
+ *
+ * @endcode
+ *
+ */
+
+static inline struct http_request *
+tcp_to_http ( struct tcp_connection *conn ) {
+       return container_of ( conn, struct http_request, tcp );
+}
+
+/**
+ * Close an HTTP connection
+ *
+ * @v conn     a TCP Connection
+ * @v status   connection status at close
+ */
+static void http_closed ( struct tcp_connection *conn, int status ) {
+       struct http_request *http = tcp_to_http ( conn );
+       async_done ( &http->aop, status );
+}
+
+/**
+ * Callback after a TCP connection is established
+ *
+ * @v conn     a TCP Connection
+ */
+static void http_connected ( struct tcp_connection *conn ) {
+       struct http_request *http = tcp_to_http ( conn );
+
+       http->state = HTTP_REQUEST_FILE;
+}
+
+/**
+ * Callback for when TCP data is acknowledged
+ *
+ * @v conn     a TCP Connection
+ * @v len      the length of data acked
+ */
+static void http_acked ( struct tcp_connection *conn, size_t len ) {
+       struct http_request *http = tcp_to_http ( conn );
+
+       // assume that the whole GET request was sent in on epacket
+
+       switch ( http->state ) {
+       case HTTP_REQUEST_FILE:
+               http->state = HTTP_PARSE_HEADER;
+               break;
+       case HTTP_PARSE_HEADER:
+       case HTTP_RECV_FILE:
+               break;
+       case HTTP_DONE:
+               //tcp_close(conn);
+               break;
+       default:
+               break;
+       }
+       //printf("acked\n");
+}
+
+/**
+ * Callback when new TCP data is recieved
+ *
+ * @v conn     a TCP Connection
+ * @v data     a pointer to the data recieved
+ * @v len      length of data buffer
+ */
+static void http_newdata ( struct tcp_connection *conn, void *data,
+                           size_t len ) {
+       struct http_request *http = tcp_to_http ( conn );
+       char *content_length;
+       char *start = data;
+       char *rcp; int rc;
+
+       switch ( http->state ) {
+       case HTTP_PARSE_HEADER:
+               if(strncmp("HTTP/",data,5) != 0){
+                       // no http header
+                       printf("Error: no HTTP Header\n");
+               }
+               // if rc is not 200, then handle problem
+               // either redirect or not there
+               rcp = strstr(data,"HTTP");
+               if(rcp == NULL){ printf("Could not find header status line.\n"); }
+               rcp += 9;
+               rc = strtoul(rcp,NULL,10);
+               printf("RC=%d\n",rc);
+               content_length = strstr(data,"Content-Length: ");
+               if(content_length != NULL){
+                       content_length += 16;
+                       http->file_size = strtoul(content_length,NULL,10);
+                       http->file_recv = 0;
+                       printf("http->file_size = %d\n", http->file_size);
+               }
+               start = strstr(data,"\r\n\r\n");
+               if(start == NULL){ printf("No end of header\n"); }
+               else{
+                       start += 4;
+                       len -= ((void *)start - data);
+                       http->state = HTTP_RECV_FILE;
+               }
+
+               if ( http->state != HTTP_RECV_FILE )
+                       break;
+       case HTTP_RECV_FILE:
+               http->callback(http,start,len);
+               //http->file_size -= len;
+               //printf("File recv is %d\n", http->file_recv);
+               if ( http->file_recv == http->file_size ){
+                       http->state = HTTP_DONE;
+                       tcp_close(conn);
+               }
+               break;
+       case HTTP_REQUEST_FILE:
+       case HTTP_DONE:
+       default:
+               break;
+       }
+}
+
+/**
+ * Callback for sending TCP data
+ *
+ * @v conn     a TCP Connection
+ */
+static void http_senddata ( struct tcp_connection *conn, void *buf, size_t len ) {
+       struct http_request *http = tcp_to_http ( conn );
+       char buf[66]; // 16 request + 50 for filename
+       size_t len;
+
+       switch ( http->state ){
+       case HTTP_REQUEST_FILE:
+               len = snprintf(buf,66,"GET %s HTTP/1.0\r\n\r\n",http->filename);
+               printf("%s\n",buf);
+               // string is: GET <file> HTTP/1.0\r\n\r\n
+
+               tcp_send ( conn, buf, len);
+               break;
+       case HTTP_PARSE_HEADER:
+       case HTTP_RECV_FILE:
+               break;
+       case HTTP_DONE:
+               //tcp_close(conn)
+               break;
+       default:
+               break;
+       }
+}
+
+static struct tcp_operations http_tcp_operations = {
+       .closed         = http_closed,
+       .connected      = http_connected,
+       .acked          = http_acked,
+       .newdata        = http_newdata,
+       .senddata       = http_senddata,
+};
+
+/**
+ * Initiate a HTTP connection
+ *
+ * @v http     a HTTP request
+ */
+struct async_operation * get_http ( struct http_request *http ) {
+       http->tcp.tcp_op = &http_tcp_operations;
+       http->state = HTTP_REQUEST_FILE;
+       tcp_connect ( &http->tcp );
+       return &http->aop;
+}
index 47e0e8b..38bbac3 100644 (file)
@@ -57,6 +57,26 @@ static int test_dhcp_hello ( char *helloname ) {
        return 0;
 }
 
+static int test_dhcp_http ( struct net_device *netdev, char *url ) {
+       union {
+               struct sockaddr_in sin;
+               struct sockaddr_tcpip st;
+       } target;
+
+       memset ( &target, 0, sizeof ( target ) );
+       target.sin.sin_family = AF_INET;
+       target.sin.sin_port = htons ( 80 );
+
+       char *addr = url + 7; // http://
+        char *file = strchr(addr, '/');
+       *file = '\0'; // for printf and inet_aton to work
+       printf("connecting to %s\n", addr);
+       inet_aton ( addr, &target.sin.sin_addr );
+       *file = '/';
+       test_http ( netdev, &target.st, file );
+       return 0;
+}
+
 static int test_dhcp_tftp ( struct net_device *netdev, char *tftpname ) {
        union {
                struct sockaddr_in sin;
@@ -79,6 +99,8 @@ static int test_dhcp_boot ( struct net_device *netdev, char *filename ) {
                return test_dhcp_iscsi_boot ( &filename[6] );
        } else if ( strncmp ( filename, "hello:", 6 ) == 0 ) {
                return test_dhcp_hello ( &filename[6] );
+       } else if ( strncmp ( filename, "http:", 5 ) == 0 ) {
+               return test_dhcp_http ( netdev, filename );
        } else {
                return test_dhcp_tftp ( netdev, filename );
        }
diff --git a/src/tests/httptest.c b/src/tests/httptest.c
new file mode 100644 (file)
index 0000000..4b569e8
--- /dev/null
@@ -0,0 +1,37 @@
+#include <stdint.h>
+#include <string.h>
+#include <byteswap.h>
+#include <console.h>
+#include <vsprintf.h>
+#include <gpxe/async.h>
+#include <gpxe/http.h>
+#include <gpxe/ip.h>
+#include <gpxe/uaccess.h>
+#include "pxe.h"
+
+static void test_http_callback ( struct http_request *http, char *data, size_t len ) {
+       userptr_t pxe_buffer = real_to_user ( 0, 0x7c00 );
+       unsigned long offset = http->file_recv;
+       http->file_recv += len;
+       copy_to_user ( pxe_buffer, offset, data, len );
+}
+
+void test_http ( struct net_device *netdev, struct sockaddr_tcpip *server, const char *filename ) {
+       struct http_request http;
+       int rc;
+
+       memset ( &http, 0, sizeof ( http ) );
+       memcpy ( &http.tcp.peer, server, sizeof ( http.tcp.peer ) );
+       http.filename = filename;
+       http.callback = test_http_callback;
+
+       rc = async_wait ( get_http ( &http ) );
+       if ( rc ) {
+               printf ( "HTTP fetch failed\n" );
+       }
+
+       printf ( "Attempting PXE boot\n" );
+       pxe_netdev = netdev;
+       rc = pxe_boot();
+       printf ( "PXE NBP returned with status %04x\n", rc);
+}