pico-bmc/http_serv.h

298 lines
11 KiB
C
Raw Normal View History

#ifndef HTTP_SERV_H
#define HTTP_SERV_H
#include <string.h>
#include "lwip/pbuf.h"
#include "lwip/tcp.h"
#define POLL_TIME_S 5
#define HTTP_GET "GET"
#define HTTP_POST "POST"
#define HTTP_RESPONSE_HEADER "HTTP/1.1 %d OK\nContent-Length: %d\nContent-Type: application/json; charset=utf-8\nConnection: close\n\n"
extern CURRENT_STATE_T current_state;
typedef struct TCP_SERVER_T_ {
struct tcp_pcb * server_pcb;
bool complete;
ip_addr_t gw;
} TCP_SERVER_T;
typedef struct TCP_CONNECT_STATE_T_ {
struct tcp_pcb * pcb;
int sent_len;
char headers[128];
char result[256];
int header_len;
int result_len;
ip_addr_t * gw;
} TCP_CONNECT_STATE_T;
TCP_SERVER_T * state = NULL;
static err_t tcp_close_client_connection(TCP_CONNECT_STATE_T * con_state, struct tcp_pcb * client_pcb, err_t close_err) {
if (client_pcb) {
assert(con_state && con_state->pcb == client_pcb);
tcp_arg(client_pcb, NULL);
tcp_poll(client_pcb, NULL, 0);
tcp_sent(client_pcb, NULL);
tcp_recv(client_pcb, NULL);
tcp_err(client_pcb, NULL);
err_t err = tcp_close(client_pcb);
if (err != ERR_OK) {
DEBUG_printf("[HTTP] [ERR] Close failed %d, calling abort\n", err);
tcp_abort(client_pcb);
close_err = ERR_ABRT;
}
if (con_state) {
free(con_state);
}
}
return close_err;
}
static void tcp_server_close(TCP_SERVER_T *state) {
if (state->server_pcb) {
tcp_arg(state->server_pcb, NULL);
tcp_close(state->server_pcb);
state->server_pcb = NULL;
}
}
static err_t tcp_server_sent(void * arg, struct tcp_pcb * pcb, u16_t len) {
TCP_CONNECT_STATE_T * con_state = (TCP_CONNECT_STATE_T *)arg;
DEBUG_printf("[HTTP] [OK ] Sent %u\n", len);
con_state->sent_len += len;
if (con_state->sent_len >= con_state->header_len + con_state->result_len) {
DEBUG_printf("[HTTP] [OK ] Send done\n");
return tcp_close_client_connection(con_state, pcb, ERR_OK);
}
return ERR_OK;
}
static int handle_status_get (const char * request, const char * params, char * result, size_t max_result_len) {
return snprintf(result, max_result_len, "{volt: %f, temp: %f, power: %d}", current_state.voltage, current_state.tempC, current_state.power_state);
}
static int handle_power_post (const char * request, const char * params, char * result, size_t max_result_len) {
if (params) {
int requested_power_state_int;
int led_param = sscanf(params, "requested_state=%d", &requested_power_state_int);
if (led_param) {
if (requested_power_state_int == 0 || requested_power_state_int == 1) {
bmc_power_handler((bool) requested_power_state_int);
return snprintf(result, max_result_len, "{}");
}
else {
return snprintf(result, max_result_len, "{error: true, description: \"invalid requested state, must be 0 or 1\"}");
}
}
else {
return snprintf(result, max_result_len, "{error: true, description: \"invalid requested state, must be 0 or 1\"}");
}
}
else {
return snprintf(result, max_result_len, "{error: true, description: \"missing required parameter requested_state\"}");
}
}
err_t tcp_server_recv(void * arg, struct tcp_pcb * pcb, struct pbuf * p, err_t err) {
TCP_CONNECT_STATE_T * con_state = (TCP_CONNECT_STATE_T *)arg;
if (!p) {
DEBUG_printf("[HTTP] [ERR] Client connection closed\n");
return tcp_close_client_connection(con_state, pcb, ERR_OK);
}
assert(con_state && con_state->pcb == pcb);
if (p->tot_len > 0) {
DEBUG_printf("[HTTP] [OK ] Server recieved %d err %d\n", p->tot_len, err);
// Copy the request into the buffer
pbuf_copy_partial(p, con_state->headers, p->tot_len > sizeof(con_state->headers) - 1 ? sizeof(con_state->headers) - 1 : p->tot_len, 0);
// parse header, should probably put this into a separate method
char * method = con_state->headers;
char * request = NULL;
if (strncmp(method, HTTP_GET, sizeof(HTTP_GET) - 1) == 0) {
*(con_state->headers + sizeof(HTTP_GET) - 1) = 0;
request = con_state->headers + sizeof(HTTP_GET); // + space
}
else if (strncmp(method, HTTP_POST, sizeof(HTTP_POST) - 1) == 0) {
*(con_state->headers + sizeof(HTTP_POST) - 1) = 0;
request = con_state->headers + sizeof(HTTP_POST); // + space
}
char * params = strchr(request, '?');
if (params) {
if (*params) {
char * space = strchr(request, ' ');
*params++ = 0;
if (space) {
*space = 0;
}
} else {
params = NULL;
}
}
else {
char * space = strchr(request, ' ');
if (space) {
*space = 0;
}
}
// print request
if (params) {
DEBUG_printf("[HTTP] [OK ] Request: %s %s?%s\n", method, request, params);
}
else {
DEBUG_printf("[HTTP] [OK ] Request: %s %s\n", method, request);
}
int response_code;
// parse request depending on method and request
if (strncmp(method, HTTP_GET, sizeof(HTTP_GET) - 1) == 0 && strncmp(request, "/status", sizeof("/status") - 1) == 0) {
con_state->result_len = handle_status_get(request, params, con_state->result, sizeof(con_state->result));
response_code = 200;
con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_HEADER, response_code, con_state->result_len);
}
else if (strncmp(method, HTTP_POST, sizeof(HTTP_POST) - 1) == 0 && strncmp(request, "/power", sizeof("/power") - 1) == 0) {
con_state->result_len = handle_power_post(request, params, con_state->result, sizeof(con_state->result));
response_code = 200;
con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_HEADER, response_code, con_state->result_len);
}
else {
con_state->result_len = 0;
response_code = 501;
con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_HEADER, response_code, con_state->result_len);
}
// print result
DEBUG_printf("[HTTP] [OK ] Result: %d %s\n", response_code, con_state->result);
// Check result buffer size
if (con_state->result_len > sizeof(con_state->result) - 1) {
DEBUG_printf("[HTTP] [ERR] Too much result data %d\n", con_state->result_len);
return tcp_close_client_connection(con_state, pcb, ERR_CLSD);
}
// Check header buffer size
if (con_state->header_len > sizeof(con_state->headers) - 1) {
DEBUG_printf("[HTTP] [ERR] Too much header data %d\n", con_state->header_len);
return tcp_close_client_connection(con_state, pcb, ERR_CLSD);
}
// Send the headers to the client
con_state->sent_len = 0;
err_t err = tcp_write(pcb, con_state->headers, con_state->header_len, 0);
if (err != ERR_OK) {
DEBUG_printf("[HTTP] [ERR] Failed to write header data %d\n", err);
return tcp_close_client_connection(con_state, pcb, err);
}
// Send the body to the client
if (con_state->result_len) {
err = tcp_write(pcb, con_state->result, con_state->result_len, 0);
if (err != ERR_OK) {
DEBUG_printf("[HTTP] [ERR] Failed to write result data %d\n", err);
return tcp_close_client_connection(con_state, pcb, err);
}
}
tcp_recved(pcb, p->tot_len);
}
pbuf_free(p);
return ERR_OK;
}
static err_t tcp_server_poll(void * arg, struct tcp_pcb * pcb) {
TCP_CONNECT_STATE_T * con_state = (TCP_CONNECT_STATE_T *)arg;
return tcp_close_client_connection(con_state, pcb, ERR_OK); // Just disconnect clent?
}
static void tcp_server_err(void * arg, err_t err) {
TCP_CONNECT_STATE_T * con_state = (TCP_CONNECT_STATE_T *)arg;
if (err != ERR_ABRT) {
DEBUG_printf("[HTTP] [ERR] Error %d\n", err);
tcp_close_client_connection(con_state, con_state->pcb, err);
}
}
static err_t tcp_server_accept(void * arg, struct tcp_pcb * client_pcb, err_t err) {
TCP_SERVER_T * state = (TCP_SERVER_T *)arg;
if (err != ERR_OK || client_pcb == NULL) {
DEBUG_printf("[HTTP] [ERR] Failure accepting client connection\n");
return ERR_VAL;
}
DEBUG_printf("[HTTP] [OK ] Client connected\n");
// Create the state for the connection
TCP_CONNECT_STATE_T * con_state = calloc(1, sizeof(TCP_CONNECT_STATE_T));
if (!con_state) {
DEBUG_printf("[HTTP] [ERR] Failed to allocate connect state\n");
return ERR_MEM;
}
con_state->pcb = client_pcb; // for checking
con_state->gw = &state->gw;
// setup connection to client
tcp_arg(client_pcb, con_state);
tcp_sent(client_pcb, tcp_server_sent);
tcp_recv(client_pcb, tcp_server_recv);
tcp_poll(client_pcb, tcp_server_poll, POLL_TIME_S * 2);
tcp_err(client_pcb, tcp_server_err);
return ERR_OK;
}
static bool tcp_server_open(void * arg) {
TCP_SERVER_T * state = (TCP_SERVER_T *)arg;
DEBUG_printf("[HTTP] [OK ] Starting server at %s on port %d\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), TCP_PORT);
struct tcp_pcb * pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
if (!pcb) {
DEBUG_printf("[HTTP] [ERR] Failed to create pcb\n");
return false;
}
err_t err = tcp_bind(pcb, IP_ANY_TYPE, TCP_PORT);
if (err) {
DEBUG_printf("[HTTP] [ERR] Failed to bind to port %d\n", TCP_PORT);
return false;
}
state->server_pcb = tcp_listen_with_backlog(pcb, 1);
if (!state->server_pcb) {
DEBUG_printf("[HTTP] [ERR] Failed to listen on port %d\n", TCP_PORT);
if (pcb) {
tcp_close(pcb);
}
return false;
}
tcp_arg(state->server_pcb, state);
tcp_accept(state->server_pcb, tcp_server_accept);
return true;
}
int http_serv_init () {
state = calloc(1, sizeof(TCP_SERVER_T));
if (!state) {
DEBUG_printf("[HTTP] [ERR] Failed to allocate state\n");
return 1;
}
if (!tcp_server_open(state)) {
DEBUG_printf("[HTTP] [ERR] Failed to open server\n");
return 1;
}
DEBUG_printf("[HTTP] [OK ] Sucessfully initialized http server\n");
return 0;
}
int http_serv_deinit () {
tcp_server_close(state);
return 0;
}
#endif