refactor: Clean up HTTP handling

master
Sean McBride 4 years ago
parent b039745724
commit f61f34f08a

@ -47,6 +47,10 @@ client_socket_send(int client_socket, int status_code)
response = HTTP_RESPONSE_503_SERVICE_UNAVAILABLE;
http_total_increment_5XX();
break;
case 413:
response = HTTP_RESPONSE_413_PAYLOAD_TOO_LARGE;
http_total_increment_4XX();
break;
case 400:
response = HTTP_RESPONSE_400_BAD_REQUEST;
http_total_increment_4XX();

@ -1,14 +1,58 @@
#pragma once
#include <string.h>
#define HTTP_MAX_HEADER_COUNT 16
#define HTTP_MAX_HEADER_LENGTH 32
#define HTTP_MAX_HEADER_VALUE_LENGTH 64
#define HTTP_RESPONSE_200_OK "HTTP/1.1 200 OK\r\n"
#define HTTP_RESPONSE_503_SERVICE_UNAVAILABLE "HTTP/1.1 503 Service Unavailable\r\n\r\n"
#define HTTP_RESPONSE_400_BAD_REQUEST "HTTP/1.1 400 Bad Request\r\n\r\n"
#define HTTP_RESPONSE_CONTENT_LENGTH "Content-Length: "
#define HTTP_RESPONSE_CONTENT_LENGTH_TERMINATOR "\r\n\r\n" /* content body follows this */
#define HTTP_RESPONSE_CONTENT_TYPE "Content-Type: "
#define HTTP_RESPONSE_CONTENT_TYPE_PLAIN "text/plain"
#define HTTP_RESPONSE_CONTENT_TYPE_TERMINATOR " \r\n"
#define HTTP_RESPONSE_400_BAD_REQUEST "HTTP/1.1 400 Bad Request\r\n\r\n"
#define HTTP_RESPONSE_413_PAYLOAD_TOO_LARGE "HTTP/1.1 413 Payload Too Large\r\n\r\n"
#define HTTP_RESPONSE_503_SERVICE_UNAVAILABLE "HTTP/1.1 503 Service Unavailable\r\n\r\n"
#define HTTP_RESPONSE_200_TEMPLATE \
"HTTP/1.1 200 OK\r\n" \
"Content-Type: %s\r\n" \
"Content-Length: %s\r\n" \
"\r\n"
/* The sum of format specifier characters in the template above */
#define HTTP_RESPONSE_200_TEMPLATE_FORMAT_SPECIFIER_LENGTH 4
/**
* Calculates the number of bytes of the HTTP response containing the passed header values
* @return total size in bytes
*/
static inline size_t
http_response_200_size(char *content_type, char *content_length)
{
size_t size = 0;
size += strlen(HTTP_RESPONSE_200_TEMPLATE) - HTTP_RESPONSE_200_TEMPLATE_FORMAT_SPECIFIER_LENGTH;
size += strlen(content_type);
size += strlen(content_length);
return size;
}
/**
* Writes the HTTP response header to the destination. This is assumed to have been sized
* using the value returned by http_response_200_size. We have to use an intermediate buffer
* in order to truncate off the null terminator
* @return 0 on success, -1 otherwise
*/
static inline int
http_response_200(char *destination, char *content_type, char *content_length)
{
size_t response_size = http_response_200_size(content_type, content_length);
char buffer[response_size + 1];
int rc = 0;
rc = sprintf(buffer, HTTP_RESPONSE_200_TEMPLATE, content_type, content_length);
if (rc <= 0) goto err;
memmove(destination, buffer, response_size);
rc = 0;
done:
return rc;
err:
rc = -1;
goto done;
}

@ -40,14 +40,12 @@ struct module {
uint64_t max_memory; /* perhaps a specification of the module. (max 4GB) */
uint32_t relative_deadline_us;
int port;
unsigned long max_request_size;
unsigned long max_response_size; /* resp size including headers! */
struct admissions_info admissions_info;
uint64_t relative_deadline; /* cycles */
/* HTTP State */
unsigned long max_request_or_response_size; /* largest of max_request_size or max_response_size */
size_t max_request_size;
size_t max_response_size;
char response_content_type[HTTP_MAX_HEADER_VALUE_LENGTH];
struct sockaddr_in socket_address;
int socket_descriptor;

@ -18,14 +18,14 @@
/**
* Receive and Parse the Request for the current sandbox
* @return 0 if message parsing complete, -1 on error
* @return 0 if message parsing complete, -1 on error, -2 if buffers run out of space
*/
static inline int
sandbox_receive_request(struct sandbox *sandbox)
{
assert(sandbox != NULL);
assert(sandbox->module->max_request_size > 0);
assert(sandbox->buffer.length == 0);
assert(sandbox->request.length == 0);
int rc = 0;
@ -36,26 +36,28 @@ sandbox_receive_request(struct sandbox *sandbox)
http_parser * parser = &sandbox->http_parser;
const http_parser_settings *settings = http_parser_settings_get();
int fd = sandbox->client_socket_descriptor;
char * buf = &sandbox->buffer.start[sandbox->buffer.length];
size_t len = sandbox->module->max_request_size - sandbox->buffer.length;
if (sandbox->module->max_request_size <= sandbox->request.length) {
debuglog("Sandbox %lu: Ran out of Request Buffer before message end\n", sandbox->id);
goto err_nobufs;
}
ssize_t recved = recv(fd, buf, len, 0);
ssize_t bytes_received = recv(sandbox->client_socket_descriptor,
&sandbox->request.base[sandbox->request.length],
sandbox->module->max_request_size - sandbox->request.length, 0);
if (recved < 0) {
if (bytes_received == -1) {
if (errno == EAGAIN) {
scheduler_block();
continue;
} else {
/* All other errors */
debuglog("Error reading socket %d - %s\n", sandbox->client_socket_descriptor,
strerror(errno));
goto err;
}
}
/* Client request is malformed */
if (recved == 0 && !sandbox->http_request.message_end) {
/* If we received an EOF before we were able to parse a complete HTTP header, request is malformed */
if (bytes_received == 0 && !sandbox->http_request.message_end) {
char client_address_text[INET6_ADDRSTRLEN] = {};
if (unlikely(inet_ntop(AF_INET, &sandbox->client_address, client_address_text, INET6_ADDRSTRLEN)
== NULL)) {
@ -63,37 +65,37 @@ sandbox_receive_request(struct sandbox *sandbox)
}
debuglog("Sandbox %lu: recv returned 0 before a complete request was received\n", sandbox->id);
debuglog("Socket: %d. Address: %s\n", fd, client_address_text);
debuglog("Socket: %d. Address: %s\n", sandbox->client_socket_descriptor, client_address_text);
http_request_print(&sandbox->http_request);
goto err;
}
#ifdef LOG_HTTP_PARSER
debuglog("Sandbox: %lu http_parser_execute(%p, %p, %p, %zu\n)", sandbox->id, parser, settings, buf,
recved);
bytes_received);
#endif
size_t nparsed = http_parser_execute(parser, settings, buf, recved);
size_t bytes_parsed = http_parser_execute(parser, settings,
&sandbox->request.base[sandbox->request.length],
bytes_received);
if (nparsed != recved) {
/* TODO: Is this error */
if (bytes_parsed != bytes_received) {
debuglog("Error: %s, Description: %s\n",
http_errno_name((enum http_errno)sandbox->http_parser.http_errno),
http_errno_description((enum http_errno)sandbox->http_parser.http_errno));
debuglog("Length Parsed %zu, Length Read %zu\n", nparsed, recved);
debuglog("Length Parsed %zu, Length Read %zu\n", bytes_parsed, bytes_received);
debuglog("Error parsing socket %d\n", sandbox->client_socket_descriptor);
goto err;
}
sandbox->buffer.length += nparsed;
sandbox->request.length += bytes_parsed;
}
sandbox->http_request_length = sandbox->buffer.length;
rc = 0;
done:
return rc;
err_nobufs:
rc = -2;
goto done;
err:
rc = -1;
goto done;

@ -24,97 +24,51 @@ static inline int
sandbox_send_response(struct sandbox *sandbox)
{
assert(sandbox != NULL);
/* Assumption: The HTTP Request Buffer immediately precedes the HTTP Response Buffer,
* meaning that when we prepend, we are overwritting the tail of the HTTP request buffer */
assert(sandbox->request.base + sandbox->module->max_request_size == sandbox->response.base);
/*
* At this point the HTTP Request has filled the buffer up to request_length, after which
* the STDOUT of the sandbox has been appended. We assume that our HTTP Response header is
* smaller than the HTTP Request header, which allows us to use memmove once without copying
* to an intermediate buffer.
*/
memset(sandbox->buffer.start, 0, sandbox->http_request_length);
/*
* We use this cursor to keep track of our position in the buffer and later assert that we
* haven't overwritten body data.
*/
size_t response_cursor = 0;
/* Append 200 OK */
strncpy(sandbox->buffer.start, HTTP_RESPONSE_200_OK, strlen(HTTP_RESPONSE_200_OK));
response_cursor += strlen(HTTP_RESPONSE_200_OK);
/* Content Type */
strncpy(sandbox->buffer.start + response_cursor, HTTP_RESPONSE_CONTENT_TYPE,
strlen(HTTP_RESPONSE_CONTENT_TYPE));
response_cursor += strlen(HTTP_RESPONSE_CONTENT_TYPE);
/* Custom content type if provided, text/plain by default */
if (strlen(sandbox->module->response_content_type) <= 0) {
strncpy(sandbox->buffer.start + response_cursor, HTTP_RESPONSE_CONTENT_TYPE_PLAIN,
strlen(HTTP_RESPONSE_CONTENT_TYPE_PLAIN));
response_cursor += strlen(HTTP_RESPONSE_CONTENT_TYPE_PLAIN);
} else {
strncpy(sandbox->buffer.start + response_cursor, sandbox->module->response_content_type,
strlen(sandbox->module->response_content_type));
response_cursor += strlen(sandbox->module->response_content_type);
}
strncpy(sandbox->buffer.start + response_cursor, HTTP_RESPONSE_CONTENT_TYPE_TERMINATOR,
strlen(HTTP_RESPONSE_CONTENT_TYPE_TERMINATOR));
response_cursor += strlen(HTTP_RESPONSE_CONTENT_TYPE_TERMINATOR);
/* Content Length */
strncpy(sandbox->buffer.start + response_cursor, HTTP_RESPONSE_CONTENT_LENGTH,
strlen(HTTP_RESPONSE_CONTENT_LENGTH));
response_cursor += strlen(HTTP_RESPONSE_CONTENT_LENGTH);
size_t body_size = sandbox->buffer.length - sandbox->http_request_length;
char len[10] = { 0 };
sprintf(len, "%zu", body_size);
strncpy(sandbox->buffer.start + response_cursor, len, strlen(len));
response_cursor += strlen(len);
int rc;
strncpy(sandbox->buffer.start + response_cursor, HTTP_RESPONSE_CONTENT_LENGTH_TERMINATOR,
strlen(HTTP_RESPONSE_CONTENT_LENGTH_TERMINATOR));
response_cursor += strlen(HTTP_RESPONSE_CONTENT_LENGTH_TERMINATOR);
/* Determine values to template into our HTTP response */
ssize_t response_body_size = sandbox->response.length;
char content_length[20] = { 0 };
sprintf(content_length, "%zu", response_body_size);
char *module_content_type = sandbox->module->response_content_type;
char *content_type = strlen(module_content_type) > 0 ? module_content_type : "text/plain";
/*
* Assumption: Our response header is smaller than the request header, so we do not overwrite
* actual data that the program appended to the HTTP Request. If proves to be a bad assumption,
* we have to copy the STDOUT string to a temporary buffer before writing the header
*/
if (unlikely(response_cursor >= sandbox->http_request_length)) {
panic("Response Cursor: %zd is less that Request Length: %zd\n", response_cursor,
sandbox->http_request_length);
}
/* Move the Sandbox's Data after the HTTP Response Data */
memmove(sandbox->buffer.start + response_cursor, sandbox->buffer.start + sandbox->http_request_length,
body_size);
response_cursor += body_size;
/* Prepend HTTP Response Headers */
size_t response_header_size = http_response_200_size(content_type, content_length);
char * response_header = sandbox->response.base - response_header_size;
rc = http_response_200(response_header, content_type, content_length);
if (rc < 0) goto err;
/* Capture Timekeeping data for end-to-end latency */
uint64_t end_time = __getcycles();
sandbox->total_time = end_time - sandbox->timestamp_of.request_arrival;
int rc;
int sent = 0;
while (sent < response_cursor) {
rc = write(sandbox->client_socket_descriptor, &sandbox->buffer.start[sent], response_cursor - sent);
/* Send HTTP Response */
int sent = 0;
size_t response_size = response_header_size + response_body_size;
while (sent < response_size) {
rc = write(sandbox->client_socket_descriptor, response_header, response_size - sent);
if (rc < 0) {
if (errno == EAGAIN)
scheduler_block();
else {
perror("write");
return -1;
goto err;
}
}
sent += rc;
}
http_total_increment_2xx();
rc = 0;
return 0;
done:
return rc;
err:
rc = -1;
goto done;
}

@ -40,41 +40,46 @@ struct sandbox_timestamps {
};
/*
* In-memory buffer used to read requests, buffer write to STDOUT, and write HTTP responses
* The HTTP request is read in, updating buffer.length and http_request_length
* --------------------------------------------------
* | Request | Empty |
* --------------------------------------------------
* Writes to STDOUT are written starting at http_request_length, updating buffer.length
* --------------------------------------------------
* | Request | STDOUT | Empty |
* --------------------------------------------------
* The HTTP Response is written over the Request (assumes the response is smaller)
* --------------------------------------------------
* | Response | Gap | STDOUT | Empty |
* --------------------------------------------------
* And the STDOUT buffer is compacted to immediately follow the response
* --------------------------------------------------
* | Response | STDOUT | Empty |
* --------------------------------------------------
* Static In-memory buffers are used for HTTP requests read in via STDIN and HTTP
* responses written back out via STDOUT. These are allocated in pages immediately
* adjacent to the sandbox struct in the following layout. The capacity of these
* buffers are configured in the module spec and stored in sandbox->module.max_request_size
* and sandbox->module.max_response_size.
*
* Because the sandbox struct, the request header, and the response header are sized
* in pages, we must store the base pointer to the buffer. The length is increased
* and should not exceed the respective module max size.
*
* ---------------------------------------------------
* | Sandbox | Request | Response |
* ---------------------------------------------------
*
* After the sandbox writes its response, a header is written at a negative offset
* overwriting the tail end of the request buffer. This assumes that the request
* data is no longer needed because the sandbox has run to completion
*
* ---------------------------------------------------
* | Sandbox | Garbage | HDR | Response |
* ---------------------------------------------------
*/
struct sandbox_buffer {
ssize_t length; /* Should be <= module->max_request_or_response_size */
char start[1];
char * base;
size_t length;
};
struct sandbox {
uint64_t id;
sandbox_state_t state;
uint32_t sandbox_size; /* The struct plus enough buffer to hold the request or response (sized off largest) */
struct ps_list list; /* used by ps_list's default name-based MACROS for the scheduling runqueue */
struct ps_list list; /* used by ps_list's default name-based MACROS for the scheduling runqueue */
/* HTTP State */
struct sockaddr client_address; /* client requesting connection! */
int client_socket_descriptor;
http_parser http_parser;
struct http_request http_request;
ssize_t http_request_length;
struct sockaddr client_address; /* client requesting connection! */
int client_socket_descriptor;
http_parser http_parser;
struct http_request http_request;
ssize_t http_request_length; /* TODO: Get rid of me */
struct sandbox_buffer request;
struct sandbox_buffer response;
/* WebAssembly Module State */
struct module *module; /* the module this is an instance of */
@ -97,6 +102,4 @@ struct sandbox {
int32_t arguments_offset; /* actual placement of arguments in the sandbox. */
int32_t return_value;
/* This contains a Variable Length Array and thus MUST be the final member of this struct */
struct sandbox_buffer buffer;
} PAGE_ALIGNED;

@ -68,13 +68,19 @@ current_sandbox_start(void)
assert(sandbox->state == SANDBOX_RUNNING);
char *error_message = "";
int rc = 0;
sandbox_open_http(sandbox);
if (sandbox_receive_request(sandbox) < 0) {
error_message = "Unable to receive or parse client request\n";
rc = sandbox_receive_request(sandbox);
if (rc == -2) {
/* Request size exceeded Buffer, send 413 Payload Too Large */
client_socket_send(sandbox->client_socket_descriptor, 413);
goto err;
};
} else if (rc == -1) {
client_socket_send(sandbox->client_socket_descriptor, 400);
goto err;
}
/* Initialize sandbox memory */
struct module *current_module = sandbox_get_module(sandbox);
@ -116,9 +122,6 @@ err:
debuglog("%s", error_message);
assert(sandbox->state == SANDBOX_RUNNING);
/* Send a 400 error back to the client */
client_socket_send(sandbox->client_socket_descriptor, 400);
sandbox_close_http(sandbox);
sandbox_set_as_error(sandbox, SANDBOX_RUNNING);
goto done;

@ -185,13 +185,8 @@ module_new(char *name, char *path, uint32_t stack_size, uint32_t max_memory, uin
/* Request Response Buffer */
if (request_size == 0) request_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE;
if (response_size == 0) response_size = MODULE_DEFAULT_REQUEST_RESPONSE_SIZE;
module->max_request_size = request_size;
module->max_response_size = response_size;
if (request_size > response_size) {
module->max_request_or_response_size = round_up_to_page(request_size);
} else {
module->max_request_or_response_size = round_up_to_page(response_size);
}
module->max_request_size = round_up_to_page(request_size);
module->max_response_size = round_up_to_page(response_size);
/* Table initialization calls a function that runs within the sandbox. Rather than setting the current sandbox,
* we partially fake this out by only setting the module_indirect_table and then clearing after table

@ -10,7 +10,7 @@
/**
* Allocates a WebAssembly sandbox represented by the following layout
* struct sandbox | Buffer for HTTP Req/Resp | 4GB of Wasm Linear Memory | Guard Page
* struct sandbox | HTTP Req Buffer | HTTP Resp Buffer | 4GB of Wasm Linear Memory | Guard Page
* @param module the module that we want to run
* @returns the resulting sandbox or NULL if mmap failed
*/
@ -19,22 +19,25 @@ sandbox_allocate_memory(struct module *module)
{
assert(module != NULL);
char * error_message = NULL;
unsigned long memory_size = WASM_PAGE_SIZE * WASM_MEMORY_PAGES_INITIAL; /* The initial pages */
uint64_t memory_max = (uint64_t)WASM_PAGE_SIZE * WASM_MEMORY_PAGES_MAX;
struct sandbox *sandbox = NULL;
unsigned long sandbox_size = sizeof(struct sandbox) + module->max_request_or_response_size;
char * error_message = NULL;
unsigned long memory_size = WASM_PAGE_SIZE * WASM_MEMORY_PAGES_INITIAL; /* The initial pages */
uint64_t memory_max = (uint64_t)WASM_PAGE_SIZE * WASM_MEMORY_PAGES_MAX;
struct sandbox *sandbox = NULL;
unsigned long page_aligned_sandbox_size = round_up_to_page(sizeof(struct sandbox));
unsigned long size_to_alloc = page_aligned_sandbox_size + module->max_request_size + module->max_request_size
+ memory_max + /* guard page */ PAGE_SIZE;
unsigned long size_to_read_write = page_aligned_sandbox_size + module->max_request_size
+ module->max_request_size + memory_size;
/*
* Control information should be page-aligned
* TODO: Should I use round_up_to_page when setting sandbox_page? Issue #50
*/
assert(round_up_to_page(sandbox_size) == sandbox_size);
assert(round_up_to_page(size_to_alloc) == size_to_alloc);
/* At an address of the system's choosing, allocate the memory, marking it as inaccessible */
errno = 0;
void *addr = mmap(NULL, sandbox_size + memory_max + /* guard page */ PAGE_SIZE, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
void *addr = mmap(NULL, size_to_alloc, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
error_message = "sandbox_allocate_memory - memory allocation failed";
goto alloc_failed;
@ -44,8 +47,8 @@ sandbox_allocate_memory(struct module *module)
/* Set the struct sandbox, HTTP Req/Resp buffer, and the initial Wasm Pages as read/write */
errno = 0;
void *addr_rw = mmap(addr, sandbox_size + memory_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
void *addr_rw = mmap(addr, size_to_read_write, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
-1, 0);
if (addr_rw == MAP_FAILED) {
error_message = "set to r/w";
goto set_rw_failed;
@ -54,20 +57,27 @@ sandbox_allocate_memory(struct module *module)
sandbox = (struct sandbox *)addr_rw;
/* Populate Sandbox members */
sandbox->state = SANDBOX_UNINITIALIZED;
sandbox->memory.start = (char *)addr + sandbox_size;
sandbox->memory.size = memory_size;
sandbox->memory.max = memory_max;
sandbox->module = module;
sandbox->sandbox_size = sandbox_size;
sandbox->state = SANDBOX_UNINITIALIZED;
sandbox->module = module;
module_acquire(module);
sandbox->request.base = (char *)addr + page_aligned_sandbox_size;
sandbox->request.length = 0;
sandbox->response.base = (char *)addr + page_aligned_sandbox_size + module->max_request_size;
sandbox->request.length = 0;
sandbox->memory.start = (char *)addr + page_aligned_sandbox_size + module->max_request_size
+ module->max_request_size;
sandbox->memory.size = memory_size;
sandbox->memory.max = memory_max;
done:
return sandbox;
set_rw_failed:
sandbox = NULL;
errno = 0;
int rc = munmap(addr, sandbox_size + memory_size + PAGE_SIZE);
int rc = munmap(addr, size_to_alloc);
if (rc == -1) perror("Failed to munmap after fail to set r/w");
alloc_failed:
err:
@ -185,17 +195,19 @@ sandbox_free(struct sandbox *sandbox)
};
/* Free Remaining Sandbox Linear Address Space
* sandbox_size includes the struct and HTTP buffer
/* Free Sandbox Struct and HTTP Request and Response Buffers
* The linear memory was already freed during the transition from running to error|complete
* struct sandbox | HTTP Buffer | 4GB of Wasm Linear Memory | Guard Page
* Allocated | Allocated | Freed | Freed
* struct sandbox | HTTP Request Buffer | HTTP Response Buffer | 4GB of Wasm Linear Memory | Guard Page
* Allocated | Allocated | Allocated | Freed | Freed
*/
/* Linear Memory and Guard Page should already have been munmaped and set to NULL */
assert(sandbox->memory.start == NULL);
errno = 0;
rc = munmap(sandbox, sandbox->sandbox_size);
unsigned long size_to_unmap = round_up_to_page(sizeof(struct sandbox)) + sandbox->module->max_request_size
+ sandbox->module->max_response_size;
munmap(sandbox, size_to_unmap);
if (rc == -1) {
debuglog("Failed to unmap Sandbox %lu\n", sandbox->id);
goto err_free_sandbox_failed;

@ -13,12 +13,21 @@ rttests: $(TESTSRT)
clean:
@echo "Cleaning Test Applications"
@rm -rf ${TMP_DIR}
@rm -rf ${SLEDGE_BIN_DIR}/*_wasm.so
@make clean -C ./TinyEKF/extras/c/ -f wasm.mk
@make clean -C ./CMSIS_5_NN/ -f Makefile
@make clean -C ./gocr/src/ -f wasm.mk
@make clean -C ./sod/
@make clean -C ./empty/
@rm -f wasm/empty.wasm bc/empty.bc so/empty.so
@rm -rf ${SLEDGE_BIN_DIR}/empty.so ${SLEDGE_BIN_DIR}/test_empty.json
@make clean -C ./echo/
@rm -f wasm/echo.wasm bc/echo.bc so/echo.so
@rm -rf ${SLEDGE_BIN_DIR}/echo.so ${SLEDGE_BIN_DIR}/test_echo.json
@make clean -C ./fibonacci/
@rm -f wasm/fibonacci.wasm bc/fibonacci.bc so/fibonacci.so
@rm -rf ${SLEDGE_BIN_DIR}/fibonacci.so ${SLEDGE_BIN_DIR}/test_fibonacci.json
@make clean -C ./TinyEKF/ -f wasm.mk
@rm -f wasm/gps_ekf.wasm bc/gps_ekf.bc so/gps_ekf.so
@rm -rf ${SLEDGE_BIN_DIR}/gps_ekf.so ${SLEDGE_BIN_DIR}/test_gps_ekf.json
tinyekf:
@echo "Making and Installing tinyekf"

Loading…
Cancel
Save