summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDerek Pryor2006-08-11 16:13:02 +0200
committerDerek Pryor2006-08-11 16:13:02 +0200
commit25ea34a8d75f637c4b4e80c4ec3891ddc56e80b0 (patch)
tree786e7bd76109d8b1492e655aff2052e939763fe3
parentcommand->data_{in,out} are now userptr_t, so it is invalid to compare (diff)
downloadipxe-25ea34a8d75f637c4b4e80c4ec3891ddc56e80b0.tar.gz
ipxe-25ea34a8d75f637c4b4e80c4ec3891ddc56e80b0.tar.xz
ipxe-25ea34a8d75f637c4b4e80c4ec3891ddc56e80b0.zip
New HTTP protocol and test code
-rw-r--r--src/include/gpxe/http.h58
-rw-r--r--src/net/tcp/http.c201
-rw-r--r--src/tests/dhcptest.c22
-rw-r--r--src/tests/httptest.c37
4 files changed, 318 insertions, 0 deletions
diff --git a/src/include/gpxe/http.h b/src/include/gpxe/http.h
new file mode 100644
index 000000000..02c9be410
--- /dev/null
+++ b/src/include/gpxe/http.h
@@ -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
index 000000000..56e15af27
--- /dev/null
+++ b/src/net/tcp/http.c
@@ -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;
+}
diff --git a/src/tests/dhcptest.c b/src/tests/dhcptest.c
index 47e0e8b48..38bbac371 100644
--- a/src/tests/dhcptest.c
+++ b/src/tests/dhcptest.c
@@ -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
index 000000000..4b569e854
--- /dev/null
+++ b/src/tests/httptest.c
@@ -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);
+}