484 lines
13 KiB
C
484 lines
13 KiB
C
#include "meshtalos.h"
|
|
#include "./mpack.h"
|
|
#include "./picohttpparser.h"
|
|
#include "lwip/sockets.h"
|
|
#include <arpa/inet.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/param.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
|
|
#define STB_DS_IMPLEMENTATION
|
|
#include "./stb_ds.h"
|
|
|
|
#define HTTPVER "HTTP/1.1"
|
|
#define IMG_W_SIZE 152
|
|
#define IMG_H_SIZE 296
|
|
#define DISPLAY_SIZE IMG_W_SIZE *IMG_H_SIZE
|
|
#define IMG_HEADER_SIZE 15
|
|
#define IMG_SIZE IMG_HEADER_SIZE + DISPLAY_SIZE
|
|
|
|
struct client {
|
|
int fd;
|
|
struct sockaddr_in ip4;
|
|
bool invalid;
|
|
};
|
|
|
|
struct conn {
|
|
int rootfd;
|
|
fd_set master, recv;
|
|
struct client *c;
|
|
int max_client_fd;
|
|
struct timeval interval;
|
|
};
|
|
|
|
struct http_param {
|
|
const char *method, *path;
|
|
struct phr_header *headers;
|
|
size_t method_len, path_len, headers_len, body_len;
|
|
int minor_ver;
|
|
char *body_start;
|
|
};
|
|
|
|
struct conn workers, http;
|
|
char iobuf[2048] = {0};
|
|
char httpbuf[2048] = {0};
|
|
char imgbuf[IMG_SIZE];
|
|
struct phr_header headers[30];
|
|
const size_t header_nums = 30;
|
|
|
|
void tsend();
|
|
int natoi(const char *a, int len) {
|
|
int ret = 0;
|
|
for (int i = 0; i < len; i++) {
|
|
if (a[i] >= '0' && a[i] <= '9') {
|
|
ret = ret * 10 + a[i] - '0';
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
ssize_t client_send(struct client *cli, const char *buf, size_t len) {
|
|
if (cli->invalid) {
|
|
return -1;
|
|
}
|
|
int ret = send(cli->fd, buf, len, MSG_NOSIGNAL);
|
|
if (ret < 0) {
|
|
cli->invalid = true;
|
|
}
|
|
return ret;
|
|
}
|
|
ssize_t client_recv(struct client *cli, char *buf, size_t len) {
|
|
if (cli->invalid) {
|
|
return -1;
|
|
}
|
|
int ret = recv(cli->fd, buf, len, MSG_NOSIGNAL);
|
|
if (ret <= 0) {
|
|
cli->invalid = true;
|
|
}
|
|
return ret;
|
|
}
|
|
ssize_t client_dprintf(struct client *cli, const char *fmt, ...) {
|
|
va_list v;
|
|
va_start(v, fmt);
|
|
size_t l = vsnprintf(httpbuf, sizeof(httpbuf), fmt, v);
|
|
va_end(v);
|
|
return client_send(cli, httpbuf, l);
|
|
}
|
|
|
|
void conn_init(struct conn *c) {
|
|
FD_ZERO(&c->recv);
|
|
arrsetcap(c->c, 10);
|
|
}
|
|
void conn_poll_accept(struct conn *c) {
|
|
while (1) {
|
|
struct client temp;
|
|
socklen_t tlen = sizeof(temp.ip4);
|
|
temp.fd = accept(c->rootfd, (struct sockaddr *)&temp.ip4, &tlen);
|
|
if (temp.fd != -1) {
|
|
c->max_client_fd =
|
|
c->max_client_fd < temp.fd ? temp.fd : c->max_client_fd;
|
|
FD_SET(temp.fd, &c->master);
|
|
printf("poll accepted: %s\n", inet_ntoa(temp.ip4.sin_addr));
|
|
temp.invalid = false;
|
|
arrput(c->c, temp);
|
|
} else {
|
|
if (errno != EAGAIN || errno != EWOULDBLOCK) {
|
|
perror("accept error");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
void conn_poll_clients(struct conn *c,
|
|
void (*cb)(struct conn *c, struct client *client,
|
|
int client_id)) {
|
|
memcpy(&c->recv, &c->master, sizeof(c->master));
|
|
int conn = select(c->max_client_fd + 1, &c->recv, NULL, NULL, &c->interval);
|
|
if (conn < 0) {
|
|
perror("select error");
|
|
}
|
|
for (int i = 0; i < arrlen(c->c) && conn; ++i) {
|
|
if (FD_ISSET(c->c[i].fd, &c->recv)) {
|
|
cb(c, &c->c[i], i);
|
|
conn--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void conn_collect_bad_client(struct conn *c) {
|
|
for (int i = 0; i < arrlen(c->c); i++) {
|
|
if (c->c[i].invalid) {
|
|
FD_CLR(c->c[i].fd, &c->master);
|
|
close(c->c[i].fd);
|
|
arrdelswap(c->c, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
int app_close = 0;
|
|
|
|
/* http path handle */
|
|
#define CLRF "\r\n"
|
|
#define HEAD_MATCH(h, g) \
|
|
(strlen(h) == g.name_len && strncmp(h, g.name, g.name_len) == 0)
|
|
#define HEAD_VAL_MATCH(h, g) \
|
|
(strlen(h) == g.value_len && strncmp(h, g.value, g.value_len) == 0)
|
|
int badreq(struct client *c) {
|
|
client_dprintf(c,
|
|
HTTPVER " 400 Bad Request" CLRF "Connection: close" CLRF CLRF);
|
|
return -1;
|
|
}
|
|
int fbadreq(struct client *c, const char *s, ...) {
|
|
va_list p, tp;
|
|
va_start(p, s);
|
|
va_copy(tp, p);
|
|
size_t body_len = vsnprintf(NULL, 0, s, tp);
|
|
client_dprintf(c,
|
|
HTTPVER " 400 Bad Request" CLRF "Connection: close" CLRF
|
|
"Content-Length: %ld" CLRF CLRF,
|
|
body_len);
|
|
vsnprintf(httpbuf, sizeof(httpbuf), s, p);
|
|
client_send(c, httpbuf, body_len);
|
|
va_end(p);
|
|
return -1;
|
|
}
|
|
int api_tag_upload(struct client *c, struct http_param *p) {
|
|
|
|
printf("INTO TAG RECV\n");
|
|
int acc = 0;
|
|
for (int i = 0; i < p->headers_len; i++) {
|
|
if (HEAD_MATCH("Content-Type", p->headers[i]) &&
|
|
HEAD_VAL_MATCH("image/x-portable-greymap", p->headers[i])) {
|
|
acc++;
|
|
}
|
|
if (HEAD_MATCH("Content-Length", p->headers[i])) {
|
|
if (strtol(p->headers[i].value, NULL, 10) == IMG_SIZE) {
|
|
acc++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (acc != 2) {
|
|
|
|
return fbadreq(c,
|
|
"Only accept request with PGM P5 format that height and "
|
|
"width is either %03d,%03d",
|
|
IMG_W_SIZE, IMG_H_SIZE);
|
|
}
|
|
|
|
size_t upload_size = p->body_len;
|
|
memcpy(imgbuf, p->body_start, p->body_len);
|
|
|
|
while (upload_size < IMG_SIZE) {
|
|
// upload PGM sanitize
|
|
if (upload_size > IMG_HEADER_SIZE) {
|
|
if (strncmp(imgbuf, "P5", 2) != 0) {
|
|
return badreq(c);
|
|
}
|
|
int santinize_acc = 0;
|
|
int width = 0, height = IMG_W_SIZE ^ IMG_H_SIZE;
|
|
int p_start = 3, p_len = 0;
|
|
for (int i = 3; i < upload_size; i++) {
|
|
if (imgbuf[i] == ' ' || imgbuf[i] == '\r' || imgbuf[i] == '\n') {
|
|
p_len = i - p_start;
|
|
if (santinize_acc == 0) {
|
|
width = natoi(&imgbuf[p_start], p_len);
|
|
if (width != IMG_W_SIZE && width != IMG_H_SIZE) {
|
|
return badreq(c);
|
|
}
|
|
height ^= width;
|
|
} else if (santinize_acc == 1) {
|
|
if (height != natoi(&imgbuf[p_start], p_len)) {
|
|
return badreq(c);
|
|
}
|
|
}
|
|
p_start = i + 1;
|
|
p_len = 0;
|
|
santinize_acc += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// read MIN(iobuf, left_to_read)
|
|
size_t next_size = IMG_SIZE - upload_size > sizeof(httpbuf)
|
|
? sizeof(httpbuf)
|
|
: IMG_SIZE - upload_size;
|
|
ssize_t rcv_size = client_recv(c, httpbuf, next_size);
|
|
memcpy(imgbuf + upload_size, httpbuf, rcv_size);
|
|
upload_size += next_size;
|
|
}
|
|
|
|
client_dprintf(c, HTTPVER " 200 Good" CLRF "Connection: close" CLRF CLRF);
|
|
tsend();
|
|
return -1;
|
|
}
|
|
int nopath(struct client *c, struct http_param *p) {
|
|
int bodylen = snprintf(NULL, 0, "No Path: %.*s", p->path_len, p->path);
|
|
client_dprintf(c,
|
|
HTTPVER " 404 Not Found" CLRF "Connection: close" CLRF
|
|
"Content-Length: %d" CLRF
|
|
"Content-Type: text/plain" CLRF CLRF,
|
|
bodylen);
|
|
|
|
client_dprintf(c, "No Path: %.*s", p->path_len, p->path);
|
|
return -1;
|
|
}
|
|
/* http path handle */
|
|
|
|
void http_client_handle(struct conn *c, struct client *client, int client_id) {
|
|
printf("handle client\n");
|
|
ssize_t rcv_len;
|
|
size_t buflen = 0;
|
|
while ((rcv_len = read(client->fd, iobuf + buflen, sizeof(iobuf) - buflen)) ==
|
|
-1 &&
|
|
errno == EINTR)
|
|
;
|
|
|
|
if (rcv_len < 0) {
|
|
perror("failed to recv\n");
|
|
return;
|
|
} else if (rcv_len == 0) {
|
|
printf("closing request\n");
|
|
client->invalid = true;
|
|
return;
|
|
} else {
|
|
printf("read %d bytes\n", rcv_len);
|
|
}
|
|
|
|
struct http_param p;
|
|
p.headers_len = header_nums;
|
|
p.headers = headers;
|
|
|
|
int pret = phr_parse_request(iobuf, rcv_len, &p.method, &p.method_len,
|
|
&p.path, &p.path_len, &p.minor_ver, p.headers,
|
|
&p.headers_len, 0);
|
|
if (pret > 0) {
|
|
p.body_start = memmem(iobuf, rcv_len, "\r\n\r\n", 4);
|
|
if (p.body_start - iobuf + 1 >= rcv_len) {
|
|
p.body_start = NULL;
|
|
} else {
|
|
p.body_start += 4;
|
|
p.body_len = rcv_len - (p.body_start - iobuf);
|
|
}
|
|
printf("PATH: \"%.*s\"\n", (int)p.path_len, p.path);
|
|
int handler_ret = 0;
|
|
#define PEQ(g) (strlen(g) <= p.path_len && strncmp(g, p.path, strlen(g)) == 0)
|
|
if (PEQ("/api/tag") && p.method_len == 3 &&
|
|
strncmp("PUT", p.method, p.method_len) == 0) {
|
|
handler_ret = api_tag_upload(client, &p);
|
|
} else {
|
|
handler_ret = nopath(client, &p);
|
|
}
|
|
|
|
if (handler_ret == -1) {
|
|
client->invalid = true;
|
|
}
|
|
} else {
|
|
printf("can't handle request\n");
|
|
badreq(client);
|
|
client->invalid = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Rotates an 8-bit image 90 degrees clockwise IN-PLACE.
|
|
* @param data Pointer to the image buffer (1 byte per pixel)
|
|
* @param W Original width (e.g., 296)
|
|
* @param H Original height (e.g., 152)
|
|
* * NOTE: After this function, the logical width is H and height is W.
|
|
* The buffer must be large enough to hold W*H bytes.
|
|
*/
|
|
void rotate90_clockwise_inplace(uint8_t *data, int W, int H) {
|
|
int total_pixels = W * H;
|
|
|
|
// We need 1 bit per pixel to track if we've moved it.
|
|
// For 296x152, this is 44,992 bits = 5,624 bytes.
|
|
uint8_t *visited = (uint8_t *)calloc((total_pixels + 7) / 8, 1);
|
|
if (!visited)
|
|
return; // Handle allocation failure (OOM)
|
|
|
|
for (int i = 0; i < total_pixels; i++) {
|
|
// Skip if this pixel was already moved as part of a previous cycle
|
|
if (visited[i >> 3] & (1 << (i & 7)))
|
|
continue;
|
|
|
|
int curr_idx = i;
|
|
uint8_t val_to_move = data[i];
|
|
|
|
while (!(visited[curr_idx >> 3] & (1 << (curr_idx & 7)))) {
|
|
// Mark as visited
|
|
visited[curr_idx >> 3] |= (1 << (curr_idx & 7));
|
|
|
|
// Calculate new coordinates for 90-degree clockwise
|
|
// (x, y) -> (H - 1 - y, x)
|
|
int x = curr_idx % W;
|
|
int y = curr_idx / W;
|
|
|
|
// The NEW dimensions are Height H and Width W (swapped)
|
|
// New 1D Index = new_y * NEW_WIDTH + new_x
|
|
// For 90deg CW: new_x = H - 1 - y, new_y = x
|
|
int next_idx = x * H + (H - 1 - y);
|
|
|
|
// Swap values
|
|
uint8_t temp = data[next_idx];
|
|
data[next_idx] = val_to_move;
|
|
val_to_move = temp;
|
|
|
|
curr_idx = next_idx;
|
|
}
|
|
}
|
|
free(visited);
|
|
}
|
|
#define IMG_BLK_SIZE 1024
|
|
|
|
void flush_mpack(mpack_writer_t *wr, const char *b, size_t len) {
|
|
struct client *c = mpack_writer_context(wr);
|
|
printf("written %d bytes\n", len);
|
|
if (client_send(c, b, len) <= 0) {
|
|
mpack_writer_flag_error(wr, mpack_error_io);
|
|
}
|
|
}
|
|
|
|
void send_image(struct client *c) {
|
|
mpack_writer_t wr;
|
|
mpack_writer_init(&wr, iobuf, sizeof(iobuf));
|
|
mpack_writer_set_flush(&wr, flush_mpack);
|
|
mpack_writer_set_context(&wr, c);
|
|
for (uint64_t i = IMG_HEADER_SIZE; i < IMG_SIZE; i += IMG_BLK_SIZE) {
|
|
mpack_start_map(&wr, 3);
|
|
mpack_write_cstr(&wr, "command");
|
|
mpack_write_cstr(&wr, "update_image");
|
|
mpack_write_cstr(&wr, "index");
|
|
mpack_write_u64(&wr, i);
|
|
mpack_write_cstr(&wr, "data");
|
|
mpack_write_bin(&wr, &imgbuf[i], MIN(IMG_BLK_SIZE, IMG_SIZE - i));
|
|
mpack_finish_map(&wr);
|
|
mpack_writer_flush_message(&wr);
|
|
}
|
|
mpack_start_map(&wr, 1);
|
|
mpack_write_cstr(&wr, "command");
|
|
mpack_write_cstr(&wr, "push_image");
|
|
mpack_writer_destroy(&wr);
|
|
}
|
|
|
|
void tsend() {
|
|
|
|
int param_acc = 0, param_start = 0, param_len = 0;
|
|
int img_w = 0, img_h = 0;
|
|
for (int i = 0; i < sizeof(imgbuf); i++) {
|
|
if (imgbuf[i] == ' ' || imgbuf[i] == '\r' || imgbuf[i] == '\n') {
|
|
param_len = i - param_start;
|
|
switch (param_acc) {
|
|
case 1:
|
|
img_w = natoi(&imgbuf[param_start], param_len);
|
|
break;
|
|
case 2:
|
|
img_h = natoi(&imgbuf[param_start], param_len);
|
|
break;
|
|
}
|
|
|
|
param_start = i + 1;
|
|
param_acc += 1;
|
|
}
|
|
}
|
|
printf("img_w: %d, img_h: %d\n", img_w, img_h);
|
|
if (img_w == IMG_H_SIZE) {
|
|
rotate90_clockwise_inplace((uint8_t *)imgbuf + IMG_HEADER_SIZE, IMG_H_SIZE,
|
|
IMG_W_SIZE);
|
|
}
|
|
for (int i = 0; i < arrlen(workers.c); i++) {
|
|
send_image(workers.c + i);
|
|
}
|
|
}
|
|
|
|
int main_listen(const int worker_port, int http_port) {
|
|
// workers
|
|
int sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
|
struct sockaddr_in b = {0};
|
|
memset(&b, 0, sizeof(struct sockaddr_in));
|
|
b.sin_port = htons(worker_port);
|
|
b.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
b.sin_family = AF_INET;
|
|
int val = 1;
|
|
assert(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != -1);
|
|
assert(bind(sfd, (struct sockaddr *)&b, sizeof(b)) != -1);
|
|
assert(listen(sfd, 5) != -1);
|
|
printf("start listen\n");
|
|
int flags = fcntl(sfd, F_GETFL);
|
|
assert(fcntl(sfd, F_SETFL, flags | O_NONBLOCK) != -1);
|
|
|
|
// HTTP
|
|
|
|
int httpfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
memset(&b, 0, sizeof(struct sockaddr_in));
|
|
b.sin_port = htons(http_port);
|
|
b.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
b.sin_family = AF_INET;
|
|
val = 1;
|
|
assert(setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != -1);
|
|
assert(bind(httpfd, (struct sockaddr *)&b, sizeof(b)) != -1);
|
|
assert(listen(httpfd, 5) != -1);
|
|
printf("start listen\n");
|
|
flags = fcntl(httpfd, F_GETFL);
|
|
assert(fcntl(httpfd, F_SETFL, flags | O_NONBLOCK) != -1);
|
|
|
|
workers.interval.tv_usec = 300;
|
|
workers.rootfd = sfd;
|
|
conn_init(&workers);
|
|
http.interval.tv_usec = 300;
|
|
http.rootfd = httpfd;
|
|
conn_init(&http);
|
|
|
|
// event loop
|
|
while (!app_close) {
|
|
conn_poll_accept(&workers);
|
|
conn_poll_accept(&http);
|
|
conn_poll_clients(&http, &http_client_handle);
|
|
conn_collect_bad_client(&workers);
|
|
conn_collect_bad_client(&http);
|
|
|
|
// printf("iter\n");
|
|
// usleep(500000);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void mtsh_listen(struct Meshtalos *mtsh, const int worker_port, int http_port) {
|
|
main_listen(worker_port, http_port);
|
|
}
|
|
|
|
struct Meshtalos *mtsh_init() {
|
|
struct Meshtalos *m = calloc(sizeof(struct Meshtalos), 1);
|
|
return m;
|
|
}
|