summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLizzy Fleckenstein <lizzy@vlhl.dev>2024-06-19 18:07:47 +0200
committerLizzy Fleckenstein <lizzy@vlhl.dev>2024-06-19 18:07:55 +0200
commit49948b4cc0f73d02a8932c525690a35e8efb6ac5 (patch)
tree1ee33d5393046939a37fdef00b3484e87ff3ec6d
parentead2881be92d33076c2104dbd75bad3561f26088 (diff)
add client
Signed-off-by: Lizzy Fleckenstein <lizzy@vlhl.dev>
-rw-r--r--include/net.h (renamed from include/peer.h)18
-rw-r--r--include/ticker.h23
-rw-r--r--meson.build19
-rw-r--r--src/client.c125
-rw-r--r--src/net.c (renamed from src/peer.c)91
-rw-r--r--src/server.c75
-rw-r--r--src/ticker.c43
7 files changed, 312 insertions, 82 deletions
diff --git a/include/peer.h b/include/net.h
index f6baa4b..fcb12a4 100644
--- a/include/peer.h
+++ b/include/net.h
@@ -2,13 +2,18 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
-#ifndef PEER_H
-#define PEER_H
+#ifndef NET_H
+#define NET_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <poll.h>
+#include <fcntl.h>
+
+#include "str.h"
+
+#define SET_NONBLOCK(X) fcntl((X), F_SETFL, fcntl((X), F_GETFL, 0) | O_NONBLOCK)
#define PEER_INBUFFER_SIZE 0x100000 // 1MB
#define PEER_OUTBUFFER_SIZE 0x200000 // 2MB
@@ -26,16 +31,21 @@ typedef struct {
} in;
// TODO: ring buffer
struct {
- size_t avail;
+ size_t avail;
size_t cursor;
uint8_t *buffer;
} out;
} peer;
+void invalid_pkt(str from, str pkt);
+
+int socket_create(const char *host, const char *port, bool server);
+int socket_accept(int accept_fd);
+
void peer_init(peer *p, int socket);
void peer_free(peer *p);
struct pollfd peer_prepare(peer *p);
-bool peer_ready(peer *p, struct pollfd revents);
+str peer_recv(peer *p, struct pollfd pfd);
bool peer_send(peer *p, void *data, size_t len);
#endif
diff --git a/include/ticker.h b/include/ticker.h
new file mode 100644
index 0000000..f6387c4
--- /dev/null
+++ b/include/ticker.h
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2024 Lizzy Fleckenstein <lizzy@vlhl.dev>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+#ifndef TICKER_H
+#define TICKER_H
+
+#include <time.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#define NANOS 1000000000
+
+typedef struct {
+ struct timespec timestamp;
+ uint64_t freq_nanos;
+} ticker;
+
+void ticker_init(ticker *t, uint64_t f);
+bool ticker_tick(ticker *t, uint64_t *dtime);
+int ticker_timeout(ticker *t);
+
+#endif
diff --git a/meson.build b/meson.build
index e1b8dce..81dc491 100644
--- a/meson.build
+++ b/meson.build
@@ -11,16 +11,19 @@ deps = [
src = [
'src/str.c',
- 'src/peer.c',
+ 'src/net.c',
'src/ser.c',
+ 'src/ticker.c',
]
-sillyserver = executable('sillyserver',
+include = 'include/'
+
+executable('sillyserver',
sources: [
src,
'src/server.c',
],
- include_directories: 'include/',
+ include_directories: include,
dependencies: [
deps,
dependency('libpng'),
@@ -28,4 +31,12 @@ sillyserver = executable('sillyserver',
install: true,
)
-run_target('run', command: sillyserver)
+executable('sillyclient',
+ sources: [
+ src,
+ 'src/client.c',
+ ],
+ include_directories: include,
+ dependencies: deps,
+ install: true,
+)
diff --git a/src/client.c b/src/client.c
new file mode 100644
index 0000000..0dabbbf
--- /dev/null
+++ b/src/client.c
@@ -0,0 +1,125 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+#include <poll.h>
+#include <errno.h>
+#include <ctype.h>
+#include "str.h"
+#include "ser.h"
+#include "net.h"
+#include "ticker.h"
+
+typedef struct {
+ peer conn;
+ struct termios oldtio;
+ str name;
+ str pass;
+} client;
+
+void gfx_alt_buffer(bool enable)
+{
+ if (enable)
+ printf("\e[?1049h\e[?25l");
+ else
+ printf("\e[?1049l\e[?25h");
+}
+
+void gfx_render(client *c, uint64_t dtime)
+{
+ // TODO: render game
+}
+
+void client_exit(client *c, int ret)
+{
+ free(c->name.data);
+ free(c->pass.data);
+
+ peer_free(&c->conn);
+ tcsetattr(STDIN_FILENO, TCSANOW, &c->oldtio);
+ gfx_alt_buffer(false);
+
+ exit(ret);
+}
+
+void handle_input(client *c, char ch)
+{
+ ch = tolower(ch);
+ switch (ch) {
+ case 'q': client_exit(c, EXIT_SUCCESS); break;
+ default: break;
+ }
+}
+
+bool handle_pkt(client *c, str pkt)
+{
+ // TODO: handle network traffic
+ return true;
+}
+
+int main()
+{
+ client c = {0};
+
+ c.name = str_clone(S("kitten"));
+ c.pass = str_clone(S(""));
+
+ c.conn.socket = -1;
+
+ tcgetattr(STDIN_FILENO, &c.oldtio); // TODO: handle error
+
+ struct termios newtio = c.oldtio;
+ newtio.c_lflag &= ~(ICANON | ECHO);
+ tcsetattr(STDIN_FILENO, TCSANOW, &newtio); // TODO: handle error
+
+ SET_NONBLOCK(STDIN_FILENO); // TODO: handle error
+
+ int socket = socket_create("127.0.0.1", "4560", false);
+ if (socket < 0)
+ client_exit(&c, EXIT_FAILURE);
+ peer_init(&c.conn, socket);
+
+ gfx_alt_buffer(true);
+
+ ticker t;
+ ticker_init(&t, NANOS/60);
+
+ for (;;) {
+ struct pollfd fds[2];
+
+ fds[0].fd = STDIN_FILENO;
+ fds[0].events = POLLIN;
+
+ fds[1] = peer_prepare(&c.conn);
+
+ if (poll(fds, 2, ticker_timeout(&t)) < 0) {
+ switch (errno) {
+ case EINTR: continue;
+ default: perror("poll"); continue;
+ }
+ }
+
+ if (fds[0].revents) {
+ // this is cursed
+ // maybe dont do this idk
+ char ch;
+ errno = 0;
+ while (read(STDIN_FILENO, &ch, 1) == 1)
+ handle_input(&c, ch);
+ switch (errno) {
+ case 0: case EWOULDBLOCK: case EINTR: break;
+ default: perror("read"); break;
+ }
+ }
+
+ str pkt = peer_recv(&c.conn, fds[1]);
+ if (c.conn.disco)
+ client_exit(&c, EXIT_SUCCESS);
+ if (pkt.len > 0 && !handle_pkt(&c, pkt))
+ invalid_pkt(S("server"), pkt);
+
+ uint64_t dtime;
+ if (ticker_tick(&t, &dtime))
+ gfx_render(&c, dtime);
+ }
+}
diff --git a/src/peer.c b/src/net.c
index f984aec..18970c1 100644
--- a/src/peer.c
+++ b/src/net.c
@@ -8,7 +8,73 @@
#include <unistd.h>
#include <poll.h>
#include <errno.h>
-#include "peer.h"
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "net.h"
+
+void invalid_pkt(str from, str pkt)
+{
+ // TODO: maybe inform peer about failure? not sure
+ // FIXME: hexdumping a gazillon bytes to stderr might be an issue
+ fprintf(stderr, "invalid pkt from %*s: ", PSTR(from));
+ for (size_t i = 0; i < pkt.len; i++)
+ fprintf(stderr, "%02x%c", (uint8_t) pkt.data[i], i+1 == pkt.len ? '\n' : ' ');
+}
+
+int socket_create(const char *host, const char *port, bool server)
+{
+ struct addrinfo *info = NULL, hints = {0};
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ int err;
+ if ((err = getaddrinfo(host, port, &hints, &info))) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
+ return -1;
+ }
+
+#define TRY(op, val) if ((val) < 0) { perror(op); close(fd); freeaddrinfo(info); return -1; }
+
+ int fd = -1;
+ TRY("socket", fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol))
+
+ int flag = 1;
+ TRY("setsockopt", setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &flag, sizeof flag))
+
+ if (server) {
+ TRY("bind", bind(fd, info->ai_addr, info->ai_addrlen))
+ TRY("listen", listen(fd, 3))
+ } else {
+ TRY("connect", connect(fd, info->ai_addr, info->ai_addrlen))
+ TRY("fcntl", SET_NONBLOCK(fd))
+ }
+
+#undef TRY
+
+ freeaddrinfo(info);
+ return fd;
+}
+
+int socket_accept(int accept_fd)
+{
+ int socket = accept(accept_fd, NULL, NULL); // TODO: save address
+ if (socket < 0) {
+ perror("accept");
+ return -1;
+ }
+
+ if (SET_NONBLOCK(socket) < 0) {
+ perror("fcntl");
+ close(socket);
+ return -1;
+ }
+
+ return socket;
+}
void peer_init(peer *p, int socket)
{
@@ -43,10 +109,11 @@ static void next_in(peer *p, bool header, size_t len)
static bool peer_in_ready(peer *p)
{
+ errno = 0;
ssize_t got = read(p->socket, p->in.buffer + p->in.len, p->in.promised - p->in.len);
- if (got < 0) {
+ if (got <= 0) {
switch (errno) {
- case ECONNRESET:
+ case 0: case ECONNRESET:
p->disco = true;
return true;
case EINTR:
@@ -63,7 +130,7 @@ static bool peer_in_ready(peer *p)
return true;
size_t len = *(pkt_header *) p->in.buffer;
- if (len > PEER_INBUFFER_SIZE)
+ if (len > PEER_INBUFFER_SIZE || len == 0)
// TODO: figure out what to do if packet too large (disconnect?)
next_in(p, true, sizeof(pkt_header));
else
@@ -148,17 +215,17 @@ struct pollfd peer_prepare(peer *p)
return pfd;
}
-bool peer_ready(peer *p, struct pollfd pfd)
+str peer_recv(peer *p, struct pollfd pfd)
{
- bool x = false;
-
if (pfd.revents & (POLLHUP | POLLERR))
p->disco = true;
- if (pfd.revents & POLLIN)
- x = x || peer_in_ready(p);
- if (pfd.revents & POLLOUT)
- x = x || peer_out_ready(p);
+ if (!p->disco && pfd.revents & POLLOUT)
+ peer_out_ready(p);
+
+ if (!p->disco && pfd.revents & POLLIN)
+ if (peer_in_ready(p))
+ return (str) { p->in.len, (char *) p->in.buffer };
- return x;
+ return NILS;
}
diff --git a/src/server.c b/src/server.c
index fa48b25..621acae 100644
--- a/src/server.c
+++ b/src/server.c
@@ -11,18 +11,12 @@
#include <endian.h>
#include <unistd.h>
#include <inttypes.h>
-#include <fcntl.h>
#include <errno.h>
#include <poll.h>
-#include <netdb.h>
#include <png.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
#include "array.h"
#include "content.h"
-#include "peer.h"
+#include "net.h"
#include "str.h"
#include "ser.h"
@@ -56,6 +50,7 @@ typedef struct {
} player;
typedef struct {
+ int accept_fd;
str motd;
str passphrase;
array(map) maps;
@@ -179,36 +174,6 @@ bool map_load(map *m)
#undef TRY
}
-int net_listen(const char *host, const char *port)
-{
- struct addrinfo *info = NULL, hints = {0};
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
-
- int err;
- if ((err = getaddrinfo(host, port, &hints, &info))) {
- fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
- return -1;
- }
-
-#define TRY(op, val) if ((val) < 0) { perror(op); freeaddrinfo(info); return -1; }
-
- int fd;
- TRY("socket", fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol))
-
- int flag = 1;
- TRY("setsockopt", setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &flag, sizeof flag))
-
- TRY("bind", bind(fd, info->ai_addr, info->ai_addrlen))
- TRY("listen", listen(fd, 3))
-
-#undef TRY
-
- freeaddrinfo(info);
- return fd;
-
-}
-
void ser_color(strbuf *w, color c)
{
ser_u8(w, c.r);
@@ -336,7 +301,7 @@ bool handle_hi(str pkt, player *p, game *g)
return true;
}
-bool process_pkt(str pkt, player *p, game *g)
+bool handle_pkt(str pkt, player *p, game *g)
{
pkt_type type;
if (!deser_pkt_type(&pkt, &type))
@@ -355,6 +320,7 @@ int main()
{
game g = {0};
+ g.accept_fd = -1;
g.entity_id = 1;
g.motd = str_clone(S("Welcome to test server"));
g.passphrase = str_clone(S(""));
@@ -376,8 +342,7 @@ int main()
if (!map_load(&g.maps.data[0]))
game_exit(&g, EXIT_FAILURE);
- int accept_fd = net_listen("0.0.0.0", "4560");
- if (accept_fd < 0)
+ if ((g.accept_fd = socket_create("0.0.0.0", "4560", true)) < 0)
game_exit(&g, EXIT_FAILURE);
for (;;) {
@@ -394,7 +359,7 @@ int main()
fds[i++] = peer_prepare(&p->conn);
}
- fds[g.players.len].fd = accept_fd;
+ fds[g.players.len].fd = g.accept_fd;
fds[g.players.len].events = POLLIN;
if (poll(fds, g.players.len + 1, -1) < 0) {
@@ -406,32 +371,18 @@ int main()
for (size_t i = 0; i < g.players.len; i++) {
player *p = &g.players.data[i];
- if (!peer_ready(&p->conn, fds[i]))
- continue;
- if (p->conn.disco)
+
+ str pkt = peer_recv(&p->conn, fds[i]);
+ if (p->conn.disco || pkt.len == 0)
continue;
- str pkt = { p->conn.in.len, (char *) p->conn.in.buffer };
- if (!process_pkt(pkt, p, &g)) {
- // TODO: maybe inform client about failure? not sure
- // FIXME: hexdumping a gazillon bytes to stderr might be an issue
- fprintf(stderr, "invalid pkt from %*s: ", PSTR(p->name));
- for (size_t i = 0; i < pkt.len; i++)
- fprintf(stderr, "%02x%c", (uint8_t) pkt.data[i], i+1 == pkt.len ? '\n' : ' ');
- }
+ if (!handle_pkt(pkt, p, &g))
+ invalid_pkt(p->name, pkt);
}
if (fds[g.players.len].revents) {
- int socket = accept(accept_fd, NULL, NULL); // TODO: save ip
- if (socket < 0) {
- perror("accept");
+ int socket = socket_accept(g.accept_fd);
+ if (socket < 0)
continue;
- }
-
- if (fcntl(socket, F_SETFL, fcntl(socket, F_GETFL, 0) | O_NONBLOCK) < 0) {
- close(socket);
- perror("fcntl");
- continue;
- }
printf("new player\n");
player *p = ARR_APPEND(g.players);
diff --git a/src/ticker.c b/src/ticker.c
new file mode 100644
index 0000000..d3c25b5
--- /dev/null
+++ b/src/ticker.c
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: 2024 Lizzy Fleckenstein <lizzy@vlhl.dev>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+#include "ticker.h"
+
+static struct timespec timestamp_now()
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts); // TODO: handle error
+ return ts;
+}
+
+static uint64_t timestamp_diff(struct timespec a, struct timespec b)
+{
+ return NANOS * ((uint64_t) a.tv_sec - b.tv_sec) + ((int64_t) a.tv_nsec - b.tv_nsec);
+}
+
+void ticker_init(ticker *t, uint64_t f)
+{
+ t->freq_nanos = f;
+ t->timestamp = timestamp_now();
+}
+
+bool ticker_tick(ticker *t, uint64_t *dtime)
+{
+ struct timespec ts = timestamp_now();
+ *dtime = timestamp_diff(ts, t->timestamp);
+ if (*dtime < t->freq_nanos)
+ return false;
+ t->timestamp = ts;
+ return true;
+}
+
+// millis
+int ticker_timeout(ticker *t)
+{
+ uint64_t nanos = timestamp_diff(timestamp_now(), t->timestamp);
+ if (t->freq_nanos > nanos)
+ return (t->freq_nanos - nanos) / 1000000; // nanos to millis
+ else
+ return 0;
+}