shithub: moonfish

Download patch

ref: 9bf1eefe5c7c7aaef02fe75372fcdc33843a9c8e
parent: e8b9c92e4ff06afdd1e7bf4bd074ad3817b4d8ab
author: zamfofex <zamfofex@twdb.moe>
date: Wed Apr 3 21:24:52 EDT 2024

replace BearSSL with libtls

--- a/.build.yml
+++ b/.build.yml
@@ -6,8 +6,7 @@
   - 72a028fc-f8df-43d3-a315-305d80720e45
 packages:
   - build-base
-  - bearssl-dev
-  - bearssl-static
+  - libressl-dev
   - xz
   - mingw-w64-gcc
   - curl
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,5 @@
 !/tools/ugi.c
 !/tools/ugi-uci.c
 !/tools/uci-ugi.c
+!/tools/https.h
+!/tools/https.c
--- a/makefile
+++ b/makefile
@@ -10,8 +10,8 @@
 moonfish_cc := $(cc) -pthread -D_POSIX_C_SOURCE=199309L
 tools_cc := $(cc) -pthread -D_POSIX_C_SOURCE=200809L
 
-lichess_cc := $(tools_cc) -std=c99
-lichess_libs := -lbearssl -lcjson
+tools_src := moonfish.h tools/tools.h tools/utils.c chess.c
+ugi_src := $(tools_src) tools/ugi.h tools/ugi.c tools/ugi-uci.c
 
 .PHONY: all clean install uninstall
 
@@ -19,14 +19,17 @@
 
 moonfish moonfish.exe moonfish.wasm: moonfish.h chess.c search.c main.c
 	$(moonfish_cc) -o $@ $(filter %.c,$^)
+	
+%: $(tools_src) tools/%.c
+	$(tools_cc) -o $@ $(filter %.c,$^)
 
-%: moonfish.h tools/tools.h tools/%.c tools/utils.c chess.c
-	$(or $($(@)_cc),$(tools_cc)) -o $@ $(filter %.c,$^) $($(@)_libs)
+lichess: $(tools_src) tools/lichess.c tools/https.c
+	$(tools_cc) -o $@ $(filter %.c,$^) -ltls -lssl -lcrypto -lcjson
 
-ugi-uci: moonfish.h tools/tools.h tools/ugi.h tools/utils.c tools/ugi.c tools/ugi-uci.c chess.c
+ugi-uci: $(ugi_src)
 	$(tools_cc) -o $@ $(filter %.c,$^)
 
-uci-ugi: tools/tools.h tools/ugi.h tools/utils.c tools/ugi.c tools/uci-ugi.c
+uci-ugi: $(ugi_src)
 	$(tools_cc) -o $@ $(filter %.c,$^)
 
 clean:
--- /dev/null
+++ b/tools/https.c
@@ -1,0 +1,211 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "https.h"
+
+int moonfish_read(char *argv0, struct tls *tls, void *data0, size_t length)
+{
+	char *data;
+	ssize_t result;
+	
+	data = data0;
+	
+	while (length > 0)
+	{
+		result = tls_read(tls, data, length);
+		if (result == 0) return 1;
+		if (result == TLS_WANT_POLLIN || result == TLS_WANT_POLLOUT) continue;
+		if (result == -1)
+		{
+			fprintf(stderr, "%s: %s\n", argv0, tls_error(tls));
+			exit(1);
+		}
+		
+		data += result;
+		length -= result;
+	}
+	
+	return 0;
+}
+
+int moonfish_write(char *argv0, struct tls *tls, void *data0, size_t length)
+{
+	char *data;
+	ssize_t result;
+	
+	data = data0;
+	
+	while (length > 0)
+	{
+		result = tls_write(tls, data, length);
+		if (result == 0) return 1;
+		if (result == TLS_WANT_POLLIN || result == TLS_WANT_POLLOUT) continue;
+		if (result == -1)
+		{
+			fprintf(stderr, "%s: %s\n", argv0, tls_error(tls));
+			exit(1);
+		}
+		
+		data += result;
+		length -= result;
+	}
+	
+	return 0;
+}
+
+int moonfish_write_text(char *argv0, struct tls *tls, char *text)
+{
+	return moonfish_write(argv0, tls, text, strlen(text));
+}
+
+char *moonfish_read_line(char *argv0, struct tls *tls)
+{
+	char *line;
+	size_t length;
+	
+	line = NULL;
+	length = 0;
+	
+	for (;;)
+	{
+		line = realloc(line, length + 1);
+		if (line == NULL)
+		{
+			perror(argv0);
+			exit(1);
+		}
+		
+		if (moonfish_read(argv0, tls, line + length, 1))
+		{
+			if (length == 0)
+			{
+				free(line);
+				return NULL;
+			}
+			line[length] = 0;
+			return line;
+		}
+		if (line[length] == '\n')
+		{
+			line[length] = 0;
+			return line;
+		}
+		if (line[length] != '\r') length++;
+	}
+}
+
+void moonfish_request(char *argv0, struct tls *tls, char *host, char *request, char *token, char *type, int length)
+{
+	char length_string[64];
+	
+	moonfish_write_text(argv0, tls, request);
+	moonfish_write_text(argv0, tls, " HTTP/1.0\r\n");
+	
+	if (token != NULL)
+	{
+		moonfish_write_text(argv0, tls, "Authorization: Bearer ");
+		moonfish_write_text(argv0, tls, token);
+		moonfish_write_text(argv0, tls, "\r\n");
+	}
+	
+	moonfish_write_text(argv0, tls, "Connection: close\r\n");
+	
+	moonfish_write_text(argv0, tls, "Host: ");
+	moonfish_write_text(argv0, tls, host);
+	moonfish_write_text(argv0, tls, "\r\n");
+	
+	if (type != NULL)
+	{
+		moonfish_write_text(argv0, tls, "Content-Type: ");
+		moonfish_write_text(argv0, tls, type);
+		moonfish_write_text(argv0, tls, "\r\n");
+		
+		sprintf(length_string, "%d", length);
+		
+		moonfish_write_text(argv0, tls, "Content-Length: ");
+		moonfish_write_text(argv0, tls, length_string);
+		moonfish_write_text(argv0, tls, "\r\n");
+	}
+	
+	moonfish_write_text(argv0, tls, "User-Agent: moonfish/0\r\n\r\n");
+}
+
+int moonfish_response(char *argv0, struct tls *tls)
+{
+	static char success0[] = "HTTP/1.0 2";
+	static char success1[] = "HTTP/1.1 2";
+	
+	char *line;
+	
+	line = moonfish_read_line(argv0, tls);
+	
+	if (strncmp(line, success0, sizeof success0 - 1))
+	if (strncmp(line, success1, sizeof success1 - 1))
+		return 1;
+	
+	for (;;)
+	{
+		free(line);
+		line = moonfish_read_line(argv0, tls);
+		if (*line == 0)
+		{
+			free(line);
+			break;
+		}
+	}
+	
+	return 0;
+}
+
+struct tls *moonfish_connect(char *argv0, char *host, char *port)
+{
+	struct tls *tls;
+	
+	tls = tls_client();
+	if (tls == NULL)
+	{
+		fprintf(stderr, "%s: Could not create libtls client\n", argv0);
+		exit(1);
+	}
+	
+	if (tls_connect(tls, host, port))
+	{
+		fprintf(stderr, "%s: %s\n", argv0, tls_error(tls));
+		exit(1);
+	}
+	
+	return tls;
+}
+
+void moonfish_close(char *argv0, struct tls *tls)
+{
+	if (tls_close(tls) != 0)
+	{
+		fprintf(stderr, "%s: %s\n", argv0, tls_error(tls));
+		exit(1);
+	}
+	
+	tls_free(tls);
+}
+
+int moonfish_basic_request(char *argv0, char *host, char *port, char *request, char *token, char *type, char *body)
+{
+	int status;
+	struct tls *tls;
+	
+	tls = moonfish_connect(argv0, host, port);
+	
+	if (type == NULL && body != NULL) type = "application/x-www-form-urlencoded";
+	
+	moonfish_request(argv0, tls, host, token, request, type, body == NULL ? 0 : strlen(body));
+	
+	if (body != NULL) moonfish_write_text(argv0, tls, body);
+	
+	status = moonfish_response(argv0, tls);
+	moonfish_close(argv0, tls);
+	return status;
+}
--- /dev/null
+++ b/tools/https.h
@@ -1,0 +1,14 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#include <tls.h>
+
+int moonfish_read(char *argv0, struct tls *tls, void *data, size_t length);
+int moonfish_write(char *argv0, struct tls *tls, void *data, size_t length);
+int moonfish_write_text(char *argv0, struct tls *tls, char *text);
+char *moonfish_read_line(char *argv0, struct tls *tls);
+void moonfish_request(char *argv0, struct tls *tls, char *host, char *request, char *token, char *type, int length);
+int moonfish_response(char *argv0, struct tls *tls);
+int moonfish_basic_request(char *argv0, char *host, char *port, char *request, char *token, char *type, char *body);
+struct tls *moonfish_connect(char *argv0, char *host, char *port);
+void moonfish_close(char *argv0, struct tls *tls);
--- a/tools/lichess.c
+++ b/tools/lichess.c
@@ -1,27 +1,21 @@
 /* moonfish is licensed under the AGPL (v3 or later) */
 /* copyright 2023, 2024 zamfofex */
 
-#include <netdb.h>
 #include <string.h>
-#include <unistd.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <pthread.h>
 #include <signal.h>
 
-#include <bearssl.h>
 #include <cjson/cJSON.h>
 
 #include "../moonfish.h"
 #include "tools.h"
+#include "https.h"
 
-#define moonfish_write_text(io_ctx, text) br_sslio_write_all(io_ctx, text, strlen(text))
-#define moonfish_write_string(io_ctx, text) br_sslio_write_all(io_ctx, text, sizeof text - 1)
-
 struct moonfish_game
 {
 	char *argv0;
-	char *name;
+	char *host;
 	char *port;
 	char *token;
 	char id[512];
@@ -30,415 +24,6 @@
 	char fen[512];
 };
 
-static int moonfish_tcp(char *argv0, char *name, char *port)
-{
-	int fd;
-	int error;
-	struct addrinfo hints = {0};
-	struct addrinfo *infos, *info;
-	
-	hints.ai_family = AF_UNSPEC;
-	hints.ai_socktype = SOCK_STREAM;
-	hints.ai_flags = 0;
-	hints.ai_protocol = 0;
-	
-	error = getaddrinfo(name, port, &hints, &infos);
-	if (error)
-	{
-		fprintf(stderr, "%s: %s\n", argv0, gai_strerror(error));
-		exit(1);
-	}
-	
-	fd = -1;
-	
-	for (info = infos ; info != NULL ; info = info->ai_next)
-	{
-		fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
-		if (fd == -1) continue;
-		if (connect(fd, info->ai_addr, info->ai_addrlen) != -1) break;
-		close(fd);
-	}
-	
-	freeaddrinfo(infos);
-	
-	if (info == NULL)
-	{
-		fprintf(stderr, "%s: could not connect to [%s]:%s\n", argv0, name, port);
-		exit(1);
-	}
-	
-	return fd;
-}
-
-static int moonfish_read(void *data, unsigned char *buffer, size_t length)
-{
-	int fd, *fds;
-	ssize_t read_length;
-	
-	fds = data;
-	fd = *fds;
-	
-	for (;;)
-	{
-		read_length = read(fd, buffer, length);
-		if (read_length <= 0)
-		{
-			if (read_length < 0 && errno == EINTR) continue;
-			return -1;
-		}
-		return read_length;
-	}
-}
-
-static int moonfish_write(void *data, const unsigned char *buffer, size_t length)
-{
-	int fd, *fds;
-	ssize_t write_length;
-	
-	fds = data;
-	fd = *fds;
-	
-	for (;;)
-	{
-		write_length = write(fd, buffer, length);
-		if (write_length <= 0)
-		{
-			if (write_length < 0 && errno == EINTR) continue;
-			return -1;
-		}
-		return write_length;
-	}
-}
-
-static void moonfish_pem_data(void *data, const void *buffer, size_t length)
-{
-	br_x509_decoder_context *x509_ctx;
-	
-	if (length == 0) return;
-	
-	x509_ctx = data;
-	br_x509_decoder_push(x509_ctx, buffer, length);
-}
-
-static void moonfish_x509_data(void *data, const void *buffer, size_t length)
-{
-	br_x500_name *dn;
-	
-	if (length == 0) return;
-	
-	dn = data;
-	dn->len += length;
-	dn->data = realloc(dn->data, dn->len);
-	
-	if (dn->data == NULL)
-	{
-		perror(NULL);
-		exit(1);
-	}
-	
-	memcpy(dn->data + dn->len - length, buffer, length);
-}
-
-static void moonfish_copy_key(br_x509_pkey *to, br_x509_pkey *from)
-{
-	to->key_type = from->key_type;
-	
-	if (from->key_type == BR_KEYTYPE_RSA)
-	{
-		to->key.rsa.nlen = from->key.rsa.nlen;
-		to->key.rsa.n = malloc(to->key.rsa.nlen);
-		if (to->key.rsa.n == NULL)
-		{
-			perror(NULL);
-			exit(1);
-		}
-		memcpy(to->key.rsa.n, from->key.rsa.n, to->key.rsa.nlen);
-		
-		to->key.rsa.elen = from->key.rsa.elen;
-		to->key.rsa.e = malloc(to->key.rsa.elen);
-		if (to->key.rsa.e == NULL)
-		{
-			perror(NULL);
-			exit(1);
-		}
-		memcpy(to->key.rsa.e, from->key.rsa.e, to->key.rsa.elen);
-	}
-	
-	if (from->key_type == BR_KEYTYPE_EC)
-	{
-		to->key.ec.curve = from->key.ec.curve;
-		to->key.ec.qlen = from->key.ec.qlen;
-		to->key.ec.q = malloc(to->key.ec.qlen);
-		if (to->key.ec.q == NULL)
-		{
-			perror(NULL);
-			exit(1);
-		}
-		memcpy(to->key.ec.q, from->key.ec.q, to->key.ec.qlen);
-	}
-}
-
-static void moonfish_load_pem(char *argv0, FILE *file, br_x509_trust_anchor **tas, size_t *count)
-{
-	size_t read_length, n;
-	unsigned char buffer0[1024], *buffer;
-	br_pem_decoder_context pem_ctx;
-	br_x509_decoder_context x509_ctx;
-	br_x509_trust_anchor *trust_anchors, *trust_anchor;
-	size_t trust_anchor_count;
-	br_x509_pkey *pkey;
-	
-	br_pem_decoder_init(&pem_ctx);
-	
-	read_length = 0;
-	trust_anchors = NULL;
-	trust_anchor_count = 0;
-	
-	for (;;)
-	{
-		if (read_length == 0)
-		{
-			if (feof(file)) break;
-			read_length = fread(buffer0, 1, sizeof buffer0, file);
-			if (ferror(file))
-			{
-				fprintf(stderr, "%s: could not read certificat file\n", argv0);
-				exit(1);
-			}
-			buffer = buffer0;
-		}
-		
-		n = br_pem_decoder_push(&pem_ctx, buffer, read_length);
-		buffer += n;
-		read_length -= n;
-		
-		switch (br_pem_decoder_event(&pem_ctx))
-		{
-		case 0:
-			break;
-		default:
-			fprintf(stderr, "%s: PEM decoding failed\n", argv0);
-			exit(1);
-		case BR_PEM_BEGIN_OBJ:
-			trust_anchors = realloc(trust_anchors, sizeof *trust_anchors * (trust_anchor_count + 1));
-			if (trust_anchors == NULL)
-			{
-				perror(argv0);
-				exit(1);
-			}
-			trust_anchor = trust_anchors + trust_anchor_count;
-			trust_anchor->dn.len = 0;
-			trust_anchor->dn.data = NULL;
-			br_x509_decoder_init(&x509_ctx, &moonfish_x509_data, &trust_anchor->dn);
-			br_pem_decoder_setdest(&pem_ctx, &moonfish_pem_data, &x509_ctx);
-			break;
-		case BR_PEM_END_OBJ:
-			trust_anchor = trust_anchors + trust_anchor_count;
-			trust_anchor_count++;
-			pkey = br_x509_decoder_get_pkey(&x509_ctx);
-			if (pkey == NULL)
-			{
-				fprintf(stderr, "%s: X509 decoding failed: %d\n", argv0, br_x509_decoder_last_error(&x509_ctx));
-				exit(-1);
-			}
-			moonfish_copy_key(&trust_anchor->pkey, pkey);
-			trust_anchor->flags = 0;
-			if (br_x509_decoder_isCA(&x509_ctx)) trust_anchor->flags |= BR_X509_TA_CA;
-			break;
-		}
-	}
-	
-	*tas = trust_anchors;
-	*count = trust_anchor_count;
-}
-
-static void moonfish_request(
-	br_ssl_client_context *ctx,
-	br_sslio_context *io_ctx,
-	br_x509_minimal_context *min_ctx,
-	void *buffer,
-	size_t size,
-	char *argv0,
-	char *name,
-	char *port,
-	char *token,
-	char *request,
-	char *type,
-	int length,
-	int *fd
-)
-{
-	char length_string[64];
-	
-	static FILE *certs;
-	static br_x509_trust_anchor *tas = NULL;
-	static size_t count;
-	
-	if (tas == NULL)
-	{
-		certs = fopen("/etc/ssl/certs/ca-certificates.crt", "rb");
-		if (certs == NULL)
-		{
-			perror(argv0);
-			exit(1);
-		}
-		
-		moonfish_load_pem(argv0, certs, &tas, &count);
-		fclose(certs);
-	}
-	
-	br_ssl_client_init_full(ctx, min_ctx, tas, count);
-	br_ssl_engine_set_buffer(&ctx->eng, buffer, size, 1);
-	br_ssl_client_reset(ctx, name, 0);
-	
-	*fd = moonfish_tcp(argv0, name, port);
-	br_sslio_init(io_ctx, &ctx->eng, &moonfish_read, fd, &moonfish_write, fd);
-	
-	moonfish_write_text(io_ctx, request);
-	moonfish_write_string(io_ctx, " HTTP/1.0\r\n");
-	
-	moonfish_write_string(io_ctx, "Authorization: Bearer ");
-	moonfish_write_text(io_ctx, token);
-	moonfish_write_string(io_ctx, "\r\n");
-	
-	moonfish_write_string(io_ctx, "Connection: close\r\n");
-	
-	moonfish_write_string(io_ctx, "Host: ");
-	moonfish_write_text(io_ctx, name);
-	moonfish_write_string(io_ctx, "\r\n");
-	
-	if (type[0])
-	{
-		moonfish_write_string(io_ctx, "Content-Type: ");
-		moonfish_write_text(io_ctx, type);
-		moonfish_write_string(io_ctx, "\r\n");
-		
-		sprintf(length_string, "%d", length);
-		
-		moonfish_write_string(io_ctx, "Content-Length: ");
-		moonfish_write_text(io_ctx, length_string);
-		moonfish_write_string(io_ctx, "\r\n");
-	}
-	
-	moonfish_write_string(io_ctx, "User-Agent: moonfish/0\r\n\r\n");
-}
-
-static int moonfish_response(br_ssl_engine_context *ctx, br_sslio_context *io_ctx, char *argv0)
-{
-	static char success[] = "HTTP/1.0 2";
-	static char success1[] = "HTTP/1.1 2";
-	
-	char line[sizeof success];
-	char prev, cur;
-	int error;
-	
-	if (br_sslio_read_all(io_ctx, line, sizeof line - 1))
-	{
-		fprintf(stderr, "%s: malformed HTTP status\n", argv0);
-		exit(1);
-	}
-	
-	if (strncmp(line, success, sizeof line - 1))
-	if (strncmp(line, success1, sizeof line - 1))
-		return 1;
-	
-	prev = 0;
-	for (;;)
-	{
-		if (br_sslio_read_all(io_ctx, &cur, 1))
-		{
-			fprintf(stderr, "%s: malformed HTTP response\n", argv0);
-			exit(1);
-		}
-		if (prev == '\n' && cur == '\r') break;
-		if (prev == '\r' && cur == '\r') break;
-		if (prev == '\n' && cur == '\n') return 0;
-		prev = cur;
-	}
-	
-	if (br_sslio_read_all(io_ctx, &cur, 1) || cur != '\n')
-	{
-		fprintf(stderr, "%s: malformed HTTP header separator\n", argv0);
-		exit(1);
-	}
-	
-	error = br_ssl_engine_last_error(ctx);
-	if (error)
-	{
-		fprintf(stderr, "%s: BearSSL error: %d\n", argv0, error);
-		exit(1);
-	}
-	
-	return 0;
-}
-
-static int moonfish_basic_request(char *argv0, char *name, char *port, char *token, char *request, char *type, char *body)
-{
-	br_ssl_client_context ctx;
-	br_sslio_context io_ctx;
-	br_x509_minimal_context min_ctx;
-	void *buffer;
-	int fd;
-	int status;
-	
-	buffer = malloc(BR_SSL_BUFSIZE_BIDI);
-	
-	if (type[0] == 0 && body[0] != 0) type = "application/x-www-form-urlencoded";
-	
-	moonfish_request(
-		&ctx, &io_ctx, &min_ctx,
-		buffer, BR_SSL_BUFSIZE_BIDI,
-		argv0,
-		name, port,
-		token, request, type,
-		strlen(body),
-		&fd
-	);
-	
-	moonfish_write_text(&io_ctx, body);
-	br_sslio_flush(&io_ctx);
-	
-	status = moonfish_response(&ctx.eng, &io_ctx, argv0);
-	
-	close(fd);
-	free(buffer);
-	
-	return status;
-}
-
-static int moonfish_tls_line(br_ssl_engine_context *ctx, br_sslio_context *io_ctx, char *argv0, char *line, int length)
-{
-	int error;
-	
-	if (length-- == 0) return 0;
-	
-	for (;;)
-	{
-		if (!length--)
-		{
-			fprintf(stderr, "%s: line too long\n", argv0);
-			exit(1);
-		}
-		if (br_sslio_read_all(io_ctx, line, 1))
-		{
-			error = br_ssl_engine_last_error(ctx);
-			if (error)
-			{
-				fprintf(stderr, "%s: BearSSL error: %d\n", argv0, error);
-				exit(1);
-			}
-			*line = 0;
-			return 1;
-		}
-		if (*line == '\n') break;
-		line++;
-	}
-	
-	*line = 0;
-	
-	return 0;
-}
-
 static void moonfish_json_error(char *argv0)
 {
 	fprintf(stderr, "%s: malformed JSON\n", argv0);
@@ -447,9 +32,10 @@
 
 static pthread_mutex_t moonfish_mutex = PTHREAD_MUTEX_INITIALIZER;
 
-static void moonfish_handle_game_events(br_ssl_engine_context *ctx, br_sslio_context *io_ctx, struct moonfish_game *game, FILE *in, FILE *out)
+static void moonfish_handle_game_events(struct tls *tls, struct moonfish_game *game, FILE *in, FILE *out)
 {
-	char line[4096];
+	char request[2048];
+	char *line;
 	cJSON *root, *type, *state, *white_player, *id, *moves, *fen;
 	cJSON *wtime, *btime, *winc, *binc;
 	const char *end;
@@ -457,7 +43,6 @@
 	int move_count, count;
 	int i;
 	char *name, name0[6];
-	int done;
 	int variant;
 	struct moonfish_chess chess;
 	struct moonfish_move move;
@@ -478,10 +63,9 @@
 	root = NULL;
 	white = -1;
 	move_count = -1;
-	done = 0;
 	variant = 0;
 	
-	while (!done)
+	for (;;)
 	{
 		pthread_mutex_unlock(&moonfish_mutex);
 		
@@ -491,13 +75,21 @@
 			root = NULL;
 		}
 		
-		done = moonfish_tls_line(ctx, io_ctx, game->argv0, line, sizeof line);
+		line = moonfish_read_line(game->argv0, tls);
+		if (line == NULL) break;
+		
 		pthread_mutex_lock(&moonfish_mutex);
-		if (line[0] == 0) continue;
 		
+		if (line[0] == 0)
+		{
+			free(line);
+			continue;
+		}
+		
 		end = NULL;
 		root = cJSON_ParseWithOpts(line, &end, 1);
 		if (end != line + strlen(line)) moonfish_json_error(game->argv0);
+		free(line);
 		
 		if (!cJSON_IsObject(root)) moonfish_json_error(game->argv0);
 		
@@ -633,12 +225,12 @@
 			}
 		}
 		
-		snprintf(line, sizeof line, "POST /api/bot/game/%s/move/%s", game->id, name);
-		if (moonfish_basic_request(game->argv0, game->name, game->port, game->token, line, "", ""))
+		snprintf(request, sizeof request, "POST /api/bot/game/%s/move/%s", game->id, name);
+		if (moonfish_basic_request(game->argv0, game->host, game->port, game->token, request, NULL, NULL))
 		{
 			fprintf(stderr, "%s: could not make move '%s' in game '%s'\n", game->argv0, name, game->id);
-			snprintf(line, sizeof line, "POST /api/bot/game/%s/resign", game->id);
-			if (moonfish_basic_request(game->argv0, game->name, game->port, game->token, line, "", ""))
+			snprintf(request, sizeof request, "POST /api/bot/game/%s/resign", game->id);
+			if (moonfish_basic_request(game->argv0, game->host, game->port, game->token, request, NULL, NULL))
 				fprintf(stderr, "%s: could not resign game '%s'\n", game->argv0, game->id);
 			break;
 		}
@@ -655,61 +247,37 @@
 
 static void *moonfish_handle_game(void *data)
 {
-	char request[4096];
+	char request[2048];
 	struct moonfish_game *game;
 	FILE *in, *out;
-	br_ssl_client_context ctx;
-	br_sslio_context io_ctx;
-	br_x509_minimal_context min_ctx;
-	void *buffer;
-	int fd;
+	struct tls *tls;
 	
 	game = data;
 	
 	moonfish_spawn(game->argv0, game->argv, &in, &out);
+	tls = moonfish_connect(game->argv0, game->host, game->port);
 	
-	buffer = malloc(BR_SSL_BUFSIZE_BIDI);
-	
 	snprintf(request, sizeof request, "GET /api/bot/game/stream/%s", game->id);
 	
-	moonfish_request(
-		&ctx, &io_ctx, &min_ctx,
-		buffer, BR_SSL_BUFSIZE_BIDI,
-		game->argv0,
-		game->name, game->port,
-		game->token, request, "",
-		0, &fd
-	);
+	moonfish_request(game->argv0, tls, game->host, request, game->token, NULL, 0);
 	
-	br_sslio_flush(&io_ctx);
-	
-	if (moonfish_response(&ctx.eng, &io_ctx, game->argv0))
+	if (moonfish_response(game->argv0, tls))
 	{
 		fprintf(stderr, "%s: could not request game event stream\n", game->argv0);
 		exit(1);
 	}
 	
-	moonfish_handle_game_events(&ctx.eng, &io_ctx, game, in, out);
+	moonfish_handle_game_events(tls, game, in, out);
 	
+	moonfish_close(game->argv0, tls);
 	free(game);
-	free(buffer);
-	close(fd);
-	
 	return NULL;
 }
 
-static void moonfish_handle_events(
-	br_ssl_engine_context *ctx,
-	br_sslio_context *io_ctx,
-	char *argv0,
-	char *name,
-	char *port,
-	char *token,
-	char **argv,
-	char *username
-)
+static void moonfish_handle_events(struct tls *tls, char *argv0, char *host, char *port, char *token, char **argv, char *username)
 {
-	static char line[8192];
+	char request[2048];
+	char *line;
 	cJSON *root, *type, *challenge, *id, *variant, *speed, *fen;
 	const char *end;
 	struct moonfish_game *game;
@@ -728,17 +296,23 @@
 			root = NULL;
 		}
 		
-		if (moonfish_tls_line(ctx, io_ctx, argv0, line, sizeof line))
+		line = moonfish_read_line(argv0, tls);
+		if (line == NULL)
 		{
 			fprintf(stderr, "%s: connection with Lichess closed\n", argv0);
 			exit(1);
 		}
 		
-		if (line[0] == 0) continue;
+		if (line[0] == 0)
+		{
+			free(line);
+			continue;
+		}
 		
 		end = NULL;
 		root = cJSON_ParseWithOpts(line, &end, 1);
 		if (end != line + strlen(line)) moonfish_json_error(argv0);
+		free(line);
 		
 		if (!cJSON_IsObject(root)) moonfish_json_error(argv0);
 		
@@ -767,7 +341,7 @@
 			}
 			
 			game->argv0 = argv0;
-			game->name = name;
+			game->host = host;
 			game->port = port;
 			game->token = token;
 			strcpy(game->id, id->valuestring);
@@ -822,8 +396,8 @@
 			
 			if (invalid)
 			{
-				snprintf(line, sizeof line, "POST /api/challenge/%s/decline", id->valuestring);
-				if (moonfish_basic_request(argv0, name, port, token, line, "", "reason=standard"))
+				snprintf(request, sizeof request, "POST /api/challenge/%s/decline", id->valuestring);
+				if (moonfish_basic_request(argv0, host, port, token, request, NULL, "reason=standard"))
 					fprintf(stderr, "%s: could not decline challenge '%s' (chess 960 FEN)\n", argv0, id->valuestring);
 				continue;
 			}
@@ -830,8 +404,8 @@
 		}
 		else if (strcmp(variant->valuestring, "standard"))
 		{
-			snprintf(line, sizeof line, "POST /api/challenge/%s/decline", id->valuestring);
-			if (moonfish_basic_request(argv0, name, port, token, line, "", "reason=standard"))
+			snprintf(request, sizeof request, "POST /api/challenge/%s/decline", id->valuestring);
+			if (moonfish_basic_request(argv0, host, port, token, request, NULL, "reason=standard"))
 				fprintf(stderr, "%s: could not decline challenge '%s' (variant)\n", argv0, id->valuestring);
 			continue;
 		}
@@ -841,55 +415,42 @@
 		
 		if (!strcmp(speed->valuestring, "correspondence"))
 		{
-			snprintf(line, sizeof line, "POST /api/challenge/%s/decline", id->valuestring);
-			if (moonfish_basic_request(argv0, name, port, token, line, "", "reason=tooSlow"))
+			snprintf(request, sizeof request, "POST /api/challenge/%s/decline", id->valuestring);
+			if (moonfish_basic_request(argv0, host, port, token, request, NULL, "reason=tooSlow"))
 				fprintf(stderr, "%s: could not decline challenge '%s' (too slow)\n", argv0, id->valuestring);
 			continue;
 		}
 		
-		snprintf(line, sizeof line, "POST /api/challenge/%s/accept", id->valuestring);
-		if (moonfish_basic_request(argv0, name, port, token, line, "", ""))
+		snprintf(request, sizeof request, "POST /api/challenge/%s/accept", id->valuestring);
+		if (moonfish_basic_request(argv0, host, port, token, request, NULL, NULL))
 			fprintf(stderr, "%s: could not accept challenge '%s'\n", argv0, id->valuestring);
 	}
 }
 
-static char *moonfish_username(char *argv0, char *name, char *port, char *token)
+static char *moonfish_username(char *argv0, char *host, char *port, char *token)
 {
-	static char line[8192];
-	static char username[512];
-	
-	br_ssl_client_context ctx;
-	br_sslio_context io_ctx;
-	br_x509_minimal_context min_ctx;
-	void *buffer;
-	int fd;
+	char *line;
+	char *username;
 	const char *end;
 	cJSON *root, *id;
+	struct tls *tls;
 	
-	buffer = malloc(BR_SSL_BUFSIZE_BIDI);
+	tls = moonfish_connect(argv0, host, port);
 	
-	moonfish_request(
-		&ctx, &io_ctx, &min_ctx,
-		buffer, BR_SSL_BUFSIZE_BIDI,
-		argv0,
-		name, port,
-		token, "GET /api/account",
-		"", 0, &fd
-	);
+	moonfish_request(argv0, tls, host, "GET /api/account", token, NULL, 0);
 	
-	br_sslio_flush(&io_ctx);
-	
-	if (moonfish_response(&ctx.eng, &io_ctx, argv0))
+	if (moonfish_response(argv0, tls))
 	{
 		fprintf(stderr, "%s: could not request the Lichess username\n", argv0);
 		exit(1);
 	}
 	
-	moonfish_tls_line(&ctx.eng, &io_ctx, argv0, line, sizeof line);
+	line = moonfish_read_line(argv0, tls);
 	
 	end = NULL;
 	root = cJSON_ParseWithOpts(line, &end, 1);
 	if (end != line + strlen(line)) moonfish_json_error(argv0);
+	free(line);
 	
 	if (!cJSON_IsObject(root)) moonfish_json_error(argv0);
 	
@@ -896,19 +457,20 @@
 	id = cJSON_GetObjectItem(root, "id");
 	if (!cJSON_IsString(id)) moonfish_json_error(argv0);
 	
-	strcpy(username, id->valuestring);
+	username = strdup(id->valuestring);
+	if (username == NULL)
+	{
+		perror(argv0);
+		exit(0);
+	}
 	
-	br_sslio_close(&io_ctx);
+	moonfish_close(argv0, tls);
 	
-	free(buffer);
-	close(fd);
-	
 	return username;
 }
 
 int main(int argc, char **argv)
 {
-	static unsigned char buffer[BR_SSL_BUFSIZE_BIDI];
 	static char *format = "<cmd> <args>...";
 	static struct moonfish_arg args[] =
 	{
@@ -918,14 +480,11 @@
 	};
 	
 	char *token;
-	br_ssl_client_context ctx;
-	br_sslio_context io_ctx;
-	br_x509_minimal_context min_ctx;
-	int fd;
 	int i;
-	int error;
 	char **command;
 	int command_count;
+	struct tls *tls;
+	char *username;
 	
 	command = moonfish_args(args, format, argc, argv);
 	command_count = argc - (command - argv);
@@ -949,31 +508,17 @@
 	
 	if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) return 1;
 	
-	moonfish_request(&ctx, &io_ctx, &min_ctx, buffer, sizeof buffer, argv[0], args[0].value, args[1].value, token, "GET /api/stream/event", "", 0, &fd);
-	br_sslio_flush(&io_ctx);
-	if (moonfish_response(&ctx.eng, &io_ctx, argv[0]))
+	tls = moonfish_connect(argv[0], args[0].value, args[1].value);
+	moonfish_request(argv[0], tls, args[0].value, "GET /api/stream/event", token, NULL, 0);
+	if (moonfish_response(argv[0], tls))
 	{
 		fprintf(stderr, "%s: could not request event stream\n", argv[0]);
 		return 1;
 	}
 	
-	moonfish_handle_events(&ctx.eng, &io_ctx, argv[0], args[0].value, args[1].value, token, command, moonfish_username(argv[0], args[0].value, args[1].value, token));
+	username = moonfish_username(argv[0], args[0].value, args[1].value, token);
+	moonfish_handle_events(tls, argv[0], args[0].value, args[1].value, token, command, username);
 	
-	br_ssl_engine_close(&ctx.eng);
-	close(fd);
-	
-	if (br_ssl_engine_current_state(&ctx.eng) != BR_SSL_CLOSED)
-	{
-		fprintf(stderr,"%s: TLS connection closed improperly\n", argv[0]);
-		return 1;
-	}
-	
-	error = br_ssl_engine_last_error(&ctx.eng);
-	if (error)
-	{
-		fprintf(stderr, "%s: BearSSL error: %d\n", argv[0], error);
-		return 1;
-	}
-	
-	return 0;
+	fprintf(stderr, "%s: Unreachable\n", argv[0]);
+	return 1;
 }
--