Major refactoring
This commit is contained in:
@@ -15,3 +15,6 @@ example_large
|
|||||||
examples/example
|
examples/example
|
||||||
.cluster/
|
.cluster/
|
||||||
vsr_boot_marker
|
vsr_boot_marker
|
||||||
|
*.pem
|
||||||
|
*.exe
|
||||||
|
.claude/
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
CC ?= gcc
|
|
||||||
CFLAGS ?= -Wall -Wextra -ggdb -O0
|
|
||||||
AR ?= ar
|
|
||||||
|
|
||||||
INCLUDES = -Iquakey/include -Iinclude -I.
|
|
||||||
|
|
||||||
# ---- Library (client-side only) ----
|
|
||||||
|
|
||||||
LIB_SRCS = src/client.c src/tcp.c src/byte_queue.c src/message.c src/basic.c
|
|
||||||
LIB_OBJS = $(LIB_SRCS:.c=.o)
|
|
||||||
|
|
||||||
STATIC_LIB = libtoastyfs.a
|
|
||||||
SHARED_LIB = libtoastyfs.so
|
|
||||||
|
|
||||||
# ---- Server binary ----
|
|
||||||
|
|
||||||
SERVER_SRCS = src/basic.c src/file_system.c src/byte_queue.c src/message.c \
|
|
||||||
src/tcp.c src/server.c src/main.c src/log.c src/client_table.c \
|
|
||||||
src/chunk_store.c src/metadata.c
|
|
||||||
|
|
||||||
# ---- Client binary (random test client) ----
|
|
||||||
|
|
||||||
CLIENT_SRCS = src/basic.c src/file_system.c src/byte_queue.c src/message.c \
|
|
||||||
src/tcp.c src/server.c src/client.c src/random_client.c src/main.c \
|
|
||||||
src/log.c src/client_table.c src/chunk_store.c src/metadata.c
|
|
||||||
|
|
||||||
# ---- Simulation binary ----
|
|
||||||
|
|
||||||
SIM_SRCS = src/basic.c src/file_system.c src/byte_queue.c src/message.c \
|
|
||||||
src/tcp.c src/server.c src/client.c src/random_client.c src/main.c \
|
|
||||||
src/log.c src/client_table.c src/invariant_checker.c src/chunk_store.c \
|
|
||||||
src/metadata.c quakey/src/mockfs.c quakey/src/quakey.c
|
|
||||||
|
|
||||||
# ---- Default target ----
|
|
||||||
|
|
||||||
all: $(STATIC_LIB) $(SHARED_LIB) toastyfs toastyfs_client toastyfs_simulation
|
|
||||||
|
|
||||||
# ---- Library targets ----
|
|
||||||
|
|
||||||
lib: $(STATIC_LIB) $(SHARED_LIB)
|
|
||||||
|
|
||||||
$(STATIC_LIB): $(LIB_OBJS)
|
|
||||||
$(AR) rcs $@ $^
|
|
||||||
|
|
||||||
$(SHARED_LIB): $(LIB_SRCS)
|
|
||||||
$(CC) $(CFLAGS) -shared -fPIC $(INCLUDES) $^ -o $@
|
|
||||||
|
|
||||||
src/%.o: src/%.c
|
|
||||||
$(CC) $(CFLAGS) $(INCLUDES) -fPIC -c $< -o $@
|
|
||||||
|
|
||||||
# ---- Binary targets ----
|
|
||||||
|
|
||||||
toastyfs: $(SERVER_SRCS)
|
|
||||||
$(CC) $(CFLAGS) $(INCLUDES) -DMAIN_SERVER $^ -o $@
|
|
||||||
|
|
||||||
toastyfs_client: $(CLIENT_SRCS)
|
|
||||||
$(CC) $(CFLAGS) $(INCLUDES) -DMAIN_CLIENT $^ -o $@
|
|
||||||
|
|
||||||
toastyfs_simulation: $(SIM_SRCS)
|
|
||||||
$(CC) $(CFLAGS) $(INCLUDES) -DMAIN_SIMULATION -DFAULT_INJECTION $^ -o $@
|
|
||||||
|
|
||||||
# ---- Install ----
|
|
||||||
|
|
||||||
PREFIX ?= /usr/local
|
|
||||||
LIBDIR ?= $(PREFIX)/lib
|
|
||||||
INCLUDEDIR?= $(PREFIX)/include
|
|
||||||
|
|
||||||
install: $(STATIC_LIB) $(SHARED_LIB)
|
|
||||||
install -d $(DESTDIR)$(LIBDIR)
|
|
||||||
install -d $(DESTDIR)$(INCLUDEDIR)
|
|
||||||
install -m 644 $(STATIC_LIB) $(DESTDIR)$(LIBDIR)/
|
|
||||||
install -m 755 $(SHARED_LIB) $(DESTDIR)$(LIBDIR)/
|
|
||||||
install -m 644 include/toastyfs.h $(DESTDIR)$(INCLUDEDIR)/
|
|
||||||
|
|
||||||
# ---- Clean ----
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(LIB_OBJS) $(STATIC_LIB) $(SHARED_LIB)
|
|
||||||
rm -f toastyfs toastyfs_client toastyfs_simulation
|
|
||||||
|
|
||||||
.PHONY: all lib clean install
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
# ToastyFS
|
|
||||||
|
|
||||||
ToastyFS is a distributed and fault-tolerant object storage.
|
|
||||||
|
|
||||||
WARNING: I'm still finishing up :) Log compaction and persistence are missing.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
First, build ToastyFS by running the build script:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This will produce three executables: `toastyfs`, `toastyfs_random_client` and `toastyfs_simulation`.
|
|
||||||
|
|
||||||
The `toastyfs` is the one you need to run. The other executables are for testing purposes.
|
|
||||||
|
|
||||||
You can pick a cluster size based on how many faults you want it to handle. Let's start with a cluster size of 3 which will allow one node to crash at a given time.
|
|
||||||
|
|
||||||
Run three instances of `toastyfs` while specifying to each one the addresses of the others:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./toastyfs --addr 127.0.0.1:8081 --peer 127.0.0.1:8082 --peer 127.0.0.1:8083
|
|
||||||
./toastyfs --addr 127.0.0.1:8082 --peer 127.0.0.1:8081 --peer 127.0.0.1:8083
|
|
||||||
./toastyfs --addr 127.0.0.1:8083 --peer 127.0.0.1:8081 --peer 127.0.0.1:8082
|
|
||||||
```
|
|
||||||
|
|
||||||
The cluster is now working.
|
|
||||||
|
|
||||||
To upload/download/delete objects, you need to compile a client program which needs the toasty client library.
|
|
||||||
|
|
||||||
To build the library, run the following, which will generate the `libtoasty.a` and `libtoasty.so` files:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
Makefile
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use the following example program which will upload a simple object:
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <time.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <toastyfs.h>
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
srand(time(NULL));
|
|
||||||
|
|
||||||
char addr1[] = "127.0.0.1:8081";
|
|
||||||
char addr2[] = "127.0.0.1:8082";
|
|
||||||
char addr3[] = "127.0.0.1:8083";
|
|
||||||
char *addrs[] = { addr1, addr2, addr3 };
|
|
||||||
|
|
||||||
printf("Connecting to cluster...\n");
|
|
||||||
ToastyFS *tfs = toastyfs_init(rand(), addrs, 3);
|
|
||||||
if (tfs == NULL) {
|
|
||||||
fprintf(stderr, "toastyfs_init failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char key[] = "hello";
|
|
||||||
char data[] = "world";
|
|
||||||
|
|
||||||
printf("PUT key=\"%s\" data=\"%s\" (%d bytes)\n", key, data, (int)strlen(data));
|
|
||||||
|
|
||||||
ToastyFS_Result res;
|
|
||||||
int ret = toastyfs_put(tfs, key, strlen(key), data, strlen(data), &res);
|
|
||||||
if (ret < 0) {
|
|
||||||
fprintf(stderr, "toastyfs_put returned %d\n", ret);
|
|
||||||
toastyfs_free(tfs);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Done.\n");
|
|
||||||
toastyfs_free(tfs);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Save it as `example_client.c` and compile it by running:
|
|
||||||
|
|
||||||
```
|
|
||||||
gcc example_client.c libtoasty.a -o example
|
|
||||||
```
|
|
||||||
|
|
||||||
By running the client while the cluster is running, the object will be created!
|
|
||||||
|
|
||||||
## Fault Tolerance
|
|
||||||
|
|
||||||
ToastyFS allows any minority of nodes to be dead at any given time and continue running without issues. Generally speaking, if your system has 2f+1 nodes, it can work without f of them.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
ToastyFS is tested with Deterministic Simulation Testing (DST).
|
|
||||||
|
|
||||||
The entire system (server nodes and clients) is simulated in the memory of a single process deterministically. Faults (node crashes, network partitons) and latencies are pseudo-randonly injected in the simulation such that a variety of scenarios are tested but in a way that can be perfectly reproduced by using the same simulation seed from which all random events are chosen.
|
|
||||||
|
|
||||||
After each node schedulation, the simulator inspects all node states at the same instant and verifies that none of the system's invariant were broken.
|
|
||||||
|
|
||||||
You can build and run the simulation by doing:
|
|
||||||
|
|
||||||
```
|
|
||||||
./build.sh
|
|
||||||
./toastyfs_simulation
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
[ ] Add log compaction
|
[ ] Add log compaction
|
||||||
[ ] Add log persistence
|
[ ] Add log persistence
|
||||||
[ ] Add chunk locations to metadata
|
[ ] Add chunk locations to metadata
|
||||||
|
[ ] Remove sender_idx fields in messages since it's provided by the message header
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
set INCLUDES=-Iquakey/include -Iinclude -I.
|
||||||
|
set CFLAGS=-Wall -Wextra -ggdb -O0
|
||||||
|
|
||||||
|
set LIB_CORE=lib/basic.c lib/byte_queue.c lib/tcp.c
|
||||||
|
set LIB_FULL=%LIB_CORE% lib/file_system.c lib/message.c
|
||||||
|
set LIB_TLS=%LIB_FULL% lib/tls_schannel.c
|
||||||
|
set LIB_HTTP=%LIB_TLS% lib/http_parse.c lib/http_server.c
|
||||||
|
|
||||||
|
set SRC_SERVER=src/server.c src/log.c src/client_table.c src/chunk_store.c src/metadata.c
|
||||||
|
|
||||||
|
set QUAKEY=quakey/src/mockfs.c quakey/src/quakey.c
|
||||||
|
|
||||||
|
rem 1. Simulation
|
||||||
|
echo Building simulation...
|
||||||
|
gcc %LIB_FULL% %SRC_SERVER% src/client.c src/random_client.c src/main.c src/invariant_checker.c %QUAKEY% -o toastyfs_simulation.exe %INCLUDES% %CFLAGS% -DMAIN_SIMULATION -DFAULT_INJECTION -lws2_32
|
||||||
|
if errorlevel 1 goto :error
|
||||||
|
|
||||||
|
rem 2. Server
|
||||||
|
echo Building server...
|
||||||
|
gcc %LIB_TLS% %SRC_SERVER% src/main.c -o toastyfs_server.exe %INCLUDES% %CFLAGS% -DMAIN_SERVER -DTLS_ENABLED -DTLS_SCHANNEL -lws2_32 -lsecur32 -lcrypt32 -lncrypt
|
||||||
|
if errorlevel 1 goto :error
|
||||||
|
|
||||||
|
rem 3. Random client
|
||||||
|
echo Building random client...
|
||||||
|
gcc %LIB_CORE% src/client.c src/random_client.c src/main.c -o toastyfs_random_client.exe %INCLUDES% %CFLAGS% -DMAIN_CLIENT -lws2_32
|
||||||
|
if errorlevel 1 goto :error
|
||||||
|
|
||||||
|
rem 4. HTTP proxy
|
||||||
|
echo Building http proxy...
|
||||||
|
gcc %LIB_HTTP% src/client.c src/http_proxy.c src/main.c -o toastyfs_http_proxy.exe %INCLUDES% %CFLAGS% -DMAIN_HTTP_PROXY -DTLS_ENABLED -DTLS_SCHANNEL -lws2_32 -lsecur32 -lcrypt32 -lncrypt
|
||||||
|
if errorlevel 1 goto :error
|
||||||
|
|
||||||
|
echo Done.
|
||||||
|
goto :eof
|
||||||
|
|
||||||
|
:error
|
||||||
|
echo Build failed.
|
||||||
|
exit /b 1
|
||||||
@@ -1,3 +1,45 @@
|
|||||||
gcc src/basic.c src/file_system.c src/byte_queue.c src/message.c src/tcp.c src/server.c src/client.c src/random_client.c src/main.c src/log.c src/client_table.c src/invariant_checker.c src/chunk_store.c src/metadata.c quakey/src/mockfs.c quakey/src/quakey.c -o toastyfs_simulation -Iquakey/include -Iinclude -I. -Wall -Wextra -ggdb -O0 -DMAIN_SIMULATION -DFAULT_INJECTION
|
#!/bin/bash
|
||||||
gcc src/basic.c src/file_system.c src/byte_queue.c src/message.c src/tcp.c src/server.c src/main.c src/log.c src/client_table.c src/chunk_store.c src/metadata.c -o toastyfs -Iquakey/include -Iinclude -I. -Wall -Wextra -ggdb -O0 -DMAIN_SERVER
|
set -e
|
||||||
gcc src/basic.c src/file_system.c src/byte_queue.c src/message.c src/tcp.c src/server.c src/client.c src/random_client.c src/main.c src/log.c src/client_table.c src/chunk_store.c src/metadata.c -o toastyfs_random_client -Iquakey/include -Iinclude -I. -Wall -Wextra -ggdb -O0 -DMAIN_CLIENT
|
|
||||||
|
INCLUDES="-Iquakey/include -Iinclude -I."
|
||||||
|
CFLAGS="-Wall -Wextra -ggdb -O0"
|
||||||
|
|
||||||
|
LIB_CORE="lib/basic.c lib/byte_queue.c lib/tcp.c"
|
||||||
|
LIB_FULL="$LIB_CORE lib/file_system.c lib/message.c"
|
||||||
|
LIB_TLS="$LIB_FULL lib/tls_openssl.c"
|
||||||
|
LIB_HTTP="$LIB_TLS lib/http_parse.c lib/http_server.c"
|
||||||
|
|
||||||
|
SRC_SERVER="src/server.c src/log.c src/client_table.c src/chunk_store.c src/metadata.c"
|
||||||
|
|
||||||
|
QUAKEY="quakey/src/mockfs.c quakey/src/quakey.c"
|
||||||
|
|
||||||
|
# 1. Simulation
|
||||||
|
echo "Building simulation..."
|
||||||
|
gcc $LIB_FULL \
|
||||||
|
$SRC_SERVER src/client.c src/random_client.c src/main.c src/invariant_checker.c \
|
||||||
|
$QUAKEY \
|
||||||
|
-o toastyfs_simulation \
|
||||||
|
$INCLUDES $CFLAGS -DMAIN_SIMULATION -DFAULT_INJECTION
|
||||||
|
|
||||||
|
# 2. Server
|
||||||
|
echo "Building server..."
|
||||||
|
gcc $LIB_TLS \
|
||||||
|
$SRC_SERVER src/main.c \
|
||||||
|
-o toastyfs_server \
|
||||||
|
$INCLUDES $CFLAGS -DMAIN_SERVER -DTLS_ENABLED -DTLS_OPENSSL -lssl -lcrypto
|
||||||
|
|
||||||
|
# 3. Random client
|
||||||
|
echo "Building random client..."
|
||||||
|
gcc $LIB_CORE lib/message.c \
|
||||||
|
src/client.c src/random_client.c src/main.c \
|
||||||
|
-o toastyfs_random_client \
|
||||||
|
$INCLUDES $CFLAGS -DMAIN_CLIENT
|
||||||
|
|
||||||
|
# 4. HTTP proxy
|
||||||
|
echo "Building http proxy..."
|
||||||
|
gcc $LIB_HTTP \
|
||||||
|
src/client.c src/http_proxy.c src/main.c \
|
||||||
|
-o toastyfs_http_proxy \
|
||||||
|
$INCLUDES $CFLAGS -DMAIN_HTTP_PROXY -DTLS_ENABLED -DTLS_OPENSSL -lssl -lcrypto
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
|
|||||||
-176
@@ -1,176 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# cluster.sh - Manage a local 3-node ToastyFS cluster.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# ./cluster.sh up Build the server and start 3 nodes
|
|
||||||
# ./cluster.sh down Stop all nodes
|
|
||||||
# ./cluster.sh status Show which nodes are running
|
|
||||||
# ./cluster.sh logs [1|2|3] Tail logs (all nodes, or one)
|
|
||||||
# ./cluster.sh run <source.c> Build the library, compile <source.c>
|
|
||||||
# against it, start the cluster, run the
|
|
||||||
# binary, then tear down the cluster.
|
|
||||||
#
|
|
||||||
# The three server nodes listen on:
|
|
||||||
# 127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8083
|
|
||||||
#
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
CLUSTER_DIR="$SCRIPT_DIR/.cluster"
|
|
||||||
ADDRS=("127.0.0.1:8081" "127.0.0.1:8082" "127.0.0.1:8083")
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
sed -n '3,14s/^# \?//p' "$0"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster_up() {
|
|
||||||
echo "==> Building server..."
|
|
||||||
make -C "$SCRIPT_DIR" toastyfs
|
|
||||||
|
|
||||||
mkdir -p "$CLUSTER_DIR"
|
|
||||||
|
|
||||||
for i in 1 2 3; do
|
|
||||||
pidfile="$CLUSTER_DIR/node${i}.pid"
|
|
||||||
if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
|
|
||||||
echo " Node $i already running (pid $(cat "$pidfile"))"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
node_dir="$CLUSTER_DIR/node${i}"
|
|
||||||
mkdir -p "$node_dir"
|
|
||||||
|
|
||||||
# Build argument list: --addr for self, --peer for the others.
|
|
||||||
args=()
|
|
||||||
for j in 1 2 3; do
|
|
||||||
idx=$((j - 1))
|
|
||||||
if [ "$j" -eq "$i" ]; then
|
|
||||||
args+=(--addr "${ADDRS[$idx]}")
|
|
||||||
else
|
|
||||||
args+=(--peer "${ADDRS[$idx]}")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
(
|
|
||||||
cd "$node_dir"
|
|
||||||
# ServerState is ~40 MB (MetaStore), which exceeds the
|
|
||||||
# default 8 MB stack. Raise the limit for the server.
|
|
||||||
ulimit -s unlimited
|
|
||||||
"$SCRIPT_DIR/toastyfs" "${args[@]}" \
|
|
||||||
>> "$CLUSTER_DIR/node${i}.log" 2>&1 &
|
|
||||||
echo $! > "$pidfile"
|
|
||||||
)
|
|
||||||
echo " Node $i started (pid $(cat "$pidfile")) on ${ADDRS[$((i-1))]}"
|
|
||||||
done
|
|
||||||
echo "==> Cluster is running."
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster_down() {
|
|
||||||
if [ ! -d "$CLUSTER_DIR" ]; then
|
|
||||||
echo "No cluster state found."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
for i in 1 2 3; do
|
|
||||||
pidfile="$CLUSTER_DIR/node${i}.pid"
|
|
||||||
if [ -f "$pidfile" ]; then
|
|
||||||
pid="$(cat "$pidfile")"
|
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
|
||||||
kill "$pid" 2>/dev/null || true
|
|
||||||
echo " Node $i stopped (pid $pid)"
|
|
||||||
else
|
|
||||||
echo " Node $i was not running"
|
|
||||||
fi
|
|
||||||
rm -f "$pidfile"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clean up node data directories so fresh starts don't trigger
|
|
||||||
# the crash-recovery path (vsr_boot_marker).
|
|
||||||
rm -rf "$CLUSTER_DIR"
|
|
||||||
echo "==> Cluster stopped."
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster_status() {
|
|
||||||
if [ ! -d "$CLUSTER_DIR" ]; then
|
|
||||||
echo "No cluster state found. Run './cluster.sh up' first."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "%-8s %-8s %-20s %s\n" "NODE" "PID" "ADDRESS" "STATUS"
|
|
||||||
for i in 1 2 3; do
|
|
||||||
pidfile="$CLUSTER_DIR/node${i}.pid"
|
|
||||||
addr="${ADDRS[$((i-1))]}"
|
|
||||||
if [ -f "$pidfile" ]; then
|
|
||||||
pid="$(cat "$pidfile")"
|
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
|
||||||
printf "%-8s %-8s %-20s %s\n" "node$i" "$pid" "$addr" "running"
|
|
||||||
else
|
|
||||||
printf "%-8s %-8s %-20s %s\n" "node$i" "$pid" "$addr" "dead"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
printf "%-8s %-8s %-20s %s\n" "node$i" "-" "$addr" "not started"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster_logs() {
|
|
||||||
if [ $# -gt 0 ]; then
|
|
||||||
logfile="$CLUSTER_DIR/node${1}.log"
|
|
||||||
if [ ! -f "$logfile" ]; then
|
|
||||||
echo "No log file for node $1."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
tail -f "$logfile"
|
|
||||||
else
|
|
||||||
tail -f "$CLUSTER_DIR"/node*.log
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster_run() {
|
|
||||||
local src="$1"
|
|
||||||
local bin
|
|
||||||
bin="$(basename "${src%.c}")"
|
|
||||||
|
|
||||||
echo "==> Building libtoastyfs..."
|
|
||||||
make -C "$SCRIPT_DIR" lib
|
|
||||||
|
|
||||||
echo "==> Compiling $src..."
|
|
||||||
${CC:-gcc} -Wall -Wextra -o "$bin" "$src" \
|
|
||||||
-I"$SCRIPT_DIR/include" \
|
|
||||||
-L"$SCRIPT_DIR" \
|
|
||||||
-ltoastyfs
|
|
||||||
|
|
||||||
echo "==> Starting cluster..."
|
|
||||||
cluster_up
|
|
||||||
# Give the servers a moment to elect a leader.
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
echo "==> Running ./$bin"
|
|
||||||
echo "---"
|
|
||||||
LD_LIBRARY_PATH="$SCRIPT_DIR:${LD_LIBRARY_PATH:-}" "./$bin" || true
|
|
||||||
echo "---"
|
|
||||||
|
|
||||||
echo "==> Stopping cluster..."
|
|
||||||
cluster_down
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ $# -lt 1 ]; then
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
up) cluster_up ;;
|
|
||||||
down) cluster_down ;;
|
|
||||||
status) cluster_status ;;
|
|
||||||
logs) shift; cluster_logs "$@" ;;
|
|
||||||
run)
|
|
||||||
if [ $# -lt 2 ]; then
|
|
||||||
echo "Error: 'run' requires a source file argument." >&2
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
cluster_run "$2"
|
|
||||||
;;
|
|
||||||
*) usage ;;
|
|
||||||
esac
|
|
||||||
@@ -71,10 +71,7 @@ int deadline_to_timeout(Time deadline, Time current_time)
|
|||||||
{
|
{
|
||||||
if (deadline == INVALID_TIME)
|
if (deadline == INVALID_TIME)
|
||||||
return -1;
|
return -1;
|
||||||
if (deadline <= current_time)
|
return (deadline - current_time) / 1000000;
|
||||||
return 0;
|
|
||||||
// Round up so sub-millisecond deadlines become 1ms, not 0
|
|
||||||
return CEIL(deadline - current_time, 1000000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getargb(int argc, char **argv, char *name)
|
bool getargb(int argc, char **argv, char *name)
|
||||||
@@ -37,7 +37,6 @@ typedef uint64_t Time;
|
|||||||
|
|
||||||
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
||||||
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
|
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
|
||||||
#define CEIL(X, Y) (((X) + (Y) - 1) / (Y))
|
|
||||||
|
|
||||||
#define UNREACHABLE __builtin_trap();
|
#define UNREACHABLE __builtin_trap();
|
||||||
|
|
||||||
@@ -108,6 +108,11 @@ void byte_queue_read_ack(ByteQueue *queue, uint32_t num)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool byte_queue_reading(ByteQueue *queue)
|
||||||
|
{
|
||||||
|
return (queue->flags & BYTE_QUEUE_READ) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
ByteView byte_queue_write_buf(ByteQueue *queue)
|
ByteView byte_queue_write_buf(ByteQueue *queue)
|
||||||
{
|
{
|
||||||
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL)
|
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL)
|
||||||
@@ -39,6 +39,7 @@ int byte_queue_full(ByteQueue *queue);
|
|||||||
|
|
||||||
ByteView byte_queue_read_buf(ByteQueue *queue);
|
ByteView byte_queue_read_buf(ByteQueue *queue);
|
||||||
void byte_queue_read_ack(ByteQueue *queue, uint32_t num);
|
void byte_queue_read_ack(ByteQueue *queue, uint32_t num);
|
||||||
|
bool byte_queue_reading(ByteQueue *queue);
|
||||||
|
|
||||||
ByteView byte_queue_write_buf(ByteQueue *queue);
|
ByteView byte_queue_write_buf(ByteQueue *queue);
|
||||||
void byte_queue_write_ack(ByteQueue *queue, uint32_t num);
|
void byte_queue_write_ack(ByteQueue *queue, uint32_t num);
|
||||||
@@ -36,7 +36,7 @@ int file_open(string path, Handle *fd)
|
|||||||
memcpy(zt, path.ptr, path.len);
|
memcpy(zt, path.ptr, path.len);
|
||||||
zt[path.len] = '\0';
|
zt[path.len] = '\0';
|
||||||
|
|
||||||
int ret = open(zt, O_RDWR | O_CREAT, 0644);
|
int ret = open(zt, O_RDWR | O_CREAT | O_APPEND, 0644);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ int get_full_path(string path, char *dst)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (_fullpath(path_zt, dst, PATH_MAX) == NULL)
|
if (_fullpath(dst, path_zt, PATH_MAX) == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
+1633
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,161 @@
|
|||||||
|
#ifndef HTTP_PARSE_INCLUDED
|
||||||
|
#define HTTP_PARSE_INCLUDED
|
||||||
|
|
||||||
|
#include "basic.h"
|
||||||
|
|
||||||
|
#define CHTTP_MAX_HEADERS 32
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned int data;
|
||||||
|
} CHTTP_IPv4;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned short data[8];
|
||||||
|
} CHTTP_IPv6;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CHTTP_HOST_MODE_VOID = 0,
|
||||||
|
CHTTP_HOST_MODE_NAME,
|
||||||
|
CHTTP_HOST_MODE_IPV4,
|
||||||
|
CHTTP_HOST_MODE_IPV6,
|
||||||
|
} CHTTP_HostMode;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CHTTP_HostMode mode;
|
||||||
|
string text;
|
||||||
|
union {
|
||||||
|
string name;
|
||||||
|
CHTTP_IPv4 ipv4;
|
||||||
|
CHTTP_IPv6 ipv6;
|
||||||
|
};
|
||||||
|
} CHTTP_Host;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
string userinfo;
|
||||||
|
CHTTP_Host host;
|
||||||
|
int port;
|
||||||
|
} CHTTP_Authority;
|
||||||
|
|
||||||
|
// ZII
|
||||||
|
typedef struct {
|
||||||
|
string scheme;
|
||||||
|
CHTTP_Authority authority;
|
||||||
|
string path;
|
||||||
|
string query;
|
||||||
|
string fragment;
|
||||||
|
} CHTTP_URL;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CHTTP_METHOD_GET,
|
||||||
|
CHTTP_METHOD_HEAD,
|
||||||
|
CHTTP_METHOD_POST,
|
||||||
|
CHTTP_METHOD_PUT,
|
||||||
|
CHTTP_METHOD_DELETE,
|
||||||
|
CHTTP_METHOD_CONNECT,
|
||||||
|
CHTTP_METHOD_OPTIONS,
|
||||||
|
CHTTP_METHOD_TRACE,
|
||||||
|
CHTTP_METHOD_PATCH,
|
||||||
|
} CHTTP_Method;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
string name;
|
||||||
|
string value;
|
||||||
|
} CHTTP_Header;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool secure;
|
||||||
|
CHTTP_Method method;
|
||||||
|
CHTTP_URL url;
|
||||||
|
int minor;
|
||||||
|
int num_headers;
|
||||||
|
CHTTP_Header headers[CHTTP_MAX_HEADERS];
|
||||||
|
string body;
|
||||||
|
} CHTTP_Request;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void* context;
|
||||||
|
int minor;
|
||||||
|
int status;
|
||||||
|
string reason;
|
||||||
|
int num_headers;
|
||||||
|
CHTTP_Header headers[CHTTP_MAX_HEADERS];
|
||||||
|
string body;
|
||||||
|
} CHTTP_Response;
|
||||||
|
|
||||||
|
int chttp_parse_ipv4(char *src, int len, CHTTP_IPv4 *ipv4);
|
||||||
|
int chttp_parse_ipv6(char *src, int len, CHTTP_IPv6 *ipv6);
|
||||||
|
int chttp_parse_url(char *src, int len, CHTTP_URL *url);
|
||||||
|
int chttp_parse_request(char *src, int len, CHTTP_Request *req);
|
||||||
|
int chttp_parse_response(char *src, int len, CHTTP_Response *res);
|
||||||
|
|
||||||
|
int chttp_find_header(CHTTP_Header *headers, int num_headers, string name);
|
||||||
|
|
||||||
|
string chttp_get_cookie(CHTTP_Request *req, string name);
|
||||||
|
string chttp_get_param(string body, string str, char *mem, int cap);
|
||||||
|
int chttp_get_param_i(string body, string str);
|
||||||
|
|
||||||
|
// Checks whether the request was meant for the host with the given
|
||||||
|
// domain an port. If port is -1, the default value of 80 is assumed.
|
||||||
|
bool chttp_match_host(CHTTP_Request *req, string domain, int port);
|
||||||
|
|
||||||
|
// Date and cookie types for Set-Cookie header parsing
|
||||||
|
typedef enum {
|
||||||
|
CHTTP_WEEKDAY_MON,
|
||||||
|
CHTTP_WEEKDAY_TUE,
|
||||||
|
CHTTP_WEEKDAY_WED,
|
||||||
|
CHTTP_WEEKDAY_THU,
|
||||||
|
CHTTP_WEEKDAY_FRI,
|
||||||
|
CHTTP_WEEKDAY_SAT,
|
||||||
|
CHTTP_WEEKDAY_SUN,
|
||||||
|
} CHTTP_WeekDay;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CHTTP_MONTH_JAN,
|
||||||
|
CHTTP_MONTH_FEB,
|
||||||
|
CHTTP_MONTH_MAR,
|
||||||
|
CHTTP_MONTH_APR,
|
||||||
|
CHTTP_MONTH_MAY,
|
||||||
|
CHTTP_MONTH_JUN,
|
||||||
|
CHTTP_MONTH_JUL,
|
||||||
|
CHTTP_MONTH_AUG,
|
||||||
|
CHTTP_MONTH_SEP,
|
||||||
|
CHTTP_MONTH_OCT,
|
||||||
|
CHTTP_MONTH_NOV,
|
||||||
|
CHTTP_MONTH_DEC,
|
||||||
|
} CHTTP_Month;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CHTTP_WeekDay week_day;
|
||||||
|
int day;
|
||||||
|
CHTTP_Month month;
|
||||||
|
int year;
|
||||||
|
int hour;
|
||||||
|
int minute;
|
||||||
|
int second;
|
||||||
|
} CHTTP_Date;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
string name;
|
||||||
|
string value;
|
||||||
|
|
||||||
|
bool secure;
|
||||||
|
bool chttp_only;
|
||||||
|
|
||||||
|
bool have_date;
|
||||||
|
CHTTP_Date date;
|
||||||
|
|
||||||
|
bool have_max_age;
|
||||||
|
uint32_t max_age;
|
||||||
|
|
||||||
|
bool have_domain;
|
||||||
|
string domain;
|
||||||
|
|
||||||
|
bool have_path;
|
||||||
|
string path;
|
||||||
|
} CHTTP_SetCookie;
|
||||||
|
|
||||||
|
// Parses a Set-Cookie header value
|
||||||
|
// Returns 0 on success, -1 on error
|
||||||
|
int chttp_parse_set_cookie(string str, CHTTP_SetCookie *out);
|
||||||
|
|
||||||
|
#endif // HTTP_PARSE_INCLUDED
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
#include <quakey.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "http_server.h"
|
||||||
|
|
||||||
|
static string reason_phrase(int status)
|
||||||
|
{
|
||||||
|
switch(status) {
|
||||||
|
|
||||||
|
case 100: return S("Continue");
|
||||||
|
case 101: return S("Switching Protocols");
|
||||||
|
case 102: return S("Processing");
|
||||||
|
|
||||||
|
case 200: return S("OK");
|
||||||
|
case 201: return S("Created");
|
||||||
|
case 202: return S("Accepted");
|
||||||
|
case 203: return S("Non-Authoritative Information");
|
||||||
|
case 204: return S("No Content");
|
||||||
|
case 205: return S("Reset Content");
|
||||||
|
case 206: return S("Partial Content");
|
||||||
|
case 207: return S("Multi-Status");
|
||||||
|
case 208: return S("Already Reported");
|
||||||
|
|
||||||
|
case 300: return S("Multiple Choices");
|
||||||
|
case 301: return S("Moved Permanently");
|
||||||
|
case 302: return S("Found");
|
||||||
|
case 303: return S("See Other");
|
||||||
|
case 304: return S("Not Modified");
|
||||||
|
case 305: return S("Use Proxy");
|
||||||
|
case 306: return S("Switch Proxy");
|
||||||
|
case 307: return S("Temporary Redirect");
|
||||||
|
case 308: return S("Permanent Redirect");
|
||||||
|
|
||||||
|
case 400: return S("Bad Request");
|
||||||
|
case 401: return S("Unauthorized");
|
||||||
|
case 402: return S("Payment Required");
|
||||||
|
case 403: return S("Forbidden");
|
||||||
|
case 404: return S("Not Found");
|
||||||
|
case 405: return S("Method Not Allowed");
|
||||||
|
case 406: return S("Not Acceptable");
|
||||||
|
case 407: return S("Proxy Authentication Required");
|
||||||
|
case 408: return S("Request Timeout");
|
||||||
|
case 409: return S("Conflict");
|
||||||
|
case 410: return S("Gone");
|
||||||
|
case 411: return S("Length Required");
|
||||||
|
case 412: return S("Precondition Failed");
|
||||||
|
case 413: return S("Request Entity Too Large");
|
||||||
|
case 414: return S("Request-URI Too Long");
|
||||||
|
case 415: return S("Unsupported Media Type");
|
||||||
|
case 416: return S("Requested Range Not Satisfiable");
|
||||||
|
case 417: return S("Expectation Failed");
|
||||||
|
case 418: return S("I'm a teapot");
|
||||||
|
case 420: return S("Enhance your calm");
|
||||||
|
case 422: return S("Unprocessable Entity");
|
||||||
|
case 426: return S("Upgrade Required");
|
||||||
|
case 429: return S("Too many requests");
|
||||||
|
case 431: return S("Request Header Fields Too Large");
|
||||||
|
case 449: return S("Retry With");
|
||||||
|
case 451: return S("Unavailable For Legal Reasons");
|
||||||
|
|
||||||
|
case 500: return S("Internal Server Error");
|
||||||
|
case 501: return S("Not Implemented");
|
||||||
|
case 502: return S("Bad Gateway");
|
||||||
|
case 503: return S("Service Unavailable");
|
||||||
|
case 504: return S("Gateway Timeout");
|
||||||
|
case 505: return S("HTTP Version Not Supported");
|
||||||
|
case 509: return S("Bandwidth Limit Exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
return S("???");
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_server_init(HTTP_Server *server, int max_conns)
|
||||||
|
{
|
||||||
|
server->conns = malloc(max_conns * sizeof(HTTP_Conn));
|
||||||
|
if (server->conns == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < max_conns; i++) {
|
||||||
|
server->conns[i].state = HTTP_CONN_STATE_FREE;
|
||||||
|
server->conns[i].gen = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server->max_conns = max_conns;
|
||||||
|
server->num_conns = 0;
|
||||||
|
|
||||||
|
int ret = tcp_init(&server->tcp, max_conns);
|
||||||
|
if (ret < 0) {
|
||||||
|
free(server->conns);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_server_free(HTTP_Server *server)
|
||||||
|
{
|
||||||
|
tcp_free(&server->tcp);
|
||||||
|
free(server->conns);
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_server_listen_tcp(HTTP_Server *server, string addr, uint16_t port)
|
||||||
|
{
|
||||||
|
int ret = tcp_listen_tcp(&server->tcp, addr, port, true, 128);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_server_listen_tls(HTTP_Server *server, string addr, uint16_t port,
|
||||||
|
string cert_file, string key_file)
|
||||||
|
{
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
int ret = tls_server_init(&server->tcp.tls, cert_file, key_file);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
ret = tcp_listen_tls(&server->tcp, addr, port, true, 128);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
(void)server; (void)addr; (void)port; (void)cert_file; (void)key_file;
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_server_add_cert(HTTP_Server *server, string cert_file, string key_file)
|
||||||
|
{
|
||||||
|
int ret = tcp_add_cert(&server->tcp, cert_file, key_file);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void http_conn_init(HTTP_Conn *conn, TCP_Handle handle)
|
||||||
|
{
|
||||||
|
assert(conn->state == HTTP_CONN_STATE_FREE);
|
||||||
|
conn->state = HTTP_CONN_STATE_IDLE;
|
||||||
|
conn->ready = false;
|
||||||
|
conn->num_served = 0;
|
||||||
|
conn->request_len = 0;
|
||||||
|
conn->keep_alive = true;
|
||||||
|
conn->handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void http_conn_free(HTTP_Conn *conn)
|
||||||
|
{
|
||||||
|
assert(conn->state != HTTP_CONN_STATE_FREE);
|
||||||
|
|
||||||
|
conn->gen++;
|
||||||
|
if (conn->gen == 0)
|
||||||
|
conn->gen = 1;
|
||||||
|
|
||||||
|
tcp_close(conn->handle);
|
||||||
|
|
||||||
|
conn->state = HTTP_CONN_STATE_FREE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_server_process_events(HTTP_Server *server,
|
||||||
|
void **ptrs, struct pollfd *arr, int num)
|
||||||
|
{
|
||||||
|
tcp_process_events(&server->tcp, ptrs, arr, num);
|
||||||
|
|
||||||
|
TCP_Event event;
|
||||||
|
while (tcp_next_event(&server->tcp, &event)) {
|
||||||
|
|
||||||
|
if (event.flags & TCP_EVENT_NEW) {
|
||||||
|
// New connection. Find an HTTP_Conn struct.
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < server->max_conns && server->conns[i].state != HTTP_CONN_STATE_FREE)
|
||||||
|
i++;
|
||||||
|
if (i == server->max_conns) {
|
||||||
|
tcp_close(event.handle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
HTTP_Conn *conn = &server->conns[i];
|
||||||
|
|
||||||
|
http_conn_init(conn, event.handle);
|
||||||
|
tcp_set_user_ptr(event.handle, conn);
|
||||||
|
|
||||||
|
server->num_conns++;
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTP_Conn *conn = tcp_get_user_ptr(event.handle);
|
||||||
|
assert(conn);
|
||||||
|
|
||||||
|
bool defer_close = false;
|
||||||
|
if (event.flags & TCP_EVENT_DATA) {
|
||||||
|
|
||||||
|
// Only idle connections can buffer bytes
|
||||||
|
assert(conn->state == HTTP_CONN_STATE_IDLE);
|
||||||
|
|
||||||
|
ByteView src = tcp_read_buf(event.handle);
|
||||||
|
int ret = chttp_parse_request((char*) src.ptr, src.len, &conn->request);
|
||||||
|
if (ret < 0) {
|
||||||
|
tcp_read_ack(event.handle, 0);
|
||||||
|
defer_close = true;
|
||||||
|
} else if (ret == 0) {
|
||||||
|
tcp_read_ack(event.handle, 0);
|
||||||
|
} else {
|
||||||
|
// Request was buffered
|
||||||
|
|
||||||
|
// Decide whether the connection should be closed
|
||||||
|
// after the next response
|
||||||
|
|
||||||
|
conn->keep_alive = true;
|
||||||
|
|
||||||
|
if (conn->num_served+1 >= 100)
|
||||||
|
conn->keep_alive = false;
|
||||||
|
|
||||||
|
conn->response_offset = tcp_write_off(event.handle);
|
||||||
|
conn->request_len = ret;
|
||||||
|
conn->state = HTTP_CONN_STATE_STATUS;
|
||||||
|
conn->ready = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.flags & TCP_EVENT_HUP) {
|
||||||
|
defer_close = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defer_close) {
|
||||||
|
http_conn_free(conn);
|
||||||
|
server->num_conns--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_server_register_events(HTTP_Server *server,
|
||||||
|
void **ptrs, struct pollfd *arr, int cap)
|
||||||
|
{
|
||||||
|
return tcp_register_events(&server->tcp, ptrs, arr, cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool http_server_next_request(HTTP_Server *server,
|
||||||
|
HTTP_Request **request, HTTP_ResponseBuilder *builder)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < server->max_conns; i++) {
|
||||||
|
if (server->conns[i].state == HTTP_CONN_STATE_FREE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (server->conns[i].ready) {
|
||||||
|
*request = &server->conns[i].request;
|
||||||
|
*builder = (HTTP_ResponseBuilder) {
|
||||||
|
.server = server,
|
||||||
|
.idx = i,
|
||||||
|
.gen = server->conns[i].gen,
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HTTP_Conn*
|
||||||
|
builder_to_conn(HTTP_ResponseBuilder builder)
|
||||||
|
{
|
||||||
|
if (builder.server == NULL)
|
||||||
|
return NULL;
|
||||||
|
HTTP_Server *server = builder.server;
|
||||||
|
|
||||||
|
if (builder.idx < 0 || builder.idx >= server->max_conns)
|
||||||
|
return NULL;
|
||||||
|
HTTP_Conn *conn = &server->conns[builder.idx];
|
||||||
|
|
||||||
|
if (conn->state == HTTP_CONN_STATE_FREE || conn->gen != builder.gen)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_response_builder_status(HTTP_ResponseBuilder builder, int status)
|
||||||
|
{
|
||||||
|
HTTP_Conn *conn = builder_to_conn(builder);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state == HTTP_CONN_STATE_IDLE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state != HTTP_CONN_STATE_STATUS) {
|
||||||
|
tcp_clear_from_offset(conn->handle, conn->response_offset);
|
||||||
|
conn->state = HTTP_CONN_STATE_STATUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(status > 99);
|
||||||
|
assert(status < 1000);
|
||||||
|
char tmp[3] = {
|
||||||
|
'0' + (status % 1000) / 100,
|
||||||
|
'0' + (status % 100) / 10,
|
||||||
|
'0' + (status % 10) / 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
tcp_write(conn->handle, S("HTTP/1.1 "));
|
||||||
|
tcp_write(conn->handle, (string) { tmp, 3 });
|
||||||
|
tcp_write(conn->handle, S(" "));
|
||||||
|
tcp_write(conn->handle, reason_phrase(status));
|
||||||
|
tcp_write(conn->handle, S("\r\n"));
|
||||||
|
|
||||||
|
conn->state = HTTP_CONN_STATE_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_response_builder_header(HTTP_ResponseBuilder builder, string header)
|
||||||
|
{
|
||||||
|
HTTP_Conn *conn = builder_to_conn(builder);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state != HTTP_CONN_STATE_HEADER)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tcp_write(conn->handle, header);
|
||||||
|
tcp_write(conn->handle, S("\r\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_special_headers(HTTP_Conn *conn)
|
||||||
|
{
|
||||||
|
if (conn->keep_alive) {
|
||||||
|
tcp_write(conn->handle, S("Connection: Keep-Alive\r\n"));
|
||||||
|
} else {
|
||||||
|
tcp_write(conn->handle, S("Connection: Close\r\n"));
|
||||||
|
}
|
||||||
|
tcp_write(conn->handle, S("Content-Length: "));
|
||||||
|
conn->content_length_header_offset = tcp_write_off(conn->handle);
|
||||||
|
tcp_write(conn->handle, S(" "));
|
||||||
|
tcp_write(conn->handle, S("\r\n"));
|
||||||
|
tcp_write(conn->handle, S("\r\n"));
|
||||||
|
conn->content_offset = tcp_write_off(conn->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_response_builder_content(HTTP_ResponseBuilder builder, string content)
|
||||||
|
{
|
||||||
|
HTTP_Conn *conn = builder_to_conn(builder);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state == HTTP_CONN_STATE_STATUS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state != HTTP_CONN_STATE_CONTENT) {
|
||||||
|
append_special_headers(conn);
|
||||||
|
conn->state = HTTP_CONN_STATE_CONTENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp_write(conn->handle, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_response_builder_submit(HTTP_ResponseBuilder builder)
|
||||||
|
{
|
||||||
|
HTTP_Conn *conn = builder_to_conn(builder);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state == HTTP_CONN_STATE_STATUS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state != HTTP_CONN_STATE_CONTENT) {
|
||||||
|
append_special_headers(conn);
|
||||||
|
conn->state = HTTP_CONN_STATE_CONTENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCP_Offset current_offset = tcp_write_off(conn->handle);
|
||||||
|
int content_length = current_offset - conn->content_offset;
|
||||||
|
|
||||||
|
char buf[11];
|
||||||
|
int ret = snprintf(buf, sizeof(buf), "%d", content_length);
|
||||||
|
assert(ret > 0);
|
||||||
|
assert(ret < (int) sizeof(buf));
|
||||||
|
tcp_patch(conn->handle, conn->content_length_header_offset, buf, ret);
|
||||||
|
|
||||||
|
conn->num_served++;
|
||||||
|
|
||||||
|
tcp_read_ack(conn->handle, conn->request_len);
|
||||||
|
conn->request_len = 0;
|
||||||
|
|
||||||
|
if (conn->keep_alive) {
|
||||||
|
tcp_mark_ready(conn->handle);
|
||||||
|
conn->ready = false;
|
||||||
|
conn->state = HTTP_CONN_STATE_IDLE;
|
||||||
|
} else {
|
||||||
|
http_conn_free(conn);
|
||||||
|
builder.server->num_conns--;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef HTTP_SERVER_INCLUDED
|
||||||
|
#define HTTP_SERVER_INCLUDED
|
||||||
|
|
||||||
|
#include "tcp.h"
|
||||||
|
#include "http_parse.h"
|
||||||
|
|
||||||
|
typedef CHTTP_Request HTTP_Request;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HTTP_CONN_STATE_FREE,
|
||||||
|
HTTP_CONN_STATE_IDLE,
|
||||||
|
HTTP_CONN_STATE_STATUS,
|
||||||
|
HTTP_CONN_STATE_HEADER,
|
||||||
|
HTTP_CONN_STATE_CONTENT,
|
||||||
|
} HTTP_ConnState;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HTTP_ConnState state;
|
||||||
|
uint16_t gen;
|
||||||
|
bool ready;
|
||||||
|
int num_served;
|
||||||
|
int request_len;
|
||||||
|
bool keep_alive;
|
||||||
|
TCP_Handle handle;
|
||||||
|
HTTP_Request request;
|
||||||
|
TCP_Offset content_offset;
|
||||||
|
TCP_Offset content_length_header_offset;
|
||||||
|
TCP_Offset response_offset;
|
||||||
|
} HTTP_Conn;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
TCP tcp;
|
||||||
|
int num_conns;
|
||||||
|
int max_conns;
|
||||||
|
HTTP_Conn *conns;
|
||||||
|
} HTTP_Server;
|
||||||
|
|
||||||
|
int http_server_init(HTTP_Server *server, int max_conns);
|
||||||
|
void http_server_free(HTTP_Server *server);
|
||||||
|
|
||||||
|
int http_server_listen_tcp(HTTP_Server *server, string addr, uint16_t port);
|
||||||
|
|
||||||
|
int http_server_listen_tls(HTTP_Server *server, string addr, uint16_t port,
|
||||||
|
string cert_file, string key_file);
|
||||||
|
|
||||||
|
int http_server_add_cert(HTTP_Server *server, string cert_file, string key_file);
|
||||||
|
|
||||||
|
void http_server_process_events(HTTP_Server *server,
|
||||||
|
void **ptrs, struct pollfd *arr, int cap);
|
||||||
|
|
||||||
|
int http_server_register_events(HTTP_Server *server,
|
||||||
|
void **ptrs, struct pollfd *arr, int cap);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HTTP_Server *server;
|
||||||
|
int idx;
|
||||||
|
int gen;
|
||||||
|
} HTTP_ResponseBuilder;
|
||||||
|
|
||||||
|
bool http_server_next_request(HTTP_Server *server,
|
||||||
|
HTTP_Request **request, HTTP_ResponseBuilder *builder);
|
||||||
|
|
||||||
|
void http_response_builder_status(HTTP_ResponseBuilder builder, int status);
|
||||||
|
void http_response_builder_header(HTTP_ResponseBuilder builder, string header);
|
||||||
|
void http_response_builder_content(HTTP_ResponseBuilder builder, string content);
|
||||||
|
void http_response_builder_submit(HTTP_ResponseBuilder builder);
|
||||||
|
|
||||||
|
#endif // HTTP_SERVER_INCLUDED
|
||||||
+289
@@ -0,0 +1,289 @@
|
|||||||
|
#include <quakey.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "message.h"
|
||||||
|
|
||||||
|
#define MESSAGE_SYSTEM_VERSION 1
|
||||||
|
|
||||||
|
int message_system_init(MessageSystem *msys,
|
||||||
|
Address *addrs, int num_addrs)
|
||||||
|
{
|
||||||
|
if (num_addrs > MESSAGE_SYSTEM_NODE_LIMIT)
|
||||||
|
return -1;
|
||||||
|
for (int i = 0; i < num_addrs; i++)
|
||||||
|
msys->addrs[i] = addrs[i];
|
||||||
|
msys->num_addrs = num_addrs;
|
||||||
|
|
||||||
|
int max_conns = 2 * num_addrs + 1;
|
||||||
|
|
||||||
|
msys->conns = malloc(max_conns * sizeof(ConnMetadata));
|
||||||
|
if (msys->conns == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
msys->max_conns = max_conns;
|
||||||
|
|
||||||
|
for (int i = 0; i < max_conns; i++) {
|
||||||
|
msys->conns[i].used = false;
|
||||||
|
msys->conns[i].gen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = tcp_init(&msys->tcp, max_conns);
|
||||||
|
if (ret < 0) {
|
||||||
|
free(msys->conns);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int message_system_free(MessageSystem *msys)
|
||||||
|
{
|
||||||
|
tcp_free(&msys->tcp);
|
||||||
|
free(msys->conns);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int message_system_listen_tcp(MessageSystem *msys, Address addr)
|
||||||
|
{
|
||||||
|
int ret = tcp_listen_tcp(&msys->tcp, S(""), addr.port, true, 128);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int message_system_listen_tls(MessageSystem *msys, Address addr)
|
||||||
|
{
|
||||||
|
int ret = tcp_listen_tls(&msys->tcp, S(""), addr.port, true, 128);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void message_system_process_events(MessageSystem *msys,
|
||||||
|
void **ptrs, struct pollfd *arr, int num)
|
||||||
|
{
|
||||||
|
tcp_process_events(&msys->tcp, ptrs, arr, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
int message_system_register_events(MessageSystem *msys,
|
||||||
|
void **ptrs, struct pollfd *arr, int cap)
|
||||||
|
{
|
||||||
|
return tcp_register_events(&msys->tcp, ptrs, arr, cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *get_next_message(MessageSystem *msys)
|
||||||
|
{
|
||||||
|
TCP_Event event;
|
||||||
|
while (tcp_next_event(&msys->tcp, &event)) {
|
||||||
|
|
||||||
|
if (event.flags & TCP_EVENT_NEW) {
|
||||||
|
|
||||||
|
// Skip if already set up by ensure_conn (outbound connection)
|
||||||
|
if (tcp_get_user_ptr(event.handle) == NULL) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < msys->max_conns && msys->conns[i].used)
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (i == msys->max_conns) {
|
||||||
|
tcp_close(event.handle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnMetadata *meta = &msys->conns[i];
|
||||||
|
meta->used = true;
|
||||||
|
meta->num_senders = 0;
|
||||||
|
meta->message = NULL;
|
||||||
|
tcp_set_user_ptr(event.handle, meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnMetadata *meta = tcp_get_user_ptr(event.handle);
|
||||||
|
assert(meta);
|
||||||
|
|
||||||
|
|
||||||
|
if (event.flags & TCP_EVENT_HUP) {
|
||||||
|
meta->used = false;
|
||||||
|
tcp_close(event.handle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event.flags & TCP_EVENT_DATA))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (meta->message)
|
||||||
|
continue; // Already processing message
|
||||||
|
|
||||||
|
ByteView buf = tcp_read_buf(event.handle);
|
||||||
|
|
||||||
|
Message message;
|
||||||
|
if (buf.len < sizeof(message)) {
|
||||||
|
tcp_read_ack(event.handle, 0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
memcpy(&message, buf.ptr, sizeof(message));
|
||||||
|
// TODO: endianess?
|
||||||
|
|
||||||
|
if (message.version != MESSAGE_SYSTEM_VERSION) {
|
||||||
|
assert(0); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.length > (uint64_t)buf.len) {
|
||||||
|
tcp_read_ack(event.handle, 0);
|
||||||
|
continue; // Still buffering
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate this sender with the TCP connection
|
||||||
|
if (message.sender < msys->num_addrs) {
|
||||||
|
bool found = false;
|
||||||
|
for (int i = 0; i < meta->num_senders; i++) {
|
||||||
|
if (meta->senders[i] == message.sender) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
meta->senders[meta->num_senders++] = message.sender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meta->message = buf.ptr;
|
||||||
|
return buf.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TCP_Handle find_conn_by_message(MessageSystem *msys, void *message)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < msys->max_conns; i++) {
|
||||||
|
ConnMetadata *meta = &msys->conns[i];
|
||||||
|
if (meta->message == message)
|
||||||
|
return (TCP_Handle) { &msys->tcp, meta->gen, i };
|
||||||
|
}
|
||||||
|
return (TCP_Handle) {0};
|
||||||
|
}
|
||||||
|
|
||||||
|
static TCP_Handle find_conn_by_target(MessageSystem *msys, int target)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < msys->max_conns; i++) {
|
||||||
|
ConnMetadata *meta = &msys->conns[i];
|
||||||
|
for (int j = 0; j < meta->num_senders; j++) {
|
||||||
|
if (meta->senders[j] == target) {
|
||||||
|
return (TCP_Handle) { &msys->tcp, meta->gen, i };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (TCP_Handle) {0};
|
||||||
|
}
|
||||||
|
|
||||||
|
static TCP_Handle ensure_conn(MessageSystem *msys, int target)
|
||||||
|
{
|
||||||
|
TCP_Handle handle = find_conn_by_target(msys, target);
|
||||||
|
if (handle.tcp)
|
||||||
|
return handle;
|
||||||
|
|
||||||
|
if (target < 0 || target >= msys->num_addrs)
|
||||||
|
return (TCP_Handle) {0};
|
||||||
|
|
||||||
|
int ret = tcp_connect(&msys->tcp, false, &msys->addrs[target], 1);
|
||||||
|
if (ret < 0)
|
||||||
|
return (TCP_Handle) {0};
|
||||||
|
|
||||||
|
// Find the newly created connection slot and pre-associate with target
|
||||||
|
for (int i = 0; i < msys->max_conns; i++) {
|
||||||
|
TCP_Conn *conn = &msys->tcp.conns[i];
|
||||||
|
if (conn->state == TCP_CONN_STATE_FREE)
|
||||||
|
continue;
|
||||||
|
if (conn->user_ptr != NULL)
|
||||||
|
continue;
|
||||||
|
if (conn->state != TCP_CONN_STATE_CONNECTING
|
||||||
|
&& conn->state != TCP_CONN_STATE_ESTABLISHED)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ConnMetadata *meta = &msys->conns[i];
|
||||||
|
meta->used = true;
|
||||||
|
meta->gen = conn->gen;
|
||||||
|
meta->num_senders = 1;
|
||||||
|
meta->senders[0] = target;
|
||||||
|
meta->message = NULL;
|
||||||
|
|
||||||
|
TCP_Handle h = { &msys->tcp, conn->gen, i };
|
||||||
|
tcp_set_user_ptr(h, meta);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TCP_Handle) {0};
|
||||||
|
}
|
||||||
|
|
||||||
|
void consume_message(MessageSystem *msys, void *ptr)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
while (i < msys->max_conns && msys->conns[i].message != ptr)
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (i == msys->max_conns)
|
||||||
|
return; // Not found
|
||||||
|
|
||||||
|
Message message;
|
||||||
|
memcpy(&message, ptr, sizeof(message));
|
||||||
|
|
||||||
|
TCP_Handle handle = { &msys->tcp, msys->conns[i].gen, i };
|
||||||
|
tcp_read_ack(handle, message.length);
|
||||||
|
tcp_mark_ready(handle);
|
||||||
|
msys->conns[i].message = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_message(MessageSystem *msys, int target, Message *message)
|
||||||
|
{
|
||||||
|
TCP_Handle handle = ensure_conn(msys, target);
|
||||||
|
if (!handle.tcp) return;
|
||||||
|
tcp_write(handle, (string) { (char*) message, message->length });
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_message_ex(MessageSystem *msys, int target,
|
||||||
|
Message *header, void *extra, int extra_len)
|
||||||
|
{
|
||||||
|
TCP_Handle handle = ensure_conn(msys, target);
|
||||||
|
if (!handle.tcp) return;
|
||||||
|
int header_size = header->length - extra_len;
|
||||||
|
tcp_write(handle, (string) { (char*) header, header_size });
|
||||||
|
tcp_write(handle, (string) { (char*) extra, extra_len });
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply_to_message(MessageSystem *msys, void *incoming_message,
|
||||||
|
Message *outgoing_message)
|
||||||
|
{
|
||||||
|
TCP_Handle handle = find_conn_by_message(msys, incoming_message);
|
||||||
|
if (!handle.tcp) return;
|
||||||
|
tcp_write(handle, (string) { (char*) outgoing_message, outgoing_message->length });
|
||||||
|
}
|
||||||
|
|
||||||
|
void reply_to_message_ex(MessageSystem *msys, void *incoming_message,
|
||||||
|
Message *outgoing_message, void *extra, int extra_len)
|
||||||
|
{
|
||||||
|
TCP_Handle handle = find_conn_by_message(msys, incoming_message);
|
||||||
|
if (!handle.tcp) return;
|
||||||
|
int header_size = outgoing_message->length - extra_len;
|
||||||
|
tcp_write(handle, (string) { (char*) outgoing_message, header_size });
|
||||||
|
tcp_write(handle, (string) { (char*) extra, extra_len });
|
||||||
|
}
|
||||||
|
|
||||||
|
void broadcast_message(MessageSystem *msys, int self_idx, Message *message)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < msys->num_addrs; i++) {
|
||||||
|
if (i != self_idx)
|
||||||
|
send_message(msys, i, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void broadcast_message_ex(MessageSystem *msys, int self_idx,
|
||||||
|
Message *header, void *extra, int extra_len)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < msys->num_addrs; i++) {
|
||||||
|
if (i != self_idx)
|
||||||
|
send_message_ex(msys, i, header, extra, extra_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef MESSAGE_INCLUDED
|
||||||
|
#define MESSAGE_INCLUDED
|
||||||
|
|
||||||
|
#include "tcp.h"
|
||||||
|
|
||||||
|
#define MESSAGE_SYSTEM_NODE_LIMIT 8
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool used;
|
||||||
|
uint16_t gen;
|
||||||
|
int senders[MESSAGE_SYSTEM_NODE_LIMIT];
|
||||||
|
int num_senders;
|
||||||
|
void* message;
|
||||||
|
} ConnMetadata;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
TCP tcp;
|
||||||
|
|
||||||
|
Address addrs[MESSAGE_SYSTEM_NODE_LIMIT];
|
||||||
|
int num_addrs;
|
||||||
|
|
||||||
|
int max_conns;
|
||||||
|
ConnMetadata *conns;
|
||||||
|
} MessageSystem;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t version;
|
||||||
|
uint16_t type;
|
||||||
|
uint16_t sender;
|
||||||
|
uint64_t length;
|
||||||
|
} Message;
|
||||||
|
|
||||||
|
int message_system_init(MessageSystem *msys,
|
||||||
|
Address *addrs, int num_addrs);
|
||||||
|
|
||||||
|
int message_system_free(MessageSystem *msys);
|
||||||
|
|
||||||
|
int message_system_listen_tcp(MessageSystem *msys, Address addr);
|
||||||
|
int message_system_listen_tls(MessageSystem *msys, Address addr);
|
||||||
|
|
||||||
|
void message_system_process_events(MessageSystem *msys,
|
||||||
|
void **ptrs, struct pollfd *arr, int num);
|
||||||
|
|
||||||
|
int message_system_register_events(MessageSystem *msys,
|
||||||
|
void **ptrs, struct pollfd *arr, int cap);
|
||||||
|
|
||||||
|
void *get_next_message(MessageSystem *msys);
|
||||||
|
|
||||||
|
void consume_message(MessageSystem *msys, void *ptr);
|
||||||
|
|
||||||
|
void send_message(MessageSystem *msys, int target, Message *message);
|
||||||
|
|
||||||
|
void send_message_ex(MessageSystem *msys, int target,
|
||||||
|
Message *header, void *extra, int extra_len);
|
||||||
|
|
||||||
|
void reply_to_message(MessageSystem *msys, void *incoming_message,
|
||||||
|
Message *outgoing_message);
|
||||||
|
|
||||||
|
void reply_to_message_ex(MessageSystem *msys, void *incoming_message,
|
||||||
|
Message *outgoing_message, void *extra, int extra_len);
|
||||||
|
|
||||||
|
void broadcast_message(MessageSystem *msys, int self_idx, Message *message);
|
||||||
|
|
||||||
|
void broadcast_message_ex(MessageSystem *msys, int self_idx,
|
||||||
|
Message *header, void *extra, int extra_len);
|
||||||
|
|
||||||
|
#endif // MESSAGE_INCLUDED
|
||||||
@@ -0,0 +1,917 @@
|
|||||||
|
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||||
|
#define QUAKEY_ENABLE_MOCKS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <quakey.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "tcp.h"
|
||||||
|
|
||||||
|
#define MIN_RECV 4096
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
typedef SOCKET NATIVE_SOCKET;
|
||||||
|
#else
|
||||||
|
typedef int NATIVE_SOCKET;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void tcp_conn_free(TCP_Conn *conn);
|
||||||
|
static bool tcp_conn_free_maybe(TCP_Conn *conn);
|
||||||
|
|
||||||
|
static int set_socket_blocking(NATIVE_SOCKET sock, bool value)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
u_long mode = !value;
|
||||||
|
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
int flags = fcntl(sock, F_GETFL, 0);
|
||||||
|
if (flags < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
flags &= ~O_NONBLOCK;
|
||||||
|
else
|
||||||
|
flags |= O_NONBLOCK;
|
||||||
|
|
||||||
|
if (fcntl(sock, F_SETFL, flags) < 0)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_listen_socket(string addr, uint16_t port,
|
||||||
|
bool reuse_addr, int backlog)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
// TODO: Only do this if socket creation fails due to
|
||||||
|
// winsock not being initialized, then try again
|
||||||
|
// with the socket
|
||||||
|
WSADATA wsa;
|
||||||
|
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (fd == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (set_socket_blocking(fd, false) < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reuse_addr) {
|
||||||
|
int one = 1;
|
||||||
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct in_addr addr_buf;
|
||||||
|
if (addr.len == 0)
|
||||||
|
addr_buf.s_addr = htonl(INADDR_ANY);
|
||||||
|
else {
|
||||||
|
|
||||||
|
char copy[100];
|
||||||
|
if (addr.len >= (int) sizeof(copy)) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(copy, addr.ptr, addr.len);
|
||||||
|
copy[addr.len] = '\0';
|
||||||
|
|
||||||
|
if (inet_pton(AF_INET, copy, &addr_buf) < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in bind_buf;
|
||||||
|
bind_buf.sin_family = AF_INET;
|
||||||
|
bind_buf.sin_addr = addr_buf;
|
||||||
|
bind_buf.sin_port = htons(port);
|
||||||
|
if (bind(fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(fd, backlog) < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tcp_init(TCP *tcp, int max_conns)
|
||||||
|
{
|
||||||
|
TCP_Conn *conns = malloc(max_conns * sizeof(TCP_Conn));
|
||||||
|
if (conns == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
tcp->tls_listen_fd = -1;
|
||||||
|
tcp->tcp_listen_fd = -1;
|
||||||
|
tcp->num_conns = 0;
|
||||||
|
tcp->max_conns = max_conns;
|
||||||
|
tcp->conns = conns;
|
||||||
|
|
||||||
|
for (int i = 0; i < tcp->max_conns; i++) {
|
||||||
|
tcp->conns[i].state = TCP_CONN_STATE_FREE;
|
||||||
|
tcp->conns[i].gen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_free(TCP *tcp)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < tcp->max_conns; i++) {
|
||||||
|
if (tcp->conns[i].state != TCP_CONN_STATE_FREE)
|
||||||
|
tcp_conn_free(&tcp->conns[i]);
|
||||||
|
}
|
||||||
|
free(tcp->conns);
|
||||||
|
|
||||||
|
if (tcp->tcp_listen_fd != -1)
|
||||||
|
close(tcp->tcp_listen_fd);
|
||||||
|
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (tcp->tls_listen_fd != -1) {
|
||||||
|
close(tcp->tls_listen_fd);
|
||||||
|
tls_server_free(&tcp->tls);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int tcp_listen_tcp(TCP *tcp, string addr, uint16_t port, bool reuse_addr, int backlog)
|
||||||
|
{
|
||||||
|
if (tcp->tcp_listen_fd != -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int fd = create_listen_socket(addr, port, reuse_addr, backlog);
|
||||||
|
if (fd == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
tcp->tcp_listen_fd = fd;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tcp_listen_tls(TCP *tcp, string addr, uint16_t port, bool reuse_addr, int backlog)
|
||||||
|
{
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (tcp->tls_listen_fd != -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int fd = create_listen_socket(addr, port, reuse_addr, backlog);
|
||||||
|
if (fd == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
tcp->tls_listen_fd = fd;
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
(void)tcp; (void)addr; (void)port; (void)reuse_addr; (void)backlog;
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int tcp_add_cert(TCP *tcp, string cert_file, string key_file)
|
||||||
|
{
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
return tls_server_add_cert(&tcp->tls, S(""), cert_file, key_file);
|
||||||
|
#else
|
||||||
|
(void)tcp; (void)cert_file; (void)key_file;
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_conn_init(TCP *tcp, TCP_Conn *conn, bool secure, TCP_ConnState state, int fd)
|
||||||
|
{
|
||||||
|
conn->state = state;
|
||||||
|
conn->flags = 0;
|
||||||
|
conn->events = 0;
|
||||||
|
conn->handled = false;
|
||||||
|
conn->closing = false;
|
||||||
|
conn->fd = fd;
|
||||||
|
conn->num_addrs = 0;
|
||||||
|
conn->addr_idx = 0;
|
||||||
|
conn->user_ptr = NULL;
|
||||||
|
|
||||||
|
byte_queue_init(&conn->input, 1<<20);
|
||||||
|
byte_queue_init(&conn->output, 1<<20);
|
||||||
|
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (secure) {
|
||||||
|
conn->flags |= TCP_CONN_FLAG_SECURE;
|
||||||
|
tls_conn_init(&conn->tls, &tcp->tls);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)tcp;
|
||||||
|
(void)secure;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_conn_free(TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
if (conn->fd >= 0) {
|
||||||
|
close(conn->fd);
|
||||||
|
conn->fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte_queue_free(&conn->input);
|
||||||
|
byte_queue_free(&conn->output);
|
||||||
|
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (conn->flags & TCP_CONN_FLAG_SECURE) {
|
||||||
|
tls_conn_free(&conn->tls);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
conn->state = TCP_CONN_STATE_FREE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_conn_set_addrs(TCP_Conn *conn,
|
||||||
|
Address *addrs, int num_addrs)
|
||||||
|
{
|
||||||
|
assert(num_addrs <= TCP_CONNECT_ADDR_LIMIT);
|
||||||
|
for (int i = 0; i < num_addrs; i++)
|
||||||
|
conn->addrs[i] = addrs[i];
|
||||||
|
conn->num_addrs = num_addrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ByteView tcp_conn_write_buf(TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (conn->flags & TCP_CONN_FLAG_SECURE) {
|
||||||
|
int cap;
|
||||||
|
char *ptr = tls_conn_net_write_buf(&conn->tls, &cap);
|
||||||
|
if (ptr == NULL)
|
||||||
|
return (ByteView) {0};
|
||||||
|
return (ByteView) { (uint8_t*) ptr, cap };
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
byte_queue_write_setmincap(&conn->input, MIN_RECV);
|
||||||
|
return byte_queue_write_buf(&conn->input);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tcp_conn_write_ack(TCP_Conn *conn, int num)
|
||||||
|
{
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (conn->flags & TCP_CONN_FLAG_SECURE) {
|
||||||
|
int ret = 0;
|
||||||
|
tls_conn_net_write_ack(&conn->tls, num);
|
||||||
|
for (bool done = false; !done; ) {
|
||||||
|
byte_queue_write_setmincap(&conn->input, MIN_RECV);
|
||||||
|
ByteView buf = byte_queue_write_buf(&conn->input);
|
||||||
|
int n = tls_conn_app_read(&conn->tls, (char*) buf.ptr, buf.len);
|
||||||
|
if (n <= 0) {
|
||||||
|
if (n < 0) {
|
||||||
|
ret = -1;
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
byte_queue_write_ack(&conn->input, n);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
byte_queue_write_ack(&conn->input, num);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
// Encrypt plaintext from the output queue through SSL_write into the BIO.
|
||||||
|
static void tcp_conn_tls_encrypt_output(TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
while (!byte_queue_empty(&conn->output)) {
|
||||||
|
ByteView src = byte_queue_read_buf(&conn->output);
|
||||||
|
if (!src.ptr || src.len == 0) {
|
||||||
|
byte_queue_read_ack(&conn->output, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int n = tls_conn_app_write(&conn->tls, (char*) src.ptr, src.len);
|
||||||
|
if (n <= 0) {
|
||||||
|
byte_queue_read_ack(&conn->output, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
byte_queue_read_ack(&conn->output, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static ByteView tcp_conn_read_buf(TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (conn->flags & TCP_CONN_FLAG_SECURE) {
|
||||||
|
tcp_conn_tls_encrypt_output(conn);
|
||||||
|
int n;
|
||||||
|
char *ptr = tls_conn_net_read_buf(&conn->tls, &n);
|
||||||
|
if (ptr == NULL)
|
||||||
|
return (ByteView) {0};
|
||||||
|
return (ByteView) { (uint8_t*) ptr, n };
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return byte_queue_read_buf(&conn->output);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_conn_read_ack(TCP_Conn *conn, int num)
|
||||||
|
{
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (conn->flags & TCP_CONN_FLAG_SECURE) {
|
||||||
|
tls_conn_net_read_ack(&conn->tls, num);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
byte_queue_read_ack(&conn->output, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tcp_conn_needs_flushing(TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (conn->flags & TCP_CONN_FLAG_SECURE) {
|
||||||
|
return !byte_queue_empty(&conn->output)
|
||||||
|
|| tls_conn_needs_flushing(&conn->tls);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return !byte_queue_empty(&conn->output);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tcp_conn_is_buffering(TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
if (conn->closing)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (conn->state == TCP_CONN_STATE_HANDSHAKE ||
|
||||||
|
conn->state == TCP_CONN_STATE_ACCEPTING)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return !byte_queue_reading(&conn->input);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tcp_conn_free_maybe(TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
if (!conn->handled && conn->fd < 0) {
|
||||||
|
tcp_conn_free(conn);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_conn_invalidate_handles(TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
conn->gen++;
|
||||||
|
if (conn->gen == 0)
|
||||||
|
conn->gen = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int build_sockaddr(Address *addr, struct sockaddr_in *out)
|
||||||
|
{
|
||||||
|
memset(out, 0, sizeof(*out));
|
||||||
|
if (!addr->is_ipv4)
|
||||||
|
return -1; // Only IPv4 supported for now
|
||||||
|
out->sin_family = AF_INET;
|
||||||
|
out->sin_port = htons(addr->port);
|
||||||
|
memcpy(&out->sin_addr, &addr->ipv4, sizeof(addr->ipv4));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tcp_connect(TCP *tcp, bool secure, Address *addrs, int num_addrs)
|
||||||
|
{
|
||||||
|
if (tcp->num_conns == tcp->max_conns)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < tcp->max_conns && tcp->conns[i].state != TCP_CONN_STATE_FREE)
|
||||||
|
i++;
|
||||||
|
assert(i < tcp->max_conns);
|
||||||
|
|
||||||
|
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (set_socket_blocking(fd, false) < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in sa;
|
||||||
|
if (build_sockaddr(&addrs[0], &sa) < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCP_ConnState state;
|
||||||
|
int ret = connect(fd, (struct sockaddr*) &sa, sizeof(sa));
|
||||||
|
if (ret == 0) {
|
||||||
|
if (secure) {
|
||||||
|
state = TCP_CONN_STATE_HANDSHAKE;
|
||||||
|
} else {
|
||||||
|
state = TCP_CONN_STATE_ESTABLISHED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert(ret < 0);
|
||||||
|
if (errno == EINPROGRESS) {
|
||||||
|
state = TCP_CONN_STATE_CONNECTING;
|
||||||
|
} else {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp_conn_init(tcp, &tcp->conns[i], secure, state, fd);
|
||||||
|
tcp_conn_set_addrs(&tcp->conns[i], addrs, num_addrs);
|
||||||
|
tcp->num_conns++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int restart_connect(TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
close(conn->fd);
|
||||||
|
conn->fd = -1;
|
||||||
|
|
||||||
|
conn->addr_idx++;
|
||||||
|
if (conn->addr_idx == conn->num_addrs) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (set_socket_blocking(fd, false) < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in sa;
|
||||||
|
if (build_sockaddr(&conn->addrs[conn->addr_idx], &sa) < 0) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCP_ConnState state;
|
||||||
|
int ret = connect(fd, (struct sockaddr*) &sa, sizeof(sa));
|
||||||
|
if (ret == 0) {
|
||||||
|
if (conn->flags & TCP_CONN_FLAG_SECURE) {
|
||||||
|
state = TCP_CONN_STATE_HANDSHAKE;
|
||||||
|
} else {
|
||||||
|
state = TCP_CONN_STATE_ESTABLISHED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert(ret < 0);
|
||||||
|
if (errno == EINPROGRESS) {
|
||||||
|
state = TCP_CONN_STATE_CONNECTING;
|
||||||
|
} else {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->fd = fd;
|
||||||
|
conn->state = state;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_process_events(TCP *tcp, void **ptrs, struct pollfd *arr, int num)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < num; i++) {
|
||||||
|
if (arr[i].fd == tcp->tcp_listen_fd ||
|
||||||
|
arr[i].fd == tcp->tls_listen_fd) {
|
||||||
|
|
||||||
|
assert(ptrs[i] == NULL);
|
||||||
|
|
||||||
|
if (arr[i].revents & POLLIN) {
|
||||||
|
|
||||||
|
if (tcp->num_conns == tcp->max_conns)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool is_tls = false;
|
||||||
|
if (arr[i].fd == tcp->tls_listen_fd)
|
||||||
|
is_tls = true;
|
||||||
|
|
||||||
|
int new_fd = accept(arr[i].fd, NULL, NULL);
|
||||||
|
if (new_fd == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (set_socket_blocking(new_fd, false) < 0) {
|
||||||
|
close(new_fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a free connection slot
|
||||||
|
int slot = 0;
|
||||||
|
while (slot < tcp->max_conns && tcp->conns[slot].state != TCP_CONN_STATE_FREE)
|
||||||
|
slot++;
|
||||||
|
if (slot == tcp->max_conns) {
|
||||||
|
close(new_fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCP_ConnState state;
|
||||||
|
if (is_tls) {
|
||||||
|
state = TCP_CONN_STATE_ACCEPTING;
|
||||||
|
} else {
|
||||||
|
state = TCP_CONN_STATE_ESTABLISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCP_Conn *conn = &tcp->conns[slot];
|
||||||
|
tcp_conn_init(tcp, conn, is_tls, state, new_fd);
|
||||||
|
if (!is_tls)
|
||||||
|
conn->events |= TCP_EVENT_NEW;
|
||||||
|
tcp->num_conns++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
TCP_Conn *conn = ptrs[i];
|
||||||
|
|
||||||
|
bool defer_ready = false;
|
||||||
|
bool defer_close = false;
|
||||||
|
bool defer_connect = false;
|
||||||
|
switch (conn->state) {
|
||||||
|
case TCP_CONN_STATE_CONNECTING:
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
socklen_t len = sizeof(err);
|
||||||
|
if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) {
|
||||||
|
defer_connect = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
defer_connect = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn->flags & TCP_CONN_FLAG_SECURE) {
|
||||||
|
conn->state = TCP_CONN_STATE_HANDSHAKE;
|
||||||
|
} else {
|
||||||
|
conn->state = TCP_CONN_STATE_ESTABLISHED;
|
||||||
|
conn->events |= TCP_EVENT_NEW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TCP_CONN_STATE_HANDSHAKE:
|
||||||
|
case TCP_CONN_STATE_ACCEPTING:
|
||||||
|
{
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
if (arr[i].revents & POLLIN) {
|
||||||
|
int cap;
|
||||||
|
char *buf = tls_conn_net_write_buf(&conn->tls, &cap);
|
||||||
|
if (buf) {
|
||||||
|
int n = recv(conn->fd, buf, cap, 0);
|
||||||
|
if (n == 0) { defer_close = true; break; }
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN)
|
||||||
|
{ defer_close = true; break; }
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
|
tls_conn_net_write_ack(&conn->tls, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = tls_conn_handshake(&conn->tls);
|
||||||
|
if (ret == -1) {
|
||||||
|
defer_close = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arr[i].revents & POLLOUT) {
|
||||||
|
int num;
|
||||||
|
char *buf = tls_conn_net_read_buf(&conn->tls, &num);
|
||||||
|
if (buf) {
|
||||||
|
int n = send(conn->fd, buf, num, 0);
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN)
|
||||||
|
{ defer_close = true; break; }
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
|
tls_conn_net_read_ack(&conn->tls, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 1) {
|
||||||
|
conn->state = TCP_CONN_STATE_ESTABLISHED;
|
||||||
|
conn->events |= TCP_EVENT_NEW;
|
||||||
|
|
||||||
|
// Decrypt any application data already in the BIO
|
||||||
|
for (;;) {
|
||||||
|
byte_queue_write_setmincap(&conn->input, MIN_RECV);
|
||||||
|
ByteView buf = byte_queue_write_buf(&conn->input);
|
||||||
|
if (!buf.ptr) break;
|
||||||
|
int n = tls_conn_app_read(&conn->tls, (char*) buf.ptr, buf.len);
|
||||||
|
if (n <= 0) { byte_queue_write_ack(&conn->input, 0); break; }
|
||||||
|
byte_queue_write_ack(&conn->input, n);
|
||||||
|
conn->events |= TCP_EVENT_DATA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
defer_close = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TCP_CONN_STATE_ESTABLISHED:
|
||||||
|
{
|
||||||
|
if (arr[i].revents & POLLIN) {
|
||||||
|
ByteView buf = tcp_conn_write_buf(conn);
|
||||||
|
int n = recv(conn->fd, (char*) buf.ptr, buf.len, 0);
|
||||||
|
if (n == 0) {
|
||||||
|
defer_close = true;
|
||||||
|
} else {
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN)
|
||||||
|
defer_close = true;
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int ret = tcp_conn_write_ack(conn, n);
|
||||||
|
if (ret < 0)
|
||||||
|
defer_close = true;
|
||||||
|
defer_ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arr[i].revents & POLLOUT) {
|
||||||
|
ByteView buf = tcp_conn_read_buf(conn);
|
||||||
|
int n = send(conn->fd, (char*) buf.ptr, buf.len, 0);
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN)
|
||||||
|
defer_close = true;
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
|
tcp_conn_read_ack(conn, n);
|
||||||
|
if (conn->closing && !tcp_conn_needs_flushing(conn))
|
||||||
|
defer_close = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TCP_CONN_STATE_SHUTDOWN:
|
||||||
|
{
|
||||||
|
// TLS shutdown — just close for now
|
||||||
|
defer_close = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defer_connect) {
|
||||||
|
int ret = restart_connect(conn);
|
||||||
|
if (ret < 0) {
|
||||||
|
defer_close = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defer_ready) {
|
||||||
|
conn->events |= TCP_EVENT_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defer_close) {
|
||||||
|
|
||||||
|
close(conn->fd);
|
||||||
|
conn->fd = -1;
|
||||||
|
conn->events |= TCP_EVENT_HUP;
|
||||||
|
|
||||||
|
if (tcp_conn_free_maybe(conn)) {
|
||||||
|
tcp->num_conns--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int tcp_register_events(TCP *tcp, void **ptrs, struct pollfd *arr, int cap)
|
||||||
|
{
|
||||||
|
if (cap < tcp->num_conns+2)
|
||||||
|
return -1;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (tcp->tcp_listen_fd > -1) {
|
||||||
|
if (tcp->num_conns < tcp->max_conns) {
|
||||||
|
arr[ret].fd = tcp->tcp_listen_fd;
|
||||||
|
arr[ret].events = POLLIN;
|
||||||
|
arr[ret].revents = 0;
|
||||||
|
ptrs[ret] = NULL;
|
||||||
|
ret++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tcp->tls_listen_fd > -1) {
|
||||||
|
if (tcp->num_conns < tcp->max_conns) {
|
||||||
|
arr[ret].fd = tcp->tls_listen_fd;
|
||||||
|
arr[ret].events = POLLIN;
|
||||||
|
arr[ret].revents = 0;
|
||||||
|
ptrs[ret] = NULL;
|
||||||
|
ret++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0, j=0; j < tcp->num_conns; i++) {
|
||||||
|
|
||||||
|
TCP_Conn *conn = &tcp->conns[i];
|
||||||
|
if (conn->state == TCP_CONN_STATE_FREE)
|
||||||
|
continue;
|
||||||
|
j++;
|
||||||
|
|
||||||
|
int events = 0;
|
||||||
|
|
||||||
|
if (conn->state == TCP_CONN_STATE_CONNECTING)
|
||||||
|
events |= POLLOUT;
|
||||||
|
|
||||||
|
if (tcp_conn_is_buffering(conn))
|
||||||
|
events |= POLLIN;
|
||||||
|
|
||||||
|
if (tcp_conn_needs_flushing(conn))
|
||||||
|
events |= POLLOUT;
|
||||||
|
|
||||||
|
if (events) {
|
||||||
|
arr[ret].fd = conn->fd;
|
||||||
|
arr[ret].events = events;
|
||||||
|
arr[ret].revents = 0;
|
||||||
|
ptrs[ret] = conn;
|
||||||
|
ret++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TCP_Handle conn_to_handle(TCP *tcp, TCP_Conn *conn)
|
||||||
|
{
|
||||||
|
TCP_Handle handle = {
|
||||||
|
.tcp=tcp,
|
||||||
|
.gen=conn->gen,
|
||||||
|
.idx=conn - tcp->conns,
|
||||||
|
};
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TCP_Conn *handle_to_conn(TCP_Handle handle)
|
||||||
|
{
|
||||||
|
if (handle.tcp == NULL)
|
||||||
|
return NULL;
|
||||||
|
TCP *tcp = handle.tcp;
|
||||||
|
|
||||||
|
if (handle.idx < 0 || handle.idx >= tcp->max_conns)
|
||||||
|
return NULL;
|
||||||
|
TCP_Conn *conn = &tcp->conns[handle.idx];
|
||||||
|
|
||||||
|
if (conn->state == TCP_CONN_STATE_FREE || conn->gen != handle.gen)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool conn_to_event(TCP *tcp, TCP_Conn *conn, TCP_Event *event)
|
||||||
|
{
|
||||||
|
if (!conn->events)
|
||||||
|
return false;
|
||||||
|
*event = (TCP_Event) {
|
||||||
|
.flags = conn->events,
|
||||||
|
.handle = conn_to_handle(tcp, conn),
|
||||||
|
};
|
||||||
|
conn->events = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tcp_next_event(TCP *tcp, TCP_Event *event)
|
||||||
|
{
|
||||||
|
for (int i = 0, j = 0; j < tcp->num_conns; i++) {
|
||||||
|
|
||||||
|
TCP_Conn *conn = &tcp->conns[i];
|
||||||
|
if (conn->state == TCP_CONN_STATE_FREE)
|
||||||
|
continue;
|
||||||
|
j++;
|
||||||
|
|
||||||
|
if (conn->flags & TCP_CONN_FLAG_CLOSED)
|
||||||
|
continue; // User isn't interested in this connection anymore
|
||||||
|
|
||||||
|
if (conn_to_event(tcp, conn, event))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteView tcp_read_buf(TCP_Handle handle)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return (ByteView) {0};
|
||||||
|
|
||||||
|
return byte_queue_read_buf(&conn->input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_read_ack(TCP_Handle handle, int num)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte_queue_read_ack(&conn->input, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteView tcp_write_buf(TCP_Handle handle)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return (ByteView) {0};
|
||||||
|
|
||||||
|
return byte_queue_write_buf(&conn->output);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_write_ack(TCP_Handle handle, int num)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte_queue_write_ack(&conn->output, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
TCP_Offset tcp_write_off(TCP_Handle handle)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return byte_queue_offset(&conn->output);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_write(TCP_Handle handle, string str)
|
||||||
|
{
|
||||||
|
while (str.len > 0) {
|
||||||
|
byte_queue_write_setmincap(&handle_to_conn(handle)->output, str.len);
|
||||||
|
ByteView buf = tcp_write_buf(handle);
|
||||||
|
int num = MIN(buf.len, str.len);
|
||||||
|
memcpy(buf.ptr, str.ptr, num);
|
||||||
|
tcp_write_ack(handle, num);
|
||||||
|
str.ptr += num;
|
||||||
|
str.len -= num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_patch(TCP_Handle handle, TCP_Offset offset, void *src, int len)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte_queue_patch(&conn->output, offset, src, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_clear_from_offset(TCP_Handle handle, TCP_Offset offset)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte_queue_remove_from_offset(&conn->output, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_close(TCP_Handle handle)
|
||||||
|
{
|
||||||
|
TCP *tcp = handle.tcp;
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
conn->flags |= TCP_CONN_FLAG_CLOSED;
|
||||||
|
conn->handled = false;
|
||||||
|
tcp_conn_invalidate_handles(conn);
|
||||||
|
if (tcp_conn_free_maybe(conn)) {
|
||||||
|
tcp->num_conns--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_set_user_ptr(TCP_Handle handle, void *ptr)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
conn->user_ptr = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *tcp_get_user_ptr(TCP_Handle handle)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return conn->user_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_mark_ready(TCP_Handle handle)
|
||||||
|
{
|
||||||
|
TCP_Conn *conn = handle_to_conn(handle);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
conn->events |= TCP_EVENT_DATA;
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#ifndef TCP_INCLUDED
|
||||||
|
#define TCP_INCLUDED
|
||||||
|
|
||||||
|
#include "basic.h"
|
||||||
|
#include "byte_queue.h"
|
||||||
|
#include "tls.h"
|
||||||
|
|
||||||
|
typedef struct TCP TCP;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
TCP* tcp;
|
||||||
|
uint16_t gen;
|
||||||
|
int idx;
|
||||||
|
} TCP_Handle;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TCP_CONN_STATE_FREE,
|
||||||
|
TCP_CONN_STATE_HANDSHAKE,
|
||||||
|
TCP_CONN_STATE_ESTABLISHED,
|
||||||
|
TCP_CONN_STATE_CONNECTING,
|
||||||
|
TCP_CONN_STATE_ACCEPTING,
|
||||||
|
TCP_CONN_STATE_SHUTDOWN,
|
||||||
|
} TCP_ConnState;
|
||||||
|
|
||||||
|
#define TCP_CONNECT_ADDR_LIMIT 8
|
||||||
|
|
||||||
|
enum {
|
||||||
|
TCP_EVENT_NEW = 1<<0,
|
||||||
|
TCP_EVENT_HUP = 1<<1,
|
||||||
|
TCP_EVENT_DATA = 1<<2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
TCP_CONN_FLAG_CLOSED = 1<<0,
|
||||||
|
TCP_CONN_FLAG_SECURE = 1<<1,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
TCP_ConnState state;
|
||||||
|
int flags;
|
||||||
|
int events;
|
||||||
|
uint16_t gen;
|
||||||
|
int fd;
|
||||||
|
bool handled;
|
||||||
|
bool closing;
|
||||||
|
void *user_ptr;
|
||||||
|
ByteQueue input;
|
||||||
|
ByteQueue output;
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
TLS_Conn tls;
|
||||||
|
#endif
|
||||||
|
Address addrs[TCP_CONNECT_ADDR_LIMIT];
|
||||||
|
int num_addrs;
|
||||||
|
int addr_idx;
|
||||||
|
} TCP_Conn;
|
||||||
|
|
||||||
|
struct TCP {
|
||||||
|
int tls_listen_fd;
|
||||||
|
int tcp_listen_fd;
|
||||||
|
int num_conns;
|
||||||
|
int max_conns;
|
||||||
|
TCP_Conn *conns;
|
||||||
|
#ifdef TLS_ENABLED
|
||||||
|
TLS_Server tls;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int flags;
|
||||||
|
TCP_Handle handle;
|
||||||
|
} TCP_Event;
|
||||||
|
|
||||||
|
typedef ByteQueueOffset TCP_Offset;
|
||||||
|
|
||||||
|
struct pollfd;
|
||||||
|
|
||||||
|
int tcp_init(TCP *tcp, int max_conns);
|
||||||
|
void tcp_free(TCP *tcp);
|
||||||
|
int tcp_listen_tcp(TCP *tcp, string addr, uint16_t port, bool reuse_addr, int backlog);
|
||||||
|
int tcp_listen_tls(TCP *tcp, string addr, uint16_t port, bool reuse_addr, int backlog);
|
||||||
|
int tcp_add_cert(TCP *tcp, string cert_file, string key_file);
|
||||||
|
int tcp_connect(TCP *tcp, bool secure, Address *addrs, int num_addrs);
|
||||||
|
void tcp_process_events(TCP *tcp, void **ptrs, struct pollfd *arr, int num);
|
||||||
|
int tcp_register_events(TCP *tcp, void **ptrs, struct pollfd *arr, int cap);
|
||||||
|
bool tcp_next_event(TCP *tcp, TCP_Event *event);
|
||||||
|
ByteView tcp_read_buf(TCP_Handle handle);
|
||||||
|
void tcp_read_ack(TCP_Handle handle, int num);
|
||||||
|
ByteView tcp_write_buf(TCP_Handle handle);
|
||||||
|
void tcp_write_ack(TCP_Handle handle, int num);
|
||||||
|
TCP_Offset tcp_write_off(TCP_Handle handle);
|
||||||
|
void tcp_write(TCP_Handle handle, string str);
|
||||||
|
void tcp_patch(TCP_Handle handle, TCP_Offset offset, void *src, int len);
|
||||||
|
void tcp_clear_from_offset(TCP_Handle handle, TCP_Offset offset);
|
||||||
|
void tcp_close(TCP_Handle handle);
|
||||||
|
void tcp_set_user_ptr(TCP_Handle handle, void *ptr);
|
||||||
|
void *tcp_get_user_ptr(TCP_Handle handle);
|
||||||
|
void tcp_mark_ready(TCP_Handle handle);
|
||||||
|
|
||||||
|
#endif // TCP_INCLUDED
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#ifdef TLS_ENABLED
|
||||||
|
#ifndef TLS_INCLUDED
|
||||||
|
#define TLS_INCLUDED
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "basic.h"
|
||||||
|
|
||||||
|
#define TLS_CERT_LIMIT 8
|
||||||
|
|
||||||
|
#ifdef TLS_SCHANNEL
|
||||||
|
|
||||||
|
#define SECURITY_WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <security.h>
|
||||||
|
#include <schannel.h>
|
||||||
|
|
||||||
|
#include "byte_queue.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char domain[128];
|
||||||
|
CredHandle cred;
|
||||||
|
} TLS_Cert;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CredHandle cred;
|
||||||
|
PCCERT_CONTEXT cert_ctx;
|
||||||
|
int num_certs;
|
||||||
|
TLS_Cert certs[TLS_CERT_LIMIT];
|
||||||
|
} TLS_Server;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CtxtHandle ctx;
|
||||||
|
bool ctx_valid;
|
||||||
|
bool handshake;
|
||||||
|
CredHandle *cred;
|
||||||
|
ByteQueue in_buf;
|
||||||
|
ByteQueue out_buf;
|
||||||
|
SecPkgContext_StreamSizes sizes;
|
||||||
|
char *pending;
|
||||||
|
int pending_off;
|
||||||
|
int pending_len;
|
||||||
|
} TLS_Conn;
|
||||||
|
|
||||||
|
#else // OpenSSL
|
||||||
|
|
||||||
|
typedef struct ssl_ctx_st SSL_CTX;
|
||||||
|
typedef struct ssl_st SSL;
|
||||||
|
typedef struct bio_st BIO;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char domain[128];
|
||||||
|
SSL_CTX *ctx;
|
||||||
|
} TLS_Cert;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SSL_CTX *ctx;
|
||||||
|
int num_certs;
|
||||||
|
TLS_Cert certs[TLS_CERT_LIMIT];
|
||||||
|
} TLS_Server;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SSL *ssl;
|
||||||
|
BIO *network_bio;
|
||||||
|
bool handshake;
|
||||||
|
} TLS_Conn;
|
||||||
|
|
||||||
|
#endif // TLS_SCHANNEL
|
||||||
|
|
||||||
|
void tls_global_init(void);
|
||||||
|
void tls_global_free(void);
|
||||||
|
|
||||||
|
int tls_server_init(TLS_Server *server, string cert_file, string key_file);
|
||||||
|
void tls_server_free(TLS_Server *server);
|
||||||
|
int tls_server_add_cert(TLS_Server *server, string domain, string cert_file, string key_file);
|
||||||
|
|
||||||
|
int tls_conn_init(TLS_Conn *conn, TLS_Server *server);
|
||||||
|
void tls_conn_free(TLS_Conn *conn);
|
||||||
|
|
||||||
|
int tls_conn_handshake(TLS_Conn *conn);
|
||||||
|
|
||||||
|
char *tls_conn_net_write_buf(TLS_Conn *conn, int *cap);
|
||||||
|
void tls_conn_net_write_ack(TLS_Conn *conn, int num);
|
||||||
|
|
||||||
|
char *tls_conn_net_read_buf(TLS_Conn *conn, int *num);
|
||||||
|
void tls_conn_net_read_ack(TLS_Conn *conn, int num);
|
||||||
|
|
||||||
|
int tls_conn_app_write(TLS_Conn *conn, char *dst, int num);
|
||||||
|
int tls_conn_app_read(TLS_Conn *conn, char *src, int cap);
|
||||||
|
int tls_conn_needs_flushing(TLS_Conn *conn);
|
||||||
|
|
||||||
|
#endif // TLS_INCLUDED
|
||||||
|
#endif // TLS_ENABLED
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
#ifdef TLS_ENABLED
|
||||||
|
#ifdef TLS_OPENSSL
|
||||||
|
|
||||||
|
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||||
|
#define QUAKEY_ENABLE_MOCKS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <quakey.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
// Avoid name collision between basic.h's SHA256 typedef
|
||||||
|
// and OpenSSL's SHA256 function
|
||||||
|
#define SHA256 openssl_SHA256
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#undef SHA256
|
||||||
|
|
||||||
|
#include "tls.h"
|
||||||
|
|
||||||
|
void tls_global_init(void)
|
||||||
|
{
|
||||||
|
SSL_library_init();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
OpenSSL_add_all_algorithms();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tls_global_free(void)
|
||||||
|
{
|
||||||
|
EVP_cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int servername_callback(SSL *ssl, int *ad, void *arg)
|
||||||
|
{
|
||||||
|
TLS_Server *server = arg;
|
||||||
|
|
||||||
|
// The 'ad' parameter is used to set the alert description when returning
|
||||||
|
// SSL_TLSEXT_ERR_ALERT_FATAL. Since we only return OK or NOACK, it's unused.
|
||||||
|
(void) ad;
|
||||||
|
|
||||||
|
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||||
|
if (servername == NULL)
|
||||||
|
return SSL_TLSEXT_ERR_NOACK;
|
||||||
|
|
||||||
|
for (int i = 0; i < server->num_certs; i++) {
|
||||||
|
TLS_Cert *cert = &server->certs[i];
|
||||||
|
if (!strcmp(cert->domain, servername)) {
|
||||||
|
SSL_set_SSL_CTX(ssl, cert->ctx);
|
||||||
|
return SSL_TLSEXT_ERR_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SSL_TLSEXT_ERR_NOACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_server_init(TLS_Server *server, string cert_file, string key_file)
|
||||||
|
{
|
||||||
|
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
|
||||||
|
if (ctx == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
||||||
|
|
||||||
|
char cert_buf[1024];
|
||||||
|
if (cert_file.len >= (int) sizeof(cert_buf)) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(cert_buf, cert_file.ptr, cert_file.len);
|
||||||
|
cert_buf[cert_file.len] = '\0';
|
||||||
|
|
||||||
|
char key_buf[1024];
|
||||||
|
if (key_file.len >= (int) sizeof(key_buf)) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(key_buf, key_file.ptr, key_file.len);
|
||||||
|
key_buf[key_file.len] = '\0';
|
||||||
|
|
||||||
|
// Load certificate and private key
|
||||||
|
if (SSL_CTX_use_certificate_chain_file(ctx, cert_buf) != 1) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_use_PrivateKey_file(ctx, key_buf, SSL_FILETYPE_PEM) != 1) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_check_private_key(ctx) != 1) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_tlsext_servername_callback(ctx, servername_callback);
|
||||||
|
SSL_CTX_set_tlsext_servername_arg(ctx, server);
|
||||||
|
|
||||||
|
server->ctx = ctx;
|
||||||
|
server->num_certs = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tls_server_free(TLS_Server *server)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < server->num_certs; i++)
|
||||||
|
SSL_CTX_free(server->certs[i].ctx);
|
||||||
|
SSL_CTX_free(server->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Can the domain be inferred from the cert?
|
||||||
|
int tls_server_add_cert(TLS_Server *server, string domain, string cert_file, string key_file)
|
||||||
|
{
|
||||||
|
if (server->num_certs == TLS_CERT_LIMIT)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
|
||||||
|
if (!ctx)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
||||||
|
|
||||||
|
char cert_buf[1024];
|
||||||
|
if (cert_file.len >= (int) sizeof(cert_buf)) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(cert_buf, cert_file.ptr, cert_file.len);
|
||||||
|
cert_buf[cert_file.len] = '\0';
|
||||||
|
|
||||||
|
char key_buf[1024];
|
||||||
|
if (key_file.len >= (int) sizeof(key_buf)) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(key_buf, key_file.ptr, key_file.len);
|
||||||
|
key_buf[key_file.len] = '\0';
|
||||||
|
|
||||||
|
if (SSL_CTX_use_certificate_chain_file(ctx, cert_buf) != 1) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_use_PrivateKey_file(ctx, key_buf, SSL_FILETYPE_PEM) != 1) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_check_private_key(ctx) != 1) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TLS_Cert *cert = &server->certs[server->num_certs];
|
||||||
|
if (domain.len >= (int) sizeof(cert->domain)) {
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(cert->domain, domain.ptr, domain.len);
|
||||||
|
cert->domain[domain.len] = '\0';
|
||||||
|
cert->ctx = ctx;
|
||||||
|
server->num_certs++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_conn_init(TLS_Conn *conn, TLS_Server *server)
|
||||||
|
{
|
||||||
|
SSL *ssl = SSL_new(server->ctx);
|
||||||
|
if (ssl == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Create a BIO pair:
|
||||||
|
// internal_bio — attached to the SSL object (OpenSSL uses it internally)
|
||||||
|
// network_bio — you read/write encrypted data from/to this
|
||||||
|
BIO *internal_bio = NULL;
|
||||||
|
BIO *network_bio = NULL;
|
||||||
|
if (!BIO_new_bio_pair(&internal_bio, 0, &network_bio, 0)) {
|
||||||
|
SSL_free(ssl);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind the internal side to the SSL object
|
||||||
|
SSL_set_bio(ssl, internal_bio, internal_bio);
|
||||||
|
|
||||||
|
// We're the server side
|
||||||
|
SSL_set_accept_state(ssl);
|
||||||
|
|
||||||
|
conn->ssl = ssl;
|
||||||
|
conn->network_bio = network_bio;
|
||||||
|
conn->handshake = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tls_conn_free(TLS_Conn *conn)
|
||||||
|
{
|
||||||
|
SSL_free(conn->ssl);
|
||||||
|
BIO_free(conn->network_bio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write ciphertext from the connection object to the network
|
||||||
|
char *tls_conn_net_write_buf(TLS_Conn *conn, int *cap)
|
||||||
|
{
|
||||||
|
char *buf;
|
||||||
|
int ret = BIO_nwrite0(conn->network_bio, &buf);
|
||||||
|
if (ret <= 0)
|
||||||
|
return NULL;
|
||||||
|
*cap = ret;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the write from the connection object
|
||||||
|
void tls_conn_net_write_ack(TLS_Conn *conn, int num)
|
||||||
|
{
|
||||||
|
char *dummy;
|
||||||
|
BIO_nwrite(conn->network_bio, &dummy, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read ciphertext from the network into the connection object
|
||||||
|
char *tls_conn_net_read_buf(TLS_Conn *conn, int *num)
|
||||||
|
{
|
||||||
|
char *buf;
|
||||||
|
int ret = BIO_nread0(conn->network_bio, &buf);
|
||||||
|
if (ret <= 0)
|
||||||
|
return NULL;
|
||||||
|
*num = ret;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the read from the network
|
||||||
|
void tls_conn_net_read_ack(TLS_Conn *conn, int num)
|
||||||
|
{
|
||||||
|
char *dummy;
|
||||||
|
BIO_nread(conn->network_bio, &dummy, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write plaintext from the application to the connection object
|
||||||
|
int tls_conn_app_write(TLS_Conn *conn, char *dst, int num)
|
||||||
|
{
|
||||||
|
assert(!conn->handshake);
|
||||||
|
|
||||||
|
int n = SSL_write(conn->ssl, dst, num);
|
||||||
|
if (n > 0)
|
||||||
|
return n;
|
||||||
|
|
||||||
|
int err = SSL_get_error(conn->ssl, n);
|
||||||
|
if (err == SSL_ERROR_WANT_READ ||
|
||||||
|
err == SSL_ERROR_WANT_WRITE)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read plaintext from the connection object into the application
|
||||||
|
int tls_conn_app_read(TLS_Conn *conn, char *src, int cap)
|
||||||
|
{
|
||||||
|
assert(!conn->handshake);
|
||||||
|
|
||||||
|
int n = SSL_read(conn->ssl, src, cap);
|
||||||
|
if (n > 0)
|
||||||
|
return n;
|
||||||
|
|
||||||
|
int err = SSL_get_error(conn->ssl, n);
|
||||||
|
if (err == SSL_ERROR_WANT_READ ||
|
||||||
|
err == SSL_ERROR_WANT_WRITE)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_conn_handshake(TLS_Conn *conn)
|
||||||
|
{
|
||||||
|
assert(conn->handshake);
|
||||||
|
|
||||||
|
int n = SSL_do_handshake(conn->ssl);
|
||||||
|
if (n == 1) {
|
||||||
|
conn->handshake = false;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = SSL_get_error(conn->ssl, n);
|
||||||
|
if (err == SSL_ERROR_WANT_READ ||
|
||||||
|
err == SSL_ERROR_WANT_WRITE)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_conn_needs_flushing(TLS_Conn *conn)
|
||||||
|
{
|
||||||
|
return BIO_ctrl_pending(conn->network_bio) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TLS_OPENSSL
|
||||||
|
#endif // TLS_ENABLED
|
||||||
@@ -0,0 +1,695 @@
|
|||||||
|
#ifdef TLS_ENABLED
|
||||||
|
#ifdef TLS_SCHANNEL
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define SECURITY_WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wincrypt.h>
|
||||||
|
#include <security.h>
|
||||||
|
#include <schannel.h>
|
||||||
|
|
||||||
|
#include "tls.h"
|
||||||
|
|
||||||
|
#pragma comment(lib, "secur32.lib")
|
||||||
|
#pragma comment(lib, "crypt32.lib")
|
||||||
|
#pragma comment(lib, "ncrypt.lib")
|
||||||
|
|
||||||
|
#define TLS_BUF_LIMIT (32 * 1024)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Certificate loading
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Read entire file into malloc'd buffer. Caller must free.
|
||||||
|
static char *read_file(const char *path, int *out_len)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return NULL;
|
||||||
|
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long len = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (len <= 0 || len > 1024 * 1024) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *buf = malloc((size_t) len + 1);
|
||||||
|
if (!buf) {
|
||||||
|
fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t nread = fread(buf, 1, (size_t) len, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
buf[nread] = '\0';
|
||||||
|
*out_len = (int) nread;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode PEM base64 content to DER binary. Returns malloc'd buffer.
|
||||||
|
static BYTE *pem_to_der(const char *pem, int pem_len, DWORD *out_len)
|
||||||
|
{
|
||||||
|
DWORD len = 0;
|
||||||
|
if (!CryptStringToBinaryA(pem, pem_len, CRYPT_STRING_BASE64HEADER,
|
||||||
|
NULL, &len, NULL, NULL))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
BYTE *der = malloc(len);
|
||||||
|
if (!der) return NULL;
|
||||||
|
|
||||||
|
if (!CryptStringToBinaryA(pem, pem_len, CRYPT_STRING_BASE64HEADER,
|
||||||
|
der, &len, NULL, NULL)) {
|
||||||
|
free(der);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_len = len;
|
||||||
|
return der;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import an in-memory PFX blob, acquire SChannel credential.
|
||||||
|
static int load_pfx_blob(const BYTE *pfx_data, DWORD pfx_len,
|
||||||
|
CredHandle *cred_out, PCCERT_CONTEXT *cert_ctx_out)
|
||||||
|
{
|
||||||
|
CRYPT_DATA_BLOB blob;
|
||||||
|
blob.pbData = (BYTE *) pfx_data;
|
||||||
|
blob.cbData = pfx_len;
|
||||||
|
|
||||||
|
HCERTSTORE store = PFXImportCertStore(&blob, L"", CRYPT_EXPORTABLE);
|
||||||
|
if (!store) return -1;
|
||||||
|
|
||||||
|
// Find first cert with a private key
|
||||||
|
PCCERT_CONTEXT cert_ctx = NULL;
|
||||||
|
while ((cert_ctx = CertEnumCertificatesInStore(store, cert_ctx)) != NULL) {
|
||||||
|
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hkey = 0;
|
||||||
|
DWORD key_spec = 0;
|
||||||
|
BOOL caller_free = FALSE;
|
||||||
|
if (CryptAcquireCertificatePrivateKey(cert_ctx,
|
||||||
|
CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG | CRYPT_ACQUIRE_SILENT_FLAG,
|
||||||
|
NULL, &hkey, &key_spec, &caller_free)) {
|
||||||
|
if (caller_free && hkey) {
|
||||||
|
if (key_spec == CERT_NCRYPT_KEY_SPEC)
|
||||||
|
NCryptFreeObject(hkey);
|
||||||
|
else
|
||||||
|
CryptReleaseContext(hkey, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cert_ctx) {
|
||||||
|
CertCloseStore(store, 0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PCCERT_CONTEXT dup_ctx = CertDuplicateCertificateContext(cert_ctx);
|
||||||
|
|
||||||
|
SCHANNEL_CRED sc_cred = {0};
|
||||||
|
sc_cred.dwVersion = SCHANNEL_CRED_VERSION;
|
||||||
|
sc_cred.cCreds = 1;
|
||||||
|
sc_cred.paCred = &dup_ctx;
|
||||||
|
sc_cred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER;
|
||||||
|
|
||||||
|
TimeStamp expiry;
|
||||||
|
SECURITY_STATUS ss = AcquireCredentialsHandleA(
|
||||||
|
NULL, UNISP_NAME_A, SECPKG_CRED_INBOUND,
|
||||||
|
NULL, &sc_cred, NULL, NULL,
|
||||||
|
cred_out, &expiry);
|
||||||
|
|
||||||
|
if (ss != SEC_E_OK) {
|
||||||
|
CertFreeCertificateContext(dup_ctx);
|
||||||
|
CertCloseStore(store, 0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*cert_ctx_out = dup_ctx;
|
||||||
|
// Keep store open — SChannel needs access to the cert+key
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load PEM cert+key using pure CryptoAPI (no external tools).
|
||||||
|
// Decodes PEM, imports key into a temporary CAPI keyset, exports as
|
||||||
|
// in-memory PFX, then imports via PFXImportCertStore.
|
||||||
|
static int load_pem_credential(string cert_file, string key_file,
|
||||||
|
CredHandle *cred_out, PCCERT_CONTEXT *cert_ctx_out)
|
||||||
|
{
|
||||||
|
int result = -1;
|
||||||
|
char cert_z[1024], key_z[1024], container[64];
|
||||||
|
char *cert_pem = NULL, *key_pem = NULL;
|
||||||
|
BYTE *cert_der = NULL, *key_der = NULL;
|
||||||
|
BYTE *rsa_blob = NULL, *pkcs8_buf = NULL;
|
||||||
|
PCCERT_CONTEXT cert_ctx = NULL;
|
||||||
|
HCRYPTPROV hprov = 0;
|
||||||
|
HCRYPTKEY hkey = 0;
|
||||||
|
HCERTSTORE mem_store = NULL;
|
||||||
|
CRYPT_DATA_BLOB pfx = {0};
|
||||||
|
int cert_pem_len, key_pem_len;
|
||||||
|
DWORD cert_der_len, key_der_len, rsa_blob_len, pkcs8_len;
|
||||||
|
|
||||||
|
snprintf(container, sizeof(container), "tls_tmp_%lu",
|
||||||
|
(unsigned long) GetCurrentProcessId());
|
||||||
|
|
||||||
|
// Null-terminate file paths
|
||||||
|
if (cert_file.len >= (int) sizeof(cert_z)) goto done;
|
||||||
|
memcpy(cert_z, cert_file.ptr, cert_file.len);
|
||||||
|
cert_z[cert_file.len] = '\0';
|
||||||
|
|
||||||
|
if (key_file.len >= (int) sizeof(key_z)) goto done;
|
||||||
|
memcpy(key_z, key_file.ptr, key_file.len);
|
||||||
|
key_z[key_file.len] = '\0';
|
||||||
|
|
||||||
|
// --- Certificate: PEM -> DER -> CERT_CONTEXT ---
|
||||||
|
cert_pem = read_file(cert_z, &cert_pem_len);
|
||||||
|
if (!cert_pem) goto done;
|
||||||
|
|
||||||
|
cert_der = pem_to_der(cert_pem, cert_pem_len, &cert_der_len);
|
||||||
|
if (!cert_der) goto done;
|
||||||
|
|
||||||
|
cert_ctx = CertCreateCertificateContext(
|
||||||
|
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, cert_der, cert_der_len);
|
||||||
|
if (!cert_ctx) goto done;
|
||||||
|
|
||||||
|
// --- Private key: PEM -> DER -> CAPI RSA blob ---
|
||||||
|
key_pem = read_file(key_z, &key_pem_len);
|
||||||
|
if (!key_pem) goto done;
|
||||||
|
|
||||||
|
key_der = pem_to_der(key_pem, key_pem_len, &key_der_len);
|
||||||
|
if (!key_der) goto done;
|
||||||
|
|
||||||
|
if (strstr(key_pem, "-----BEGIN PRIVATE KEY-----")) {
|
||||||
|
// PKCS#8: unwrap to get inner RSA key
|
||||||
|
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO,
|
||||||
|
key_der, key_der_len, CRYPT_DECODE_ALLOC_FLAG,
|
||||||
|
NULL, &pkcs8_buf, &pkcs8_len))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
CRYPT_PRIVATE_KEY_INFO *pki = (CRYPT_PRIVATE_KEY_INFO *) pkcs8_buf;
|
||||||
|
|
||||||
|
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY,
|
||||||
|
pki->PrivateKey.pbData, pki->PrivateKey.cbData,
|
||||||
|
CRYPT_DECODE_ALLOC_FLAG, NULL, &rsa_blob, &rsa_blob_len))
|
||||||
|
goto done;
|
||||||
|
} else {
|
||||||
|
// Traditional RSA (BEGIN RSA PRIVATE KEY)
|
||||||
|
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY,
|
||||||
|
key_der, key_der_len, CRYPT_DECODE_ALLOC_FLAG,
|
||||||
|
NULL, &rsa_blob, &rsa_blob_len))
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Import key into temporary CAPI keyset ---
|
||||||
|
CryptAcquireContextA(&hprov, container, MS_ENH_RSA_AES_PROV_A,
|
||||||
|
PROV_RSA_AES, CRYPT_DELETEKEYSET);
|
||||||
|
hprov = 0;
|
||||||
|
|
||||||
|
if (!CryptAcquireContextA(&hprov, container, MS_ENH_RSA_AES_PROV_A,
|
||||||
|
PROV_RSA_AES, CRYPT_NEWKEYSET))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
if (!CryptImportKey(hprov, rsa_blob, rsa_blob_len, 0, CRYPT_EXPORTABLE, &hkey))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
// --- Bind key to cert, export PFX in memory ---
|
||||||
|
{
|
||||||
|
wchar_t containerW[64];
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, container, -1, containerW, 64);
|
||||||
|
|
||||||
|
CRYPT_KEY_PROV_INFO prov_info = {0};
|
||||||
|
prov_info.pwszContainerName = containerW;
|
||||||
|
prov_info.pwszProvName = (LPWSTR) L"Microsoft Enhanced RSA and AES Cryptographic Provider";
|
||||||
|
prov_info.dwProvType = PROV_RSA_AES;
|
||||||
|
prov_info.dwKeySpec = AT_KEYEXCHANGE;
|
||||||
|
|
||||||
|
if (!CertSetCertificateContextProperty(cert_ctx,
|
||||||
|
CERT_KEY_PROV_INFO_PROP_ID, 0, &prov_info))
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, NULL);
|
||||||
|
if (!mem_store) goto done;
|
||||||
|
|
||||||
|
if (!CertAddCertificateContextToStore(mem_store, cert_ctx,
|
||||||
|
CERT_STORE_ADD_ALWAYS, NULL))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
// Two-pass PFX export: get size, then export
|
||||||
|
pfx.cbData = 0;
|
||||||
|
pfx.pbData = NULL;
|
||||||
|
if (!PFXExportCertStoreEx(mem_store, &pfx, L"", NULL, EXPORT_PRIVATE_KEYS))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
pfx.pbData = malloc(pfx.cbData);
|
||||||
|
if (!pfx.pbData) goto done;
|
||||||
|
|
||||||
|
if (!PFXExportCertStoreEx(mem_store, &pfx, L"", NULL, EXPORT_PRIVATE_KEYS))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
// --- Import PFX blob and acquire SChannel credential ---
|
||||||
|
result = load_pfx_blob(pfx.pbData, pfx.cbData, cred_out, cert_ctx_out);
|
||||||
|
|
||||||
|
done:
|
||||||
|
free(pfx.pbData);
|
||||||
|
if (mem_store) CertCloseStore(mem_store, 0);
|
||||||
|
if (hkey) CryptDestroyKey(hkey);
|
||||||
|
if (hprov) CryptReleaseContext(hprov, 0);
|
||||||
|
{ HCRYPTPROV tmp = 0;
|
||||||
|
CryptAcquireContextA(&tmp, container, MS_ENH_RSA_AES_PROV_A,
|
||||||
|
PROV_RSA_AES, CRYPT_DELETEKEYSET); }
|
||||||
|
if (pkcs8_buf) LocalFree(pkcs8_buf);
|
||||||
|
if (rsa_blob) LocalFree(rsa_blob);
|
||||||
|
free(key_der);
|
||||||
|
free(key_pem);
|
||||||
|
free(cert_der);
|
||||||
|
free(cert_pem);
|
||||||
|
if (cert_ctx) CertFreeCertificateContext(cert_ctx);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Global init/free (no-ops for SChannel)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void tls_global_init(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void tls_global_free(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Server init/free
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int tls_server_init(TLS_Server *server, string cert_file, string key_file)
|
||||||
|
{
|
||||||
|
memset(server, 0, sizeof(*server));
|
||||||
|
server->num_certs = 0;
|
||||||
|
|
||||||
|
int ret = load_pem_credential(cert_file, key_file, &server->cred, &server->cert_ctx);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tls_server_free(TLS_Server *server)
|
||||||
|
{
|
||||||
|
FreeCredentialsHandle(&server->cred);
|
||||||
|
if (server->cert_ctx) {
|
||||||
|
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hkey = 0;
|
||||||
|
DWORD key_spec = 0;
|
||||||
|
BOOL caller_free = FALSE;
|
||||||
|
if (CryptAcquireCertificatePrivateKey(server->cert_ctx,
|
||||||
|
CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, NULL, &hkey, &key_spec, &caller_free)) {
|
||||||
|
if (caller_free && hkey)
|
||||||
|
NCryptFreeObject(hkey);
|
||||||
|
}
|
||||||
|
CertFreeCertificateContext(server->cert_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < server->num_certs; i++)
|
||||||
|
FreeCredentialsHandle(&server->certs[i].cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_server_add_cert(TLS_Server *server, string domain, string cert_file, string key_file)
|
||||||
|
{
|
||||||
|
if (server->num_certs >= TLS_CERT_LIMIT)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
TLS_Cert *cert = &server->certs[server->num_certs];
|
||||||
|
if (domain.len >= (int) sizeof(cert->domain))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
PCCERT_CONTEXT cert_ctx = NULL;
|
||||||
|
int ret = load_pem_credential(cert_file, key_file, &cert->cred, &cert_ctx);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (cert_ctx) {
|
||||||
|
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hkey = 0;
|
||||||
|
DWORD key_spec = 0;
|
||||||
|
BOOL caller_free = FALSE;
|
||||||
|
if (CryptAcquireCertificatePrivateKey(cert_ctx,
|
||||||
|
CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, NULL, &hkey, &key_spec, &caller_free)) {
|
||||||
|
if (caller_free && hkey)
|
||||||
|
NCryptFreeObject(hkey);
|
||||||
|
}
|
||||||
|
CertFreeCertificateContext(cert_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(cert->domain, domain.ptr, domain.len);
|
||||||
|
cert->domain[domain.len] = '\0';
|
||||||
|
server->num_certs++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Connection init/free
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int tls_conn_init(TLS_Conn *conn, TLS_Server *server)
|
||||||
|
{
|
||||||
|
memset(conn, 0, sizeof(*conn));
|
||||||
|
conn->ctx_valid = false;
|
||||||
|
conn->handshake = true;
|
||||||
|
conn->cred = &server->cred;
|
||||||
|
byte_queue_init(&conn->in_buf, TLS_BUF_LIMIT);
|
||||||
|
byte_queue_init(&conn->out_buf, TLS_BUF_LIMIT);
|
||||||
|
conn->pending = NULL;
|
||||||
|
conn->pending_off = 0;
|
||||||
|
conn->pending_len = 0;
|
||||||
|
SecInvalidateHandle(&conn->ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tls_conn_free(TLS_Conn *conn)
|
||||||
|
{
|
||||||
|
if (conn->ctx_valid)
|
||||||
|
DeleteSecurityContext(&conn->ctx);
|
||||||
|
byte_queue_free(&conn->in_buf);
|
||||||
|
byte_queue_free(&conn->out_buf);
|
||||||
|
free(conn->pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Handshake
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int tls_conn_handshake(TLS_Conn *conn)
|
||||||
|
{
|
||||||
|
assert(conn->handshake);
|
||||||
|
|
||||||
|
// Read available ciphertext from in_buf
|
||||||
|
ByteView in = byte_queue_read_buf(&conn->in_buf);
|
||||||
|
if (!in.ptr || in.len == 0) {
|
||||||
|
byte_queue_read_ack(&conn->in_buf, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int in_avail = (int) in.len;
|
||||||
|
|
||||||
|
// Input buffers
|
||||||
|
SecBuffer in_bufs[2];
|
||||||
|
in_bufs[0].BufferType = SECBUFFER_TOKEN;
|
||||||
|
in_bufs[0].pvBuffer = in.ptr;
|
||||||
|
in_bufs[0].cbBuffer = (unsigned long) in_avail;
|
||||||
|
in_bufs[1].BufferType = SECBUFFER_EMPTY;
|
||||||
|
in_bufs[1].pvBuffer = NULL;
|
||||||
|
in_bufs[1].cbBuffer = 0;
|
||||||
|
|
||||||
|
SecBufferDesc in_desc;
|
||||||
|
in_desc.ulVersion = SECBUFFER_VERSION;
|
||||||
|
in_desc.cBuffers = 2;
|
||||||
|
in_desc.pBuffers = in_bufs;
|
||||||
|
|
||||||
|
// Output buffers
|
||||||
|
SecBuffer out_bufs[1];
|
||||||
|
out_bufs[0].BufferType = SECBUFFER_TOKEN;
|
||||||
|
out_bufs[0].pvBuffer = NULL;
|
||||||
|
out_bufs[0].cbBuffer = 0;
|
||||||
|
|
||||||
|
SecBufferDesc out_desc;
|
||||||
|
out_desc.ulVersion = SECBUFFER_VERSION;
|
||||||
|
out_desc.cBuffers = 1;
|
||||||
|
out_desc.pBuffers = out_bufs;
|
||||||
|
|
||||||
|
DWORD flags = ASC_REQ_STREAM
|
||||||
|
| ASC_REQ_SEQUENCE_DETECT
|
||||||
|
| ASC_REQ_REPLAY_DETECT
|
||||||
|
| ASC_REQ_CONFIDENTIALITY
|
||||||
|
| ASC_REQ_ALLOCATE_MEMORY;
|
||||||
|
|
||||||
|
DWORD out_flags = 0;
|
||||||
|
TimeStamp expiry;
|
||||||
|
|
||||||
|
SECURITY_STATUS ss = AcceptSecurityContext(
|
||||||
|
conn->cred,
|
||||||
|
conn->ctx_valid ? &conn->ctx : NULL,
|
||||||
|
&in_desc,
|
||||||
|
flags,
|
||||||
|
0,
|
||||||
|
conn->ctx_valid ? NULL : &conn->ctx,
|
||||||
|
&out_desc,
|
||||||
|
&out_flags,
|
||||||
|
&expiry);
|
||||||
|
|
||||||
|
if (ss == SEC_E_OK || ss == SEC_I_CONTINUE_NEEDED)
|
||||||
|
conn->ctx_valid = true;
|
||||||
|
|
||||||
|
// Copy output token to out_buf
|
||||||
|
if (out_bufs[0].pvBuffer && out_bufs[0].cbBuffer > 0) {
|
||||||
|
byte_queue_write_setmincap(&conn->out_buf, out_bufs[0].cbBuffer);
|
||||||
|
byte_queue_write(&conn->out_buf, out_bufs[0].pvBuffer, out_bufs[0].cbBuffer);
|
||||||
|
FreeContextBuffer(out_bufs[0].pvBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate how much input was consumed
|
||||||
|
int consumed = in_avail;
|
||||||
|
if (in_bufs[1].BufferType == SECBUFFER_EXTRA && in_bufs[1].cbBuffer > 0)
|
||||||
|
consumed = in_avail - (int) in_bufs[1].cbBuffer;
|
||||||
|
|
||||||
|
if (ss == SEC_E_INCOMPLETE_MESSAGE) {
|
||||||
|
// SChannel didn't consume anything
|
||||||
|
byte_queue_read_ack(&conn->in_buf, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte_queue_read_ack(&conn->in_buf, consumed);
|
||||||
|
|
||||||
|
if (ss == SEC_I_CONTINUE_NEEDED)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (ss == SEC_E_OK) {
|
||||||
|
conn->handshake = false;
|
||||||
|
ss = QueryContextAttributes(&conn->ctx, SECPKG_ATTR_STREAM_SIZES, &conn->sizes);
|
||||||
|
if (ss != SEC_E_OK)
|
||||||
|
return -1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Network I/O (ciphertext ↔ socket)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
char *tls_conn_net_write_buf(TLS_Conn *conn, int *cap)
|
||||||
|
{
|
||||||
|
byte_queue_write_setmincap(&conn->in_buf, 4096);
|
||||||
|
ByteView bv = byte_queue_write_buf(&conn->in_buf);
|
||||||
|
if (!bv.ptr || bv.len == 0) {
|
||||||
|
byte_queue_write_ack(&conn->in_buf, 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*cap = (int) bv.len;
|
||||||
|
return (char *) bv.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tls_conn_net_write_ack(TLS_Conn *conn, int num)
|
||||||
|
{
|
||||||
|
byte_queue_write_ack(&conn->in_buf, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *tls_conn_net_read_buf(TLS_Conn *conn, int *num)
|
||||||
|
{
|
||||||
|
ByteView bv = byte_queue_read_buf(&conn->out_buf);
|
||||||
|
if (!bv.ptr || bv.len == 0) {
|
||||||
|
byte_queue_read_ack(&conn->out_buf, 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*num = (int) bv.len;
|
||||||
|
return (char *) bv.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tls_conn_net_read_ack(TLS_Conn *conn, int num)
|
||||||
|
{
|
||||||
|
byte_queue_read_ack(&conn->out_buf, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Application I/O (encrypt/decrypt)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int tls_conn_app_write(TLS_Conn *conn, char *src, int num)
|
||||||
|
{
|
||||||
|
assert(!conn->handshake);
|
||||||
|
|
||||||
|
if (num <= 0) return 0;
|
||||||
|
|
||||||
|
int max_msg = (int) conn->sizes.cbMaximumMessage;
|
||||||
|
if (num > max_msg)
|
||||||
|
num = max_msg;
|
||||||
|
|
||||||
|
int header_size = (int) conn->sizes.cbHeader;
|
||||||
|
int trailer_size = (int) conn->sizes.cbTrailer;
|
||||||
|
int total = header_size + num + trailer_size;
|
||||||
|
|
||||||
|
// Ensure output buffer has enough space
|
||||||
|
byte_queue_write_setmincap(&conn->out_buf, total);
|
||||||
|
ByteView bv = byte_queue_write_buf(&conn->out_buf);
|
||||||
|
if (!bv.ptr || (int) bv.len < total) {
|
||||||
|
// Try with less data
|
||||||
|
if (!bv.ptr || (int) bv.len < header_size + trailer_size + 1) {
|
||||||
|
byte_queue_write_ack(&conn->out_buf, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
num = (int) bv.len - header_size - trailer_size;
|
||||||
|
total = header_size + num + trailer_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *out_ptr = (char *) bv.ptr;
|
||||||
|
|
||||||
|
// Copy plaintext into the data portion
|
||||||
|
memcpy(out_ptr + header_size, src, num);
|
||||||
|
|
||||||
|
// Set up SecBuffers for in-place encryption
|
||||||
|
SecBuffer bufs[4];
|
||||||
|
bufs[0].BufferType = SECBUFFER_STREAM_HEADER;
|
||||||
|
bufs[0].pvBuffer = out_ptr;
|
||||||
|
bufs[0].cbBuffer = (unsigned long) header_size;
|
||||||
|
|
||||||
|
bufs[1].BufferType = SECBUFFER_DATA;
|
||||||
|
bufs[1].pvBuffer = out_ptr + header_size;
|
||||||
|
bufs[1].cbBuffer = (unsigned long) num;
|
||||||
|
|
||||||
|
bufs[2].BufferType = SECBUFFER_STREAM_TRAILER;
|
||||||
|
bufs[2].pvBuffer = out_ptr + header_size + num;
|
||||||
|
bufs[2].cbBuffer = (unsigned long) trailer_size;
|
||||||
|
|
||||||
|
bufs[3].BufferType = SECBUFFER_EMPTY;
|
||||||
|
bufs[3].pvBuffer = NULL;
|
||||||
|
bufs[3].cbBuffer = 0;
|
||||||
|
|
||||||
|
SecBufferDesc desc;
|
||||||
|
desc.ulVersion = SECBUFFER_VERSION;
|
||||||
|
desc.cBuffers = 4;
|
||||||
|
desc.pBuffers = bufs;
|
||||||
|
|
||||||
|
SECURITY_STATUS ss = EncryptMessage(&conn->ctx, 0, &desc, 0);
|
||||||
|
if (ss != SEC_E_OK) {
|
||||||
|
byte_queue_write_ack(&conn->out_buf, 0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int written = (int)(bufs[0].cbBuffer + bufs[1].cbBuffer + bufs[2].cbBuffer);
|
||||||
|
byte_queue_write_ack(&conn->out_buf, written);
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_conn_app_read(TLS_Conn *conn, char *dst, int cap)
|
||||||
|
{
|
||||||
|
assert(!conn->handshake);
|
||||||
|
|
||||||
|
// Drain any pending plaintext from a previous partial read
|
||||||
|
if (conn->pending_len > 0) {
|
||||||
|
int n = conn->pending_len;
|
||||||
|
if (n > cap) n = cap;
|
||||||
|
memcpy(dst, conn->pending + conn->pending_off, n);
|
||||||
|
conn->pending_off += n;
|
||||||
|
conn->pending_len -= n;
|
||||||
|
if (conn->pending_len == 0) {
|
||||||
|
free(conn->pending);
|
||||||
|
conn->pending = NULL;
|
||||||
|
conn->pending_off = 0;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteView in = byte_queue_read_buf(&conn->in_buf);
|
||||||
|
if (!in.ptr || in.len == 0) {
|
||||||
|
byte_queue_read_ack(&conn->in_buf, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int in_avail = (int) in.len;
|
||||||
|
|
||||||
|
// DecryptMessage operates in-place
|
||||||
|
SecBuffer bufs[4];
|
||||||
|
bufs[0].BufferType = SECBUFFER_DATA;
|
||||||
|
bufs[0].pvBuffer = in.ptr;
|
||||||
|
bufs[0].cbBuffer = (unsigned long) in_avail;
|
||||||
|
bufs[1].BufferType = SECBUFFER_EMPTY;
|
||||||
|
bufs[1].pvBuffer = NULL;
|
||||||
|
bufs[1].cbBuffer = 0;
|
||||||
|
bufs[2].BufferType = SECBUFFER_EMPTY;
|
||||||
|
bufs[2].pvBuffer = NULL;
|
||||||
|
bufs[2].cbBuffer = 0;
|
||||||
|
bufs[3].BufferType = SECBUFFER_EMPTY;
|
||||||
|
bufs[3].pvBuffer = NULL;
|
||||||
|
bufs[3].cbBuffer = 0;
|
||||||
|
|
||||||
|
SecBufferDesc desc;
|
||||||
|
desc.ulVersion = SECBUFFER_VERSION;
|
||||||
|
desc.cBuffers = 4;
|
||||||
|
desc.pBuffers = bufs;
|
||||||
|
|
||||||
|
SECURITY_STATUS ss = DecryptMessage(&conn->ctx, &desc, 0, NULL);
|
||||||
|
|
||||||
|
if (ss == SEC_E_INCOMPLETE_MESSAGE) {
|
||||||
|
byte_queue_read_ack(&conn->in_buf, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ss != SEC_E_OK && ss != SEC_I_RENEGOTIATE) {
|
||||||
|
byte_queue_read_ack(&conn->in_buf, 0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find decrypted data and extra ciphertext buffers
|
||||||
|
SecBuffer *data_buf = NULL;
|
||||||
|
SecBuffer *extra_buf = NULL;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (bufs[i].BufferType == SECBUFFER_DATA)
|
||||||
|
data_buf = &bufs[i];
|
||||||
|
else if (bufs[i].BufferType == SECBUFFER_EXTRA)
|
||||||
|
extra_buf = &bufs[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = 0;
|
||||||
|
if (data_buf && data_buf->cbBuffer > 0) {
|
||||||
|
int total = (int) data_buf->cbBuffer;
|
||||||
|
int n = total;
|
||||||
|
if (n > cap) n = cap;
|
||||||
|
memcpy(dst, data_buf->pvBuffer, n);
|
||||||
|
result = n;
|
||||||
|
|
||||||
|
// Save excess plaintext for next call
|
||||||
|
int leftover = total - n;
|
||||||
|
if (leftover > 0) {
|
||||||
|
conn->pending = malloc(leftover);
|
||||||
|
if (conn->pending) {
|
||||||
|
memcpy(conn->pending, (char *) data_buf->pvBuffer + n, leftover);
|
||||||
|
conn->pending_off = 0;
|
||||||
|
conn->pending_len = leftover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume processed input, keeping any extra ciphertext
|
||||||
|
int consumed = in_avail;
|
||||||
|
if (extra_buf && extra_buf->cbBuffer > 0)
|
||||||
|
consumed = in_avail - (int) extra_buf->cbBuffer;
|
||||||
|
byte_queue_read_ack(&conn->in_buf, consumed);
|
||||||
|
|
||||||
|
if (ss == SEC_I_RENEGOTIATE)
|
||||||
|
return result > 0 ? result : 0;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_conn_needs_flushing(TLS_Conn *conn)
|
||||||
|
{
|
||||||
|
return !byte_queue_empty(&conn->out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TLS_SCHANNEL
|
||||||
|
#endif // TLS_ENABLED
|
||||||
+30
-38
@@ -4,6 +4,7 @@
|
|||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
@@ -26,8 +27,6 @@
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
typedef struct {} Quakey;
|
typedef struct {} Quakey;
|
||||||
|
|
||||||
// Function pointers to a simulated program's code
|
// Function pointers to a simulated program's code
|
||||||
@@ -35,11 +34,6 @@ typedef int (*QuakeyInitFunc)(void *state, int argc, char **argv, void **ctxs, s
|
|||||||
typedef int (*QuakeyTickFunc)(void *state, void **ctxs, struct pollfd *pdata, int pcap, int *pnum, int *timeout);
|
typedef int (*QuakeyTickFunc)(void *state, void **ctxs, struct pollfd *pdata, int pcap, int *pnum, int *timeout);
|
||||||
typedef int (*QuakeyFreeFunc)(void *state);
|
typedef int (*QuakeyFreeFunc)(void *state);
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
QUAKEY_LINUX,
|
|
||||||
QUAKEY_WINDOWS,
|
|
||||||
} QuakeyPlatform;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
||||||
// Label associated to the process for debugging
|
// Label associated to the process for debugging
|
||||||
@@ -61,9 +55,6 @@ typedef struct {
|
|||||||
// Disk size for the process
|
// Disk size for the process
|
||||||
int disk_size;
|
int disk_size;
|
||||||
|
|
||||||
// Platform used by this program
|
|
||||||
QuakeyPlatform platform;
|
|
||||||
|
|
||||||
} QuakeySpawn;
|
} QuakeySpawn;
|
||||||
|
|
||||||
typedef unsigned long long QuakeyUInt64;
|
typedef unsigned long long QuakeyUInt64;
|
||||||
@@ -84,9 +75,6 @@ void *quakey_node_state(QuakeyNode node);
|
|||||||
// Schedule and executes one program until it would block, then returns
|
// Schedule and executes one program until it would block, then returns
|
||||||
int quakey_schedule_one(Quakey *quakey);
|
int quakey_schedule_one(Quakey *quakey);
|
||||||
|
|
||||||
// Get the current simulated time in nanoseconds
|
|
||||||
QuakeyUInt64 quakey_current_time(Quakey *quakey);
|
|
||||||
|
|
||||||
// Generate a random u64
|
// Generate a random u64
|
||||||
QuakeyUInt64 quakey_random(void);
|
QuakeyUInt64 quakey_random(void);
|
||||||
|
|
||||||
@@ -98,30 +86,40 @@ typedef struct {
|
|||||||
void quakey_signal(char *name);
|
void quakey_signal(char *name);
|
||||||
int quakey_get_signal(Quakey *quakey, QuakeySignal *signal);
|
int quakey_get_signal(Quakey *quakey, QuakeySignal *signal);
|
||||||
|
|
||||||
// Limit the number of hosts that can be crashed at the same time.
|
|
||||||
// Set to 0 for no limit (default).
|
|
||||||
void quakey_set_max_crashes(Quakey *quakey, int max_crashes);
|
|
||||||
|
|
||||||
void quakey_network_partitioning(Quakey *quakey, bool enable);
|
|
||||||
|
|
||||||
// Access spawned host information
|
// Access spawned host information
|
||||||
//
|
|
||||||
// TODO: The following are to be refactored as the host index is not stable
|
|
||||||
int quakey_num_hosts(Quakey *quakey);
|
int quakey_num_hosts(Quakey *quakey);
|
||||||
void *quakey_host_state(Quakey *quakey, int idx); // Returns NULL if host is dead
|
void *quakey_host_state(Quakey *quakey, int idx); // Returns NULL if host is dead
|
||||||
int quakey_host_is_dead(Quakey *quakey, int idx);
|
int quakey_host_is_dead(Quakey *quakey, int idx);
|
||||||
const char *quakey_host_name(Quakey *quakey, int idx);
|
const char *quakey_host_name(Quakey *quakey, int idx);
|
||||||
|
|
||||||
// Enter/leave a host's context so that mock_xxx functions (file I/O,
|
// Fault injection control
|
||||||
// etc.) operate on that host's resources. Use from code that runs
|
void quakey_set_max_crashes(Quakey *quakey, int max_crashes);
|
||||||
// outside of the normal init/tick/free callbacks (e.g. invariant
|
void quakey_network_partitioning(Quakey *quakey, bool enabled);
|
||||||
// checkers).
|
|
||||||
|
// Simulation time
|
||||||
|
QuakeyUInt64 quakey_current_time(Quakey *quakey);
|
||||||
|
|
||||||
|
// Host context (for accessing mock filesystem from outside scheduled context)
|
||||||
void quakey_enter_host(QuakeyNode node);
|
void quakey_enter_host(QuakeyNode node);
|
||||||
void quakey_leave_host(void);
|
void quakey_leave_host(void);
|
||||||
|
|
||||||
int *mock_errno_ptr(void);
|
int *mock_errno_ptr(void);
|
||||||
|
|
||||||
|
// Network mocks (POSIX-style, available on all platforms)
|
||||||
|
int mock_socket(int domain, int type, int protocol);
|
||||||
|
int mock_bind(int fd, void *addr, unsigned long addr_len);
|
||||||
|
int mock_connect(int fd, void *addr, unsigned long addr_len);
|
||||||
|
int mock_getsockopt(int fd, int level, int optname, void *optval, unsigned int *optlen);
|
||||||
|
int mock_listen(int fd, int backlog);
|
||||||
|
int mock_accept(int fd, void *addr, unsigned int *addr_len);
|
||||||
|
int mock_pipe(int *fds);
|
||||||
|
int mock_recv(int fd, char *dst, int len, int flags);
|
||||||
|
int mock_send(int fd, char *src, int len, int flags);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
// Windows-specific socket functions (use SOCKET type)
|
||||||
|
int mock_closesocket(SOCKET fd);
|
||||||
|
int mock_ioctlsocket(SOCKET fd, long cmd, unsigned long *argp);
|
||||||
BOOL mock_QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);
|
BOOL mock_QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);
|
||||||
BOOL mock_QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
|
BOOL mock_QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
|
||||||
HANDLE mock_CreateFileW(WCHAR *lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
|
HANDLE mock_CreateFileW(WCHAR *lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
|
||||||
@@ -139,23 +137,18 @@ HANDLE mock_FindFirstFileA(char *lpFileName, WIN32_FIND_DATAA *lpFindFileData);
|
|||||||
BOOL mock_FindNextFileA(HANDLE hFindFile, WIN32_FIND_DATAA *lpFindFileData);
|
BOOL mock_FindNextFileA(HANDLE hFindFile, WIN32_FIND_DATAA *lpFindFileData);
|
||||||
BOOL mock_FindClose(HANDLE hFindFile);
|
BOOL mock_FindClose(HANDLE hFindFile);
|
||||||
int mock__mkdir(char *path);
|
int mock__mkdir(char *path);
|
||||||
|
int mock_open(char *path, int flags, int mode);
|
||||||
|
int mock_close(int fd);
|
||||||
|
int mock_read(int fd, char *dst, int len);
|
||||||
|
int mock_write(int fd, char *src, int len);
|
||||||
|
int mock_remove(char *path);
|
||||||
|
int mock_rename(char *oldpath, char *newpath);
|
||||||
|
int mock_mkdir(char *path, int mode);
|
||||||
#else
|
#else
|
||||||
int mock_socket(int domain, int type, int protocol);
|
|
||||||
int mock_closesocket(int fd);
|
|
||||||
int mock_ioctlsocket(int fd, long cmd, unsigned long *argp);
|
|
||||||
int mock_bind(int fd, void *addr, unsigned long addr_len);
|
|
||||||
int mock_connect(int fd, void *addr, unsigned long addr_len);
|
|
||||||
int mock_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen);
|
|
||||||
int mock_listen(int fd, int backlog);
|
|
||||||
int mock_accept(int fd, void *addr, socklen_t *addr_len);
|
|
||||||
int mock_pipe(int *fds);
|
|
||||||
int mock_recv(int fd, char *dst, int len, int flags);
|
|
||||||
int mock_send(int fd, char *src, int len, int flags);
|
|
||||||
int mock_clock_gettime(clockid_t clockid, struct timespec *tp);
|
int mock_clock_gettime(clockid_t clockid, struct timespec *tp);
|
||||||
int mock_open(char *path, int flags, int mode);
|
int mock_open(char *path, int flags, int mode);
|
||||||
int mock_fcntl(int fd, int cmd, int flags);
|
int mock_fcntl(int fd, int cmd, int flags);
|
||||||
int mock_close(int fd);
|
int mock_close(int fd);
|
||||||
int mock_access(const char *path, int mode);
|
|
||||||
int mock_ftruncate(int fd, size_t new_size);
|
int mock_ftruncate(int fd, size_t new_size);
|
||||||
int mock_fstat(int fd, struct stat *buf);
|
int mock_fstat(int fd, struct stat *buf);
|
||||||
int mock_read(int fd, char *dst, int len);
|
int mock_read(int fd, char *dst, int len);
|
||||||
@@ -204,7 +197,6 @@ void mock_free(void *ptr);
|
|||||||
#define open mock_open
|
#define open mock_open
|
||||||
#define fcntl mock_fcntl
|
#define fcntl mock_fcntl
|
||||||
#define close mock_close
|
#define close mock_close
|
||||||
#define access mock_access
|
|
||||||
#define ftruncate mock_ftruncate
|
#define ftruncate mock_ftruncate
|
||||||
#define CreateFileW mock_CreateFileW
|
#define CreateFileW mock_CreateFileW
|
||||||
#define CloseHandle mock_CloseHandle
|
#define CloseHandle mock_CloseHandle
|
||||||
|
|||||||
+111
-199
@@ -5,6 +5,7 @@
|
|||||||
#include <quakey.h>
|
#include <quakey.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// Utilities
|
// Utilities
|
||||||
@@ -244,9 +245,6 @@ struct Host {
|
|||||||
|
|
||||||
char *name;
|
char *name;
|
||||||
|
|
||||||
// Platform used by this host
|
|
||||||
QuakeyPlatform platform;
|
|
||||||
|
|
||||||
// State of the ephimeral port allocation
|
// State of the ephimeral port allocation
|
||||||
u16 next_ephemeral_port;
|
u16 next_ephemeral_port;
|
||||||
|
|
||||||
@@ -402,11 +400,6 @@ struct Sim {
|
|||||||
// reached, a new random target is generated.
|
// reached, a new random target is generated.
|
||||||
HostPairList partition;
|
HostPairList partition;
|
||||||
HostPairList target_partition;
|
HostPairList target_partition;
|
||||||
|
|
||||||
// Maximum number of hosts that can be dead at the same time.
|
|
||||||
int max_crashes;
|
|
||||||
|
|
||||||
bool network_partitioning;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void time_event_wakeup(Sim *sim, Nanos time, Host *host);
|
static void time_event_wakeup(Sim *sim, Nanos time, Host *host);
|
||||||
@@ -745,6 +738,11 @@ static b32 socket_queue_empty(SocketQueue *queue)
|
|||||||
return queue->used == 0;
|
return queue->used == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static b32 socket_queue_used(SocketQueue *queue)
|
||||||
|
{
|
||||||
|
return queue->used;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// Accept Queue Code
|
// Accept Queue Code
|
||||||
|
|
||||||
@@ -940,7 +938,6 @@ static void host_init(Host *host, Sim *sim, QuakeySpawn config, char *arg)
|
|||||||
{
|
{
|
||||||
host->sim = sim;
|
host->sim = sim;
|
||||||
host->name = config.name;
|
host->name = config.name;
|
||||||
host->platform = config.platform;
|
|
||||||
host->next_ephemeral_port = FIRST_EPHEMERAL_PORT;
|
host->next_ephemeral_port = FIRST_EPHEMERAL_PORT;
|
||||||
|
|
||||||
// Save spawn config for restart after crash
|
// Save spawn config for restart after crash
|
||||||
@@ -1062,11 +1059,8 @@ static void host_free(Host *host)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove all events associated with a host (WAKEUP events and events
|
// Remove all events associated with a host (WAKEUP events and events
|
||||||
// whose source or destination descriptors belong to this host).
|
// whose source or destination descriptors belong to this host)
|
||||||
// If skip_restart is true, RESTART events are kept (used during
|
static void remove_events_for_host(Sim *sim, Host *host)
|
||||||
// restart failure to avoid removing the event currently being
|
|
||||||
// processed by process_events_at_current_time).
|
|
||||||
static void remove_events_for_host_ex(Sim *sim, Host *host, bool skip_restart)
|
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < sim->num_events) {
|
while (i < sim->num_events) {
|
||||||
@@ -1075,7 +1069,7 @@ static void remove_events_for_host_ex(Sim *sim, Host *host, bool skip_restart)
|
|||||||
|
|
||||||
if (event->type == EVENT_TYPE_WAKEUP && event->host == host)
|
if (event->type == EVENT_TYPE_WAKEUP && event->host == host)
|
||||||
remove = true;
|
remove = true;
|
||||||
if (!skip_restart && event->type == EVENT_TYPE_RESTART && event->host == host)
|
if (event->type == EVENT_TYPE_RESTART && event->host == host)
|
||||||
remove = true;
|
remove = true;
|
||||||
if (event->src_desc && event->src_desc->host == host)
|
if (event->src_desc && event->src_desc->host == host)
|
||||||
remove = true;
|
remove = true;
|
||||||
@@ -1091,11 +1085,6 @@ static void remove_events_for_host_ex(Sim *sim, Host *host, bool skip_restart)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void remove_events_for_host(Sim *sim, Host *host)
|
|
||||||
{
|
|
||||||
remove_events_for_host_ex(sim, host, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int count_dead(Sim *sim)
|
static int count_dead(Sim *sim)
|
||||||
{
|
{
|
||||||
int n = 0;
|
int n = 0;
|
||||||
@@ -1123,11 +1112,6 @@ static void host_crash(Host *host)
|
|||||||
// Remove all pending events for this host
|
// Remove all pending events for this host
|
||||||
remove_events_for_host(sim, host);
|
remove_events_for_host(sim, host);
|
||||||
|
|
||||||
// Let the application clean up (e.g. log the crash)
|
|
||||||
host___ = host;
|
|
||||||
host->free_func(host->state);
|
|
||||||
host___ = NULL;
|
|
||||||
|
|
||||||
// Free application state
|
// Free application state
|
||||||
free(host->state);
|
free(host->state);
|
||||||
host->state = NULL;
|
host->state = NULL;
|
||||||
@@ -1138,6 +1122,7 @@ static void host_crash(Host *host)
|
|||||||
host->poll_count = 0;
|
host->poll_count = 0;
|
||||||
host->poll_timeout = -1;
|
host->poll_timeout = -1;
|
||||||
|
|
||||||
|
printf("Quakey: Host crash (%d/%d dead)\n", count_dead(sim), sim->num_hosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart a crashed host: re-initialize from saved config,
|
// Restart a crashed host: re-initialize from saved config,
|
||||||
@@ -1208,22 +1193,10 @@ static void host_restart(Host *host)
|
|||||||
desc_free(sim, &host->desc[i], true);
|
desc_free(sim, &host->desc[i], true);
|
||||||
}
|
}
|
||||||
host->num_desc = 0;
|
host->num_desc = 0;
|
||||||
// Skip removing RESTART events: this function is called from
|
remove_events_for_host(sim, host);
|
||||||
// time_event_process (EVENT_TYPE_RESTART), so the current
|
|
||||||
// restart event is still in the array being iterated by
|
|
||||||
// process_events_at_current_time. Removing it here would cause
|
|
||||||
// the outer loop's swap-removal to discard an unrelated event.
|
|
||||||
remove_events_for_host_ex(sim, host, true);
|
|
||||||
free(host->state);
|
free(host->state);
|
||||||
host->state = NULL;
|
host->state = NULL;
|
||||||
host->dead = true;
|
host->dead = true;
|
||||||
|
|
||||||
// Schedule another restart attempt. Init can fail due to
|
|
||||||
// transient I/O errors from fault injection; permanently
|
|
||||||
// killing the host would be unrealistic since a real
|
|
||||||
// process supervisor would keep retrying.
|
|
||||||
Nanos restart_delay = 1000000000ULL + (sim_random(sim) % 9000000000ULL);
|
|
||||||
time_event_restart(sim, sim->current_time + restart_delay, host);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1233,17 +1206,9 @@ static void host_restart(Host *host)
|
|||||||
} else if (host->poll_timeout == 0)
|
} else if (host->poll_timeout == 0)
|
||||||
host->timedout = true;
|
host->timedout = true;
|
||||||
|
|
||||||
|
printf("Quakey: Host restart (%d/%d dead)\n", count_dead(sim), sim->num_hosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
static b32 host_is_linux(Host *host)
|
|
||||||
{
|
|
||||||
return host->platform == QUAKEY_LINUX;
|
|
||||||
}
|
|
||||||
|
|
||||||
static b32 host_is_windows(Host *host)
|
|
||||||
{
|
|
||||||
return host->platform == QUAKEY_WINDOWS;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Nanos host_time(Host *host)
|
static Nanos host_time(Host *host)
|
||||||
{
|
{
|
||||||
@@ -1402,10 +1367,7 @@ static void host_update(Host *host)
|
|||||||
);
|
);
|
||||||
host___ = NULL;
|
host___ = NULL;
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
host_crash(host);
|
TODO;
|
||||||
Nanos restart_delay = 1000000000ULL + (sim_random(host->sim) % 9000000000ULL);
|
|
||||||
time_event_restart(host->sim, host->sim->current_time + restart_delay, host);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host->poll_timeout > 0) {
|
if (host->poll_timeout > 0) {
|
||||||
@@ -1985,7 +1947,7 @@ static int host_read(Host *host, int desc_idx, char *dst, int len)
|
|||||||
desc->type == DESC_PIPE) {
|
desc->type == DESC_PIPE) {
|
||||||
num = recv_inner(desc, dst, len);
|
num = recv_inner(desc, dst, len);
|
||||||
} else if (desc->type == DESC_FILE) {
|
} else if (desc->type == DESC_FILE) {
|
||||||
#if defined(FAULT_INJECTION) && !defined(DISABLE_SPURIOUS_IO_ERRORS)
|
#ifdef FAULT_INJECTION
|
||||||
uint64_t roll = sim_random(host->sim) % 1000;
|
uint64_t roll = sim_random(host->sim) % 1000;
|
||||||
if (roll == 0) return HOST_ERROR_IO;
|
if (roll == 0) return HOST_ERROR_IO;
|
||||||
#endif
|
#endif
|
||||||
@@ -2029,7 +1991,7 @@ static int host_write(Host *host, int desc_idx, char *src, int len)
|
|||||||
desc->type == DESC_PIPE) {
|
desc->type == DESC_PIPE) {
|
||||||
num = send_inner(desc, src, len);
|
num = send_inner(desc, src, len);
|
||||||
} else if (desc->type == DESC_FILE) {
|
} else if (desc->type == DESC_FILE) {
|
||||||
#if defined(FAULT_INJECTION) && !defined(DISABLE_SPURIOUS_IO_ERRORS)
|
#ifdef FAULT_INJECTION
|
||||||
uint64_t roll = sim_random(host->sim) % 1000;
|
uint64_t roll = sim_random(host->sim) % 1000;
|
||||||
if (roll == 0) return HOST_ERROR_IO;
|
if (roll == 0) return HOST_ERROR_IO;
|
||||||
if (roll == 1) return HOST_ERROR_NOSPC;
|
if (roll == 1) return HOST_ERROR_NOSPC;
|
||||||
@@ -2205,7 +2167,7 @@ static int host_fsync(Host *host, int desc_idx)
|
|||||||
if (desc->type != DESC_FILE)
|
if (desc->type != DESC_FILE)
|
||||||
return HOST_ERROR_BADIDX;
|
return HOST_ERROR_BADIDX;
|
||||||
|
|
||||||
#if defined(FAULT_INJECTION) && !defined(DISABLE_SPURIOUS_IO_ERRORS)
|
#ifdef FAULT_INJECTION
|
||||||
uint64_t roll = sim_random(host->sim) % 100;
|
uint64_t roll = sim_random(host->sim) % 100;
|
||||||
if (roll == 0)
|
if (roll == 0)
|
||||||
return HOST_ERROR_IO;
|
return HOST_ERROR_IO;
|
||||||
@@ -2430,7 +2392,7 @@ static bool hosts_are_partitioned(Sim *sim, Host *a, Host *b);
|
|||||||
|
|
||||||
static Nanos pick_partition_delay(Sim *sim, bool longer)
|
static Nanos pick_partition_delay(Sim *sim, bool longer)
|
||||||
{
|
{
|
||||||
Nanos min_delay = 1000000000; // 1s
|
Nanos min_delay = 10000000; // 10ms
|
||||||
Nanos max_delay = 10000000000; // 10s
|
Nanos max_delay = 10000000000; // 10s
|
||||||
if (longer) {
|
if (longer) {
|
||||||
min_delay = 10000000000;
|
min_delay = 10000000000;
|
||||||
@@ -2447,13 +2409,10 @@ static b32 time_event_process(TimeEvent *event, Sim *sim)
|
|||||||
{
|
{
|
||||||
// Try to move one step toward the target partition.
|
// Try to move one step toward the target partition.
|
||||||
// If current already matches target, pick a new target.
|
// If current already matches target, pick a new target.
|
||||||
if (!break_or_repair_link(sim)) {
|
if (!break_or_repair_link(sim))
|
||||||
if (sim->network_partitioning) {
|
if (change_target_partition(sim) < 0) {
|
||||||
if (change_target_partition(sim) < 0) {
|
TODO;
|
||||||
TODO;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Reschedule for the next partition step
|
// Reschedule for the next partition step
|
||||||
event->time = sim->current_time + pick_partition_delay(sim, true);
|
event->time = sim->current_time + pick_partition_delay(sim, true);
|
||||||
@@ -2473,8 +2432,8 @@ static b32 time_event_process(TimeEvent *event, Sim *sim)
|
|||||||
Host *peer_host = sim->hosts[idx];
|
Host *peer_host = sim->hosts[idx];
|
||||||
|
|
||||||
if (hosts_are_partitioned(sim, src_desc->host, peer_host)) {
|
if (hosts_are_partitioned(sim, src_desc->host, peer_host)) {
|
||||||
//SIM_TRACE(sim, "PARTITION: blocked connection %s -> %s",
|
SIM_TRACE(sim, "PARTITION: blocked connection %s -> %s",
|
||||||
// src_desc->host->name, peer_host->name);
|
src_desc->host->name, peer_host->name);
|
||||||
src_desc->connect_status = CONNECT_STATUS_NOHOST;
|
src_desc->connect_status = CONNECT_STATUS_NOHOST;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2791,22 +2750,11 @@ static void sim_init(Sim *sim, uint64_t seed)
|
|||||||
sim->events = NULL;
|
sim->events = NULL;
|
||||||
sim->num_signals = 0;
|
sim->num_signals = 0;
|
||||||
sim->head_signal = 0;
|
sim->head_signal = 0;
|
||||||
sim->max_crashes = 0;
|
|
||||||
sim->network_partitioning = true;
|
|
||||||
host_pair_list_init(&sim->partition);
|
host_pair_list_init(&sim->partition);
|
||||||
host_pair_list_init(&sim->target_partition);
|
host_pair_list_init(&sim->target_partition);
|
||||||
time_event_partition(sim, pick_partition_delay(sim, false));
|
time_event_partition(sim, pick_partition_delay(sim, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sim_network_partitioning(Sim *sim, bool enable)
|
|
||||||
{
|
|
||||||
sim->network_partitioning = enable;
|
|
||||||
if (!enable) {
|
|
||||||
host_pair_list_free(&sim->target_partition);
|
|
||||||
host_pair_list_init(&sim->target_partition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sim_free(Sim *sim)
|
static void sim_free(Sim *sim)
|
||||||
{
|
{
|
||||||
host_pair_list_free(&sim->target_partition);
|
host_pair_list_free(&sim->target_partition);
|
||||||
@@ -2890,8 +2838,8 @@ static void process_events_at_current_time(Sim *sim)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (sim->num_events > 10000)
|
if (sim->num_events > 10000)
|
||||||
// SIM_TRACE(sim, "WARNING: event queue large: %d events", sim->num_events);
|
SIM_TRACE(sim, "WARNING: event queue large: %d events", sim->num_events);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int find_first_ready_host(Sim *sim)
|
static int find_first_ready_host(Sim *sim)
|
||||||
@@ -2954,14 +2902,14 @@ static b32 sim_update(Sim *sim)
|
|||||||
if (sim->num_hosts > 1) {
|
if (sim->num_hosts > 1) {
|
||||||
uint64_t rng = sim_random(sim);
|
uint64_t rng = sim_random(sim);
|
||||||
if ((rng % 10000) == 0) {
|
if ((rng % 10000) == 0) {
|
||||||
|
// Pick a random live host to crash
|
||||||
int live_count = 0;
|
int live_count = 0;
|
||||||
for (int i = 0; i < sim->num_hosts; i++)
|
for (int i = 0; i < sim->num_hosts; i++)
|
||||||
if (!sim->hosts[i]->dead)
|
if (!sim->hosts[i]->dead)
|
||||||
live_count++;
|
live_count++;
|
||||||
int dead_count = sim->num_hosts - live_count;
|
|
||||||
|
|
||||||
if (dead_count < sim->max_crashes) {
|
// Only crash if at least 2 hosts are alive (keep a majority)
|
||||||
|
if (live_count >= 2) {
|
||||||
// Pick one of the live hosts at random
|
// Pick one of the live hosts at random
|
||||||
int target = sim_random(sim) % live_count;
|
int target = sim_random(sim) % live_count;
|
||||||
int j = 0;
|
int j = 0;
|
||||||
@@ -3053,18 +3001,6 @@ void quakey_free(Quakey *quakey)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void quakey_set_max_crashes(Quakey *quakey, int max_crashes)
|
|
||||||
{
|
|
||||||
Sim *sim = (Sim*) quakey;
|
|
||||||
sim->max_crashes = max_crashes;
|
|
||||||
}
|
|
||||||
|
|
||||||
void quakey_network_partitioning(Quakey *quakey, bool enable)
|
|
||||||
{
|
|
||||||
Sim *sim = (Sim*) quakey;
|
|
||||||
sim_network_partitioning(sim, enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
QuakeyNode quakey_spawn(Quakey *quakey, QuakeySpawn config, char *arg)
|
QuakeyNode quakey_spawn(Quakey *quakey, QuakeySpawn config, char *arg)
|
||||||
{
|
{
|
||||||
return (QuakeyNode) sim_spawn((Sim*) quakey, config, arg);
|
return (QuakeyNode) sim_spawn((Sim*) quakey, config, arg);
|
||||||
@@ -3076,28 +3012,11 @@ void *quakey_node_state(QuakeyNode node)
|
|||||||
return host->state;
|
return host->state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void quakey_enter_host(QuakeyNode node)
|
|
||||||
{
|
|
||||||
Host *host = (void*) node;
|
|
||||||
host___ = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
void quakey_leave_host(void)
|
|
||||||
{
|
|
||||||
host___ = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int quakey_schedule_one(Quakey *quakey)
|
int quakey_schedule_one(Quakey *quakey)
|
||||||
{
|
{
|
||||||
return sim_update((Sim*) quakey);
|
return sim_update((Sim*) quakey);
|
||||||
}
|
}
|
||||||
|
|
||||||
QuakeyUInt64 quakey_current_time(Quakey *quakey)
|
|
||||||
{
|
|
||||||
Sim *sim = (Sim*) quakey;
|
|
||||||
return sim->current_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
QuakeyUInt64 quakey_random(void)
|
QuakeyUInt64 quakey_random(void)
|
||||||
{
|
{
|
||||||
Host *host = host___;
|
Host *host = host___;
|
||||||
@@ -3157,6 +3076,39 @@ const char *quakey_host_name(Quakey *quakey, int idx)
|
|||||||
return sim->hosts[idx]->name;
|
return sim->hosts[idx]->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void quakey_set_max_crashes(Quakey *quakey, int max_crashes)
|
||||||
|
{
|
||||||
|
// TODO: Implement crash limiting. For now this is a no-op
|
||||||
|
// that allows the simulation to run without the feature.
|
||||||
|
(void) quakey;
|
||||||
|
(void) max_crashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void quakey_network_partitioning(Quakey *quakey, bool enabled)
|
||||||
|
{
|
||||||
|
// TODO: Implement network partitioning toggle. For now this is
|
||||||
|
// a no-op that allows the simulation to run without the feature.
|
||||||
|
(void) quakey;
|
||||||
|
(void) enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuakeyUInt64 quakey_current_time(Quakey *quakey)
|
||||||
|
{
|
||||||
|
Sim *sim = (Sim*) quakey;
|
||||||
|
return (QuakeyUInt64) sim->current_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void quakey_enter_host(QuakeyNode node)
|
||||||
|
{
|
||||||
|
Host *host = (void*) node;
|
||||||
|
host___ = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
void quakey_leave_host(void)
|
||||||
|
{
|
||||||
|
host___ = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// Mock System Calls
|
// Mock System Calls
|
||||||
|
|
||||||
@@ -3169,7 +3121,7 @@ int *mock_errno_ptr(void)
|
|||||||
return host_errno_ptr(host);
|
return host_errno_ptr(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _WIN32
|
// Platform-independent mock functions (used by lib/ on all platforms)
|
||||||
|
|
||||||
int mock_socket(int domain, int type, int protocol)
|
int mock_socket(int domain, int type, int protocol)
|
||||||
{
|
{
|
||||||
@@ -3221,8 +3173,6 @@ int mock_close(int fd)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_close() with no node scheduled\n");
|
abort_("Call to mock_close() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_close() not from Linux\n");
|
|
||||||
|
|
||||||
int desc_idx = fd;
|
int desc_idx = fd;
|
||||||
int ret = host_close(host, desc_idx, false);
|
int ret = host_close(host, desc_idx, false);
|
||||||
@@ -3241,17 +3191,6 @@ int mock_close(int fd)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: mock_access must be implemented for simulation to work correctly.
|
|
||||||
// chunk_store_exists() uses file_exists() which calls access(). Without a
|
|
||||||
// working mock_access, the simulation cannot check chunk existence and
|
|
||||||
// must fall back to file_open (O_CREAT), which creates empty files as a
|
|
||||||
// side effect and leads to data corruption (servers return uninitialized
|
|
||||||
// data for chunks they don't actually have).
|
|
||||||
int mock_access(const char *path, int mode)
|
|
||||||
{
|
|
||||||
assert(0); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
static int convert_addr(void *addr, size_t addr_len,
|
static int convert_addr(void *addr, size_t addr_len,
|
||||||
Addr *converted_addr, uint16_t *converted_port)
|
Addr *converted_addr, uint16_t *converted_port)
|
||||||
{
|
{
|
||||||
@@ -3443,8 +3382,6 @@ int mock_open(char *path, int flags, int mode)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_open() with no node scheduled\n");
|
abort_("Call to mock_open() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_open() not from Linux\n");
|
|
||||||
|
|
||||||
int converted_flags = convert_linux_open_flags_to_mockfs(flags);
|
int converted_flags = convert_linux_open_flags_to_mockfs(flags);
|
||||||
|
|
||||||
@@ -3474,8 +3411,6 @@ int mock_read(int fd, char *dst, int len)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_read() with no node scheduled\n");
|
abort_("Call to mock_read() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_read() not from Linux\n");
|
|
||||||
|
|
||||||
int ret = host_read(host, fd, dst, len);
|
int ret = host_read(host, fd, dst, len);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -3506,8 +3441,6 @@ int mock_write(int fd, char *src, int len)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_write() with no node scheduled\n");
|
abort_("Call to mock_write() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_write() not from Linux\n");
|
|
||||||
|
|
||||||
int ret = host_write(host, fd, src, len);
|
int ret = host_write(host, fd, src, len);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -3603,7 +3536,7 @@ int mock_send(int fd, char *src, int len, int flags)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mock_accept(int fd, void *addr, socklen_t *addr_len)
|
int mock_accept(int fd, void *addr, unsigned int *addr_len)
|
||||||
{
|
{
|
||||||
Host *host = host___;
|
Host *host = host___;
|
||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
@@ -3661,7 +3594,7 @@ int mock_accept(int fd, void *addr, socklen_t *addr_len)
|
|||||||
return new_fd;
|
return new_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mock_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen)
|
int mock_getsockopt(int fd, int level, int optname, void *optval, unsigned int *optlen)
|
||||||
{
|
{
|
||||||
if (level != SOL_SOCKET)
|
if (level != SOL_SOCKET)
|
||||||
abort_("Call to mock_getsockopt() with level other than SOL_SOCKET\n");
|
abort_("Call to mock_getsockopt() with level other than SOL_SOCKET\n");
|
||||||
@@ -3716,8 +3649,6 @@ int mock_remove(char *path)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_remove() with no node scheduled\n");
|
abort_("Call to mock_remove() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_remove() not from Linux\n");
|
|
||||||
|
|
||||||
int ret = host_remove(host, path);
|
int ret = host_remove(host, path);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -3744,8 +3675,6 @@ int mock_rename(char *oldpath, char *newpath)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_rename() with no node scheduled\n");
|
abort_("Call to mock_rename() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_rename() not from Linux\n");
|
|
||||||
|
|
||||||
int ret = host_rename(host, oldpath, newpath);
|
int ret = host_rename(host, oldpath, newpath);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -3772,14 +3701,14 @@ int mock_rename(char *oldpath, char *newpath)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
|
||||||
int mock_clock_gettime(clockid_t clockid, struct timespec *tp)
|
int mock_clock_gettime(clockid_t clockid, struct timespec *tp)
|
||||||
{
|
{
|
||||||
Host *host = host___;
|
Host *host = host___;
|
||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_clock_gettime() with no node scheduled\n");
|
abort_("Call to mock_clock_gettime() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_clock_gettime() not from Linux\n");
|
|
||||||
|
|
||||||
if (tp == NULL) {
|
if (tp == NULL) {
|
||||||
*host_errno_ptr(host) = EINVAL;
|
*host_errno_ptr(host) = EINVAL;
|
||||||
@@ -3817,8 +3746,6 @@ int mock_fsync(int fd)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_fsync() with no node scheduled\n");
|
abort_("Call to mock_fsync() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_fsync() not from Linux\n");
|
|
||||||
|
|
||||||
int ret = host_fsync(host, fd);
|
int ret = host_fsync(host, fd);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -3838,8 +3765,6 @@ int mock_ftruncate(int fd, size_t new_size)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_ftruncate() with no node scheduled\n");
|
abort_("Call to mock_ftruncate() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_ftruncate() not from Linux\n");
|
|
||||||
|
|
||||||
int ret = host_ftruncate(host, fd, (int) new_size);
|
int ret = host_ftruncate(host, fd, (int) new_size);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -3861,8 +3786,6 @@ off_t mock_lseek(int fd, off_t offset, int whence)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_lseek() with no node scheduled\n");
|
abort_("Call to mock_lseek() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_lseek() not from Linux\n");
|
|
||||||
|
|
||||||
// Convert POSIX whence to HOST whence
|
// Convert POSIX whence to HOST whence
|
||||||
int host_whence;
|
int host_whence;
|
||||||
@@ -3899,8 +3822,6 @@ int mock_fstat(int fd, struct stat *buf)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_fstat() with no node scheduled\n");
|
abort_("Call to mock_fstat() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_fstat() not from Linux\n");
|
|
||||||
|
|
||||||
if (buf == NULL) {
|
if (buf == NULL) {
|
||||||
*host_errno_ptr(host) = EINVAL;
|
*host_errno_ptr(host) = EINVAL;
|
||||||
@@ -3942,8 +3863,6 @@ char *mock_realpath(char *path, char *dst)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_realpath() with no node scheduled\n");
|
abort_("Call to mock_realpath() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_realpath() not from Linux\n");
|
|
||||||
|
|
||||||
if (path == NULL) {
|
if (path == NULL) {
|
||||||
*host_errno_ptr(host) = EINVAL;
|
*host_errno_ptr(host) = EINVAL;
|
||||||
@@ -4057,8 +3976,6 @@ int mock_mkdir(char *path, mode_t mode)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_mkdir() with no node scheduled\n");
|
abort_("Call to mock_mkdir() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_mkdir() not from Linux\n");
|
|
||||||
|
|
||||||
// LittleFS doesn't use mode, but we accept it for API compatibility
|
// LittleFS doesn't use mode, but we accept it for API compatibility
|
||||||
(void) mode;
|
(void) mode;
|
||||||
@@ -4088,8 +4005,6 @@ int mock_fcntl(int fd, int cmd, int flags)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_fcntl() with no node scheduled\n");
|
abort_("Call to mock_fcntl() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_fcntl() not from Linux\n");
|
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
|
|
||||||
@@ -4142,8 +4057,6 @@ DIR *mock_opendir(char *name)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_opendir() with no node scheduled\n");
|
abort_("Call to mock_opendir() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_opendir() not from Linux\n");
|
|
||||||
|
|
||||||
int ret = host_open_dir(host, name);
|
int ret = host_open_dir(host, name);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -4180,8 +4093,6 @@ struct dirent* mock_readdir(DIR *dirp)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_readdir() with no node scheduled\n");
|
abort_("Call to mock_readdir() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_readdir() not from Linux\n");
|
|
||||||
|
|
||||||
DIR_ *dirp_ = (DIR_*) dirp;
|
DIR_ *dirp_ = (DIR_*) dirp;
|
||||||
|
|
||||||
@@ -4230,8 +4141,6 @@ int mock_closedir(DIR *dirp)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_closedir() with no node scheduled\n");
|
abort_("Call to mock_closedir() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_linux(host))
|
|
||||||
abort_("Call to mock_closedir() not from Linux\n");
|
|
||||||
|
|
||||||
DIR_ *dirp_ = (DIR_*) dirp;
|
DIR_ *dirp_ = (DIR_*) dirp;
|
||||||
|
|
||||||
@@ -4266,8 +4175,6 @@ int mock_GetLastError(void)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_GetLastError() with no node scheduled\n");
|
abort_("Call to mock_GetLastError() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_GetLastError() not from Windows\n");
|
|
||||||
|
|
||||||
// Note that technically on windows errno and GetLastError
|
// Note that technically on windows errno and GetLastError
|
||||||
// are different things. Here we use errno_ to store the
|
// are different things. Here we use errno_ to store the
|
||||||
@@ -4287,8 +4194,6 @@ void mock_SetLastError(int err)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_SetLastError() with no node scheduled\n");
|
abort_("Call to mock_SetLastError() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_SetLastError() not from Windows\n");
|
|
||||||
|
|
||||||
*host_errno_ptr(host) = err;
|
*host_errno_ptr(host) = err;
|
||||||
}
|
}
|
||||||
@@ -4304,8 +4209,6 @@ int mock_closesocket(SOCKET fd)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_closesocket() with no node scheduled\n");
|
abort_("Call to mock_closesocket() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_closesocket() not from Windows\n");
|
|
||||||
|
|
||||||
int desc_idx = fd;
|
int desc_idx = fd;
|
||||||
int ret = host_close(host, desc_idx, true); // expect_socket = true
|
int ret = host_close(host, desc_idx, true); // expect_socket = true
|
||||||
@@ -4326,7 +4229,19 @@ int mock_closesocket(SOCKET fd)
|
|||||||
|
|
||||||
int mock_ioctlsocket(SOCKET fd, long cmd, unsigned long *argp)
|
int mock_ioctlsocket(SOCKET fd, long cmd, unsigned long *argp)
|
||||||
{
|
{
|
||||||
TODO;
|
Host *host = host___;
|
||||||
|
if (host == NULL)
|
||||||
|
abort_("Call to mock_ioctlsocket() with no node scheduled\n");
|
||||||
|
|
||||||
|
if (cmd == FIONBIO) {
|
||||||
|
int host_flags = (*argp != 0) ? HOST_FLAG_NONBLOCK : 0;
|
||||||
|
int ret = host_setdescflags(host, (int) fd, host_flags);
|
||||||
|
if (ret < 0)
|
||||||
|
return SOCKET_ERROR;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SOCKET_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to convert wide string to narrow string (ASCII subset)
|
// Helper function to convert wide string to narrow string (ASCII subset)
|
||||||
@@ -4353,11 +4268,11 @@ static int convert_windows_flags_to_lfs(DWORD dwDesiredAccess,
|
|||||||
|
|
||||||
// Convert access mode
|
// Convert access mode
|
||||||
if ((dwDesiredAccess & GENERIC_READ) && (dwDesiredAccess & GENERIC_WRITE))
|
if ((dwDesiredAccess & GENERIC_READ) && (dwDesiredAccess & GENERIC_WRITE))
|
||||||
lfs_flags = LFS_O_RDWR;
|
lfs_flags = MOCKFS_O_RDWR;
|
||||||
else if (dwDesiredAccess & GENERIC_WRITE)
|
else if (dwDesiredAccess & GENERIC_WRITE)
|
||||||
lfs_flags = LFS_O_WRONLY;
|
lfs_flags = MOCKFS_O_WRONLY;
|
||||||
else
|
else
|
||||||
lfs_flags = LFS_O_RDONLY;
|
lfs_flags = MOCKFS_O_RDONLY;
|
||||||
|
|
||||||
*truncate = false;
|
*truncate = false;
|
||||||
|
|
||||||
@@ -4365,11 +4280,11 @@ static int convert_windows_flags_to_lfs(DWORD dwDesiredAccess,
|
|||||||
switch (dwCreationDisposition) {
|
switch (dwCreationDisposition) {
|
||||||
case CREATE_NEW:
|
case CREATE_NEW:
|
||||||
// Creates a new file, fails if file exists
|
// Creates a new file, fails if file exists
|
||||||
lfs_flags |= LFS_O_CREAT | LFS_O_EXCL;
|
lfs_flags |= MOCKFS_O_CREAT | MOCKFS_O_EXCL;
|
||||||
break;
|
break;
|
||||||
case CREATE_ALWAYS:
|
case CREATE_ALWAYS:
|
||||||
// Creates a new file, always (truncates if exists)
|
// Creates a new file, always (truncates if exists)
|
||||||
lfs_flags |= LFS_O_CREAT | LFS_O_TRUNC;
|
lfs_flags |= MOCKFS_O_CREAT | MOCKFS_O_TRUNC;
|
||||||
*truncate = true;
|
*truncate = true;
|
||||||
break;
|
break;
|
||||||
case OPEN_EXISTING:
|
case OPEN_EXISTING:
|
||||||
@@ -4378,11 +4293,11 @@ static int convert_windows_flags_to_lfs(DWORD dwDesiredAccess,
|
|||||||
break;
|
break;
|
||||||
case OPEN_ALWAYS:
|
case OPEN_ALWAYS:
|
||||||
// Opens file if it exists, creates if it doesn't
|
// Opens file if it exists, creates if it doesn't
|
||||||
lfs_flags |= LFS_O_CREAT;
|
lfs_flags |= MOCKFS_O_CREAT;
|
||||||
break;
|
break;
|
||||||
case TRUNCATE_EXISTING:
|
case TRUNCATE_EXISTING:
|
||||||
// Opens and truncates, fails if file doesn't exist
|
// Opens and truncates, fails if file doesn't exist
|
||||||
lfs_flags |= LFS_O_TRUNC;
|
lfs_flags |= MOCKFS_O_TRUNC;
|
||||||
*truncate = true;
|
*truncate = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -4402,8 +4317,6 @@ HANDLE mock_CreateFileW(WCHAR *lpFileName,
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_CreateFileW() with no node scheduled\n");
|
abort_("Call to mock_CreateFileW() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_CreateFileW() not from Windows\n");
|
|
||||||
|
|
||||||
// lpSecurityAttributes and hTemplateFile are typically NULL
|
// lpSecurityAttributes and hTemplateFile are typically NULL
|
||||||
(void) lpSecurityAttributes;
|
(void) lpSecurityAttributes;
|
||||||
@@ -4464,8 +4377,6 @@ BOOL mock_CloseHandle(HANDLE handle)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_CloseHandle() with no node scheduled\n");
|
abort_("Call to mock_CloseHandle() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_CloseHandle() not from Windows\n");
|
|
||||||
|
|
||||||
if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
|
if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
|
||||||
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
||||||
@@ -4513,8 +4424,6 @@ BOOL mock_ReadFile(HANDLE handle, char *dst, DWORD len, DWORD *num, OVERLAPPED *
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_ReadFile() with no node scheduled\n");
|
abort_("Call to mock_ReadFile() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_ReadFile() not from Windows\n");
|
|
||||||
|
|
||||||
// We don't support overlapped (async) I/O
|
// We don't support overlapped (async) I/O
|
||||||
if (ov != NULL)
|
if (ov != NULL)
|
||||||
@@ -4562,8 +4471,6 @@ BOOL mock_WriteFile(HANDLE handle, char *src, DWORD len, DWORD *num, OVERLAPPED
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_WriteFile() with no node scheduled\n");
|
abort_("Call to mock_WriteFile() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_WriteFile() not from Windows\n");
|
|
||||||
|
|
||||||
// We don't support overlapped (async) I/O
|
// We don't support overlapped (async) I/O
|
||||||
if (ov != NULL)
|
if (ov != NULL)
|
||||||
@@ -4607,8 +4514,6 @@ DWORD mock_SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceTo
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_SetFilePointer() with no node scheduled\n");
|
abort_("Call to mock_SetFilePointer() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_SetFilePointer() not from Windows\n");
|
|
||||||
|
|
||||||
if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
|
if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
|
||||||
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
||||||
@@ -4675,8 +4580,6 @@ BOOL mock_GetFileSizeEx(HANDLE handle, LARGE_INTEGER *buf)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_GetFileSizeEx() with no node scheduled\n");
|
abort_("Call to mock_GetFileSizeEx() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_GetFileSizeEx() not from Windows\n");
|
|
||||||
|
|
||||||
if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
|
if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
|
||||||
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
||||||
@@ -4716,8 +4619,6 @@ BOOL mock_QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_QueryPerformanceCounter() with no node scheduled\n");
|
abort_("Call to mock_QueryPerformanceCounter() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_QueryPerformanceCounter() not from Windows\n");
|
|
||||||
|
|
||||||
if (lpPerformanceCount == NULL)
|
if (lpPerformanceCount == NULL)
|
||||||
return 0; // FALSE
|
return 0; // FALSE
|
||||||
@@ -4736,8 +4637,6 @@ BOOL mock_QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_QueryPerformanceFrequency() with no node scheduled\n");
|
abort_("Call to mock_QueryPerformanceFrequency() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_QueryPerformanceFrequency() not from Windows\n");
|
|
||||||
|
|
||||||
if (lpFrequency == NULL)
|
if (lpFrequency == NULL)
|
||||||
return 0; // FALSE
|
return 0; // FALSE
|
||||||
@@ -4755,8 +4654,6 @@ char *mock__fullpath(char *path, char *dst, int cap)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock__fullpath() with no node scheduled\n");
|
abort_("Call to mock__fullpath() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock__fullpath() not from Windows\n");
|
|
||||||
|
|
||||||
if (path == NULL) {
|
if (path == NULL) {
|
||||||
*host_errno_ptr(host) = EINVAL;
|
*host_errno_ptr(host) = EINVAL;
|
||||||
@@ -4864,8 +4761,6 @@ int mock__mkdir(char *path)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock__mkdir() with no node scheduled\n");
|
abort_("Call to mock__mkdir() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock__mkdir() not from Windows\n");
|
|
||||||
|
|
||||||
int ret = host_mkdir(host, path);
|
int ret = host_mkdir(host, path);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -4916,8 +4811,6 @@ HANDLE mock_FindFirstFileA(char *lpFileName, WIN32_FIND_DATAA *lpFindFileData)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_FindFirstFileA() with no node scheduled\n");
|
abort_("Call to mock_FindFirstFileA() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_FindFirstFileA() not from Windows\n");
|
|
||||||
|
|
||||||
if (lpFileName == NULL || lpFindFileData == NULL) {
|
if (lpFileName == NULL || lpFindFileData == NULL) {
|
||||||
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
|
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
|
||||||
@@ -5016,8 +4909,6 @@ BOOL mock_FindNextFileA(HANDLE hFindFile, WIN32_FIND_DATAA *lpFindFileData)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_FindNextFileA() with no node scheduled\n");
|
abort_("Call to mock_FindNextFileA() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_FindNextFileA() not from Windows\n");
|
|
||||||
|
|
||||||
if (hFindFile == INVALID_HANDLE_VALUE || hFindFile == NULL || lpFindFileData == NULL) {
|
if (hFindFile == INVALID_HANDLE_VALUE || hFindFile == NULL || lpFindFileData == NULL) {
|
||||||
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
||||||
@@ -5060,8 +4951,6 @@ BOOL mock_FindClose(HANDLE hFindFile)
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_FindClose() with no node scheduled\n");
|
abort_("Call to mock_FindClose() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_FindClose() not from Windows\n");
|
|
||||||
|
|
||||||
if (hFindFile == INVALID_HANDLE_VALUE || hFindFile == NULL) {
|
if (hFindFile == INVALID_HANDLE_VALUE || hFindFile == NULL) {
|
||||||
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
|
||||||
@@ -5094,8 +4983,6 @@ BOOL mock_MoveFileExW(WCHAR *lpExistingFileName, WCHAR *lpNewFileName, DWORD dwF
|
|||||||
if (host == NULL)
|
if (host == NULL)
|
||||||
abort_("Call to mock_MoveFileExW() with no node scheduled\n");
|
abort_("Call to mock_MoveFileExW() with no node scheduled\n");
|
||||||
|
|
||||||
if (!host_is_windows(host))
|
|
||||||
abort_("Call to mock_MoveFileExW() not from Windows\n");
|
|
||||||
|
|
||||||
// Validate parameters
|
// Validate parameters
|
||||||
if (lpExistingFileName == NULL) {
|
if (lpExistingFileName == NULL) {
|
||||||
@@ -5133,7 +5020,7 @@ BOOL mock_MoveFileExW(WCHAR *lpExistingFileName, WCHAR *lpNewFileName, DWORD dwF
|
|||||||
// We need to check this before calling host_rename
|
// We need to check this before calling host_rename
|
||||||
if (!(dwFlags & MOVEFILE_REPLACE_EXISTING)) {
|
if (!(dwFlags & MOVEFILE_REPLACE_EXISTING)) {
|
||||||
// Try to check if destination exists by attempting to open it
|
// Try to check if destination exists by attempting to open it
|
||||||
int check = host_open_file(host, newpath, LFS_O_RDONLY);
|
int check = host_open_file(host, newpath, MOCKFS_O_RDONLY);
|
||||||
if (check >= 0) {
|
if (check >= 0) {
|
||||||
// File exists, close it and return error
|
// File exists, close it and return error
|
||||||
host_close(host, check, false);
|
host_close(host, check, false);
|
||||||
@@ -5168,6 +5055,31 @@ BOOL mock_MoveFileExW(WCHAR *lpExistingFileName, WCHAR *lpNewFileName, DWORD dwF
|
|||||||
return 1; // TRUE
|
return 1; // TRUE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int mock_mkdir(char *path, int mode)
|
||||||
|
{
|
||||||
|
(void) mode;
|
||||||
|
Host *host = host___;
|
||||||
|
if (host == NULL)
|
||||||
|
abort_("Call to mock_mkdir() with no node scheduled\n");
|
||||||
|
|
||||||
|
int ret = host_mkdir(host, path);
|
||||||
|
if (ret < 0) {
|
||||||
|
switch (ret) {
|
||||||
|
case HOST_ERROR_EXIST:
|
||||||
|
*host_errno_ptr(host) = EEXIST;
|
||||||
|
return -1;
|
||||||
|
case HOST_ERROR_NOENT:
|
||||||
|
*host_errno_ptr(host) = ENOENT;
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
*host_errno_ptr(host) = EIO;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void *mock_malloc(size_t size)
|
void *mock_malloc(size_t size)
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "chunk_store.h"
|
#include "chunk_store.h"
|
||||||
#include "file_system.h"
|
#include <lib/file_system.h>
|
||||||
|
|
||||||
// Build the full path for a chunk: "base_path/HEX_HASH"
|
// Build the full path for a chunk: "base_path/HEX_HASH"
|
||||||
// SHA256 hex = 64 chars. Returns string pointing into buf.
|
// SHA256 hex = 64 chars. Returns string pointing into buf.
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "basic.h"
|
#include <lib/basic.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char base_path[256];
|
char base_path[256];
|
||||||
|
|||||||
+37
-98
@@ -6,16 +6,17 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <poll.h>
|
|
||||||
|
|
||||||
#include "tcp.h"
|
#include <lib/basic.h>
|
||||||
#include "basic.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include <toastyfs.h>
|
#include <toastyfs.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define POLL_CAPACITY (NODE_LIMIT * 2 + 4)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
STEP_IDLE,
|
STEP_IDLE,
|
||||||
STEP_STORE_CHUNK,
|
STEP_STORE_CHUNK,
|
||||||
@@ -45,7 +46,7 @@ typedef struct {
|
|||||||
|
|
||||||
struct ToastyFS {
|
struct ToastyFS {
|
||||||
|
|
||||||
TCP tcp;
|
MessageSystem msys;
|
||||||
|
|
||||||
Address server_addrs[NODE_LIMIT];
|
Address server_addrs[NODE_LIMIT];
|
||||||
int num_servers;
|
int num_servers;
|
||||||
@@ -74,8 +75,6 @@ struct ToastyFS {
|
|||||||
int put_data_len;
|
int put_data_len;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Client logging infrastructure (mirrors server node_log) ----
|
|
||||||
|
|
||||||
#define TIME_FMT "%7.3fs"
|
#define TIME_FMT "%7.3fs"
|
||||||
#define TIME_VAL(t) ((double)(t) / 1000000000.0)
|
#define TIME_VAL(t) ((double)(t) / 1000000000.0)
|
||||||
|
|
||||||
@@ -147,7 +146,7 @@ ToastyFS *toastyfs_init(uint64_t client_id, char **addrs, int num_addrs)
|
|||||||
tfs->num_servers = num_addrs;
|
tfs->num_servers = num_addrs;
|
||||||
addr_sort(tfs->server_addrs, tfs->num_servers);
|
addr_sort(tfs->server_addrs, tfs->num_servers);
|
||||||
|
|
||||||
if (tcp_context_init(&tfs->tcp) < 0) {
|
if (message_system_init(&tfs->msys, tfs->server_addrs, num_addrs) < 0) {
|
||||||
free(tfs);
|
free(tfs);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -161,7 +160,7 @@ ToastyFS *toastyfs_init(uint64_t client_id, char **addrs, int num_addrs)
|
|||||||
|
|
||||||
void toastyfs_free(ToastyFS *tfs)
|
void toastyfs_free(ToastyFS *tfs)
|
||||||
{
|
{
|
||||||
tcp_context_free(&tfs->tcp);
|
message_system_free(&tfs->msys);
|
||||||
free(tfs->put_data);
|
free(tfs->put_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,40 +202,6 @@ static int leader_idx(ToastyFS *tfs)
|
|||||||
return tfs->view_number % tfs->num_servers;
|
return tfs->view_number % tfs->num_servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_message_to_server(ToastyFS *tfs, int server_idx, MessageHeader *msg)
|
|
||||||
{
|
|
||||||
ByteQueue *output;
|
|
||||||
int conn_idx = tcp_index_from_tag(&tfs->tcp, server_idx);
|
|
||||||
if (conn_idx < 0) {
|
|
||||||
if (tcp_connect(&tfs->tcp, tfs->server_addrs[server_idx], server_idx, &output) < 0)
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
output = tcp_output_buffer(&tfs->tcp, conn_idx);
|
|
||||||
if (output == NULL)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte_queue_write(output, msg, msg->length);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void send_message_to_server_ex(ToastyFS *tfs, int server_idx,
|
|
||||||
MessageHeader *msg, void *extra, int extra_len)
|
|
||||||
{
|
|
||||||
ByteQueue *output;
|
|
||||||
int conn_idx = tcp_index_from_tag(&tfs->tcp, server_idx);
|
|
||||||
if (conn_idx < 0) {
|
|
||||||
if (tcp_connect(&tfs->tcp, tfs->server_addrs[server_idx], server_idx, &output) < 0)
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
output = tcp_output_buffer(&tfs->tcp, conn_idx);
|
|
||||||
if (output == NULL)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte_queue_write(output, msg, msg->length - extra_len);
|
|
||||||
byte_queue_write(output, extra, extra_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int begin_transfers(ToastyFS *tfs)
|
static int begin_transfers(ToastyFS *tfs)
|
||||||
{
|
{
|
||||||
// Count started transfers
|
// Count started transfers
|
||||||
@@ -264,7 +229,7 @@ static int begin_transfers(ToastyFS *tfs)
|
|||||||
.hash = tfs->transfers[i].hash,
|
.hash = tfs->transfers[i].hash,
|
||||||
.size = tfs->transfers[i].size,
|
.size = tfs->transfers[i].size,
|
||||||
};
|
};
|
||||||
send_message_to_server_ex(tfs, tfs->transfers[i].location,
|
send_message_ex(&tfs->msys, tfs->transfers[i].location,
|
||||||
&msg.base, tfs->transfers[i].data, tfs->transfers[i].size);
|
&msg.base, tfs->transfers[i].data, tfs->transfers[i].size);
|
||||||
} else {
|
} else {
|
||||||
FetchChunkMessage msg = {
|
FetchChunkMessage msg = {
|
||||||
@@ -276,7 +241,7 @@ static int begin_transfers(ToastyFS *tfs)
|
|||||||
.hash = tfs->transfers[i].hash,
|
.hash = tfs->transfers[i].hash,
|
||||||
.sender_idx = -1, // Client (not a peer server)
|
.sender_idx = -1, // Client (not a peer server)
|
||||||
};
|
};
|
||||||
send_message_to_server(tfs, tfs->transfers[i].location, &msg.base);
|
send_message(&tfs->msys, tfs->transfers[i].location, &msg.base);
|
||||||
}
|
}
|
||||||
tfs->transfers[i].state = TRANSFER_STARTED;
|
tfs->transfers[i].state = TRANSFER_STARTED;
|
||||||
|
|
||||||
@@ -340,7 +305,7 @@ static void replay_request(ToastyFS *tfs)
|
|||||||
msg.oper.chunks[i].hash = tfs->chunks[i];
|
msg.oper.chunks[i].hash = tfs->chunks[i];
|
||||||
msg.oper.chunks[i].size = tfs->chunk_sizes[i];
|
msg.oper.chunks[i].size = tfs->chunk_sizes[i];
|
||||||
}
|
}
|
||||||
send_message_to_server(tfs, leader_idx(tfs), &msg.base);
|
send_message(&tfs->msys, leader_idx(tfs), &msg.base);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STEP_DELETE:
|
case STEP_DELETE:
|
||||||
@@ -359,7 +324,7 @@ static void replay_request(ToastyFS *tfs)
|
|||||||
};
|
};
|
||||||
memcpy(msg.oper.bucket, tfs->bucket, META_BUCKET_MAX);
|
memcpy(msg.oper.bucket, tfs->bucket, META_BUCKET_MAX);
|
||||||
memcpy(msg.oper.key, tfs->key, META_KEY_MAX);
|
memcpy(msg.oper.key, tfs->key, META_KEY_MAX);
|
||||||
send_message_to_server(tfs, leader_idx(tfs), &msg.base);
|
send_message(&tfs->msys, leader_idx(tfs), &msg.base);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STEP_GET:
|
case STEP_GET:
|
||||||
@@ -373,7 +338,7 @@ static void replay_request(ToastyFS *tfs)
|
|||||||
};
|
};
|
||||||
memcpy(msg.bucket, tfs->bucket, META_BUCKET_MAX);
|
memcpy(msg.bucket, tfs->bucket, META_BUCKET_MAX);
|
||||||
memcpy(msg.key, tfs->key, META_KEY_MAX);
|
memcpy(msg.key, tfs->key, META_KEY_MAX);
|
||||||
send_message_to_server(tfs, leader_idx(tfs), &msg.base);
|
send_message(&tfs->msys, leader_idx(tfs), &msg.base);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -381,11 +346,8 @@ static void replay_request(ToastyFS *tfs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int process_message(ToastyFS *tfs,
|
static int process_message(ToastyFS *tfs, uint16_t type, ByteView msg)
|
||||||
int conn_idx, uint8_t type, ByteView msg)
|
|
||||||
{
|
{
|
||||||
(void) conn_idx;
|
|
||||||
|
|
||||||
switch (tfs->step) {
|
switch (tfs->step) {
|
||||||
case STEP_FETCH_CHUNK:
|
case STEP_FETCH_CHUNK:
|
||||||
{
|
{
|
||||||
@@ -477,7 +439,7 @@ static int process_message(ToastyFS *tfs,
|
|||||||
msg.oper.chunks[i].hash = tfs->chunks[i];
|
msg.oper.chunks[i].hash = tfs->chunks[i];
|
||||||
msg.oper.chunks[i].size = tfs->chunk_sizes[i];
|
msg.oper.chunks[i].size = tfs->chunk_sizes[i];
|
||||||
}
|
}
|
||||||
send_message_to_server(tfs, leader_idx(tfs), &msg.base);
|
send_message(&tfs->msys, leader_idx(tfs), &msg.base);
|
||||||
tfs->step = STEP_COMMIT;
|
tfs->step = STEP_COMMIT;
|
||||||
client_log(tfs, "SEND COMMIT_PUT", "key=%s chunks=%d req=%lu",
|
client_log(tfs, "SEND COMMIT_PUT", "key=%s chunks=%d req=%lu",
|
||||||
tfs->key, tfs->num_chunks, tfs->request_id);
|
tfs->key, tfs->num_chunks, tfs->request_id);
|
||||||
@@ -513,8 +475,8 @@ static int process_message(ToastyFS *tfs,
|
|||||||
|
|
||||||
} else if (type == MESSAGE_TYPE_REPLY) {
|
} else if (type == MESSAGE_TYPE_REPLY) {
|
||||||
|
|
||||||
ReplyMessage reply;
|
VsrReplyMessage reply;
|
||||||
if (msg.len != sizeof(ReplyMessage))
|
if (msg.len != sizeof(VsrReplyMessage))
|
||||||
return -1;
|
return -1;
|
||||||
memcpy(&reply, msg.ptr, sizeof(reply));
|
memcpy(&reply, msg.ptr, sizeof(reply));
|
||||||
|
|
||||||
@@ -564,8 +526,8 @@ static int process_message(ToastyFS *tfs,
|
|||||||
|
|
||||||
} else if (type == MESSAGE_TYPE_REPLY) {
|
} else if (type == MESSAGE_TYPE_REPLY) {
|
||||||
|
|
||||||
ReplyMessage reply;
|
VsrReplyMessage reply;
|
||||||
if (msg.len != sizeof(ReplyMessage))
|
if (msg.len != sizeof(VsrReplyMessage))
|
||||||
return -1;
|
return -1;
|
||||||
memcpy(&reply, msg.ptr, sizeof(reply));
|
memcpy(&reply, msg.ptr, sizeof(reply));
|
||||||
|
|
||||||
@@ -675,44 +637,17 @@ static int process_message(ToastyFS *tfs,
|
|||||||
|
|
||||||
void toastyfs_process_events(ToastyFS *tfs, void **ctxs, struct pollfd *pdata, int pnum)
|
void toastyfs_process_events(ToastyFS *tfs, void **ctxs, struct pollfd *pdata, int pnum)
|
||||||
{
|
{
|
||||||
Event events[TCP_EVENT_CAPACITY];
|
message_system_process_events(&tfs->msys, ctxs, pdata, pnum);
|
||||||
int num_events = tcp_translate_events(&tfs->tcp, events, ctxs, pdata, pnum);
|
|
||||||
|
|
||||||
for (int i = 0; i < num_events; i++) {
|
void *raw;
|
||||||
|
while ((raw = get_next_message(&tfs->msys)) != NULL) {
|
||||||
if (events[i].type == EVENT_DISCONNECT) {
|
Message *header = (Message *)raw;
|
||||||
int conn_idx = events[i].conn_idx;
|
ByteView msg_view = { .ptr = raw, .len = header->length };
|
||||||
client_log(tfs, "DISCONNECT", "conn=%d", conn_idx);
|
process_message(tfs, header->type, msg_view);
|
||||||
tcp_close(&tfs->tcp, conn_idx);
|
consume_message(&tfs->msys, raw);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events[i].type != EVENT_MESSAGE)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int conn_idx = events[i].conn_idx;
|
|
||||||
for (;;) {
|
|
||||||
ByteView msg;
|
|
||||||
uint16_t msg_type;
|
|
||||||
int ret = tcp_next_message(&tfs->tcp, conn_idx, &msg, &msg_type);
|
|
||||||
if (ret == 0)
|
|
||||||
break;
|
|
||||||
if (ret < 0) {
|
|
||||||
tcp_close(&tfs->tcp, conn_idx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = process_message(tfs, conn_idx, msg_type, msg);
|
|
||||||
if (ret < 0) {
|
|
||||||
tcp_close(&tfs->tcp, conn_idx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
tcp_consume_message(&tfs->tcp, conn_idx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for operation timeout — retry the current operation if the
|
// Check for operation timeout -- retry the current operation if the
|
||||||
// deadline has passed (handles initial sends that were dropped because
|
// deadline has passed (handles initial sends that were dropped because
|
||||||
// the TCP connection wasn't established yet, and unresponsive servers).
|
// the TCP connection wasn't established yet, and unresponsive servers).
|
||||||
if (tfs->step != STEP_IDLE
|
if (tfs->step != STEP_IDLE
|
||||||
@@ -758,9 +693,9 @@ int toastyfs_register_events(ToastyFS *tfs, void **ctxs, struct pollfd *pdata, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
*timeout = deadline_to_timeout(deadline, now);
|
*timeout = deadline_to_timeout(deadline, now);
|
||||||
if (pcap < TCP_POLL_CAPACITY)
|
if (pcap < POLL_CAPACITY)
|
||||||
return -1;
|
return -1;
|
||||||
return tcp_register_events(&tfs->tcp, ctxs, pdata);
|
return message_system_register_events(&tfs->msys, ctxs, pdata, pcap);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -866,7 +801,7 @@ int toastyfs_async_get(ToastyFS *tfs, char *key, int key_len)
|
|||||||
tfs->step_time = get_current_time(); // TODO: Handle INVALID_TIME error
|
tfs->step_time = get_current_time(); // TODO: Handle INVALID_TIME error
|
||||||
tfs->step = STEP_GET;
|
tfs->step = STEP_GET;
|
||||||
client_log(tfs, "ASYNC GET", "key=%s leader=%d", tfs->key, leader_idx(tfs));
|
client_log(tfs, "ASYNC GET", "key=%s leader=%d", tfs->key, leader_idx(tfs));
|
||||||
send_message_to_server(tfs, leader_idx(tfs), &msg.base);
|
send_message(&tfs->msys, leader_idx(tfs), &msg.base);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,7 +835,7 @@ int toastyfs_async_delete(ToastyFS *tfs, char *key, int key_len)
|
|||||||
tfs->step_time = get_current_time(); // TODO: Handle INVALID_TIME error
|
tfs->step_time = get_current_time(); // TODO: Handle INVALID_TIME error
|
||||||
tfs->step = STEP_DELETE;
|
tfs->step = STEP_DELETE;
|
||||||
client_log(tfs, "ASYNC DELETE", "key=%s req=%lu leader=%d", tfs->key, tfs->request_id, leader_idx(tfs));
|
client_log(tfs, "ASYNC DELETE", "key=%s req=%lu leader=%d", tfs->key, tfs->request_id, leader_idx(tfs));
|
||||||
send_message_to_server(tfs, leader_idx(tfs), &msg.base);
|
send_message(&tfs->msys, leader_idx(tfs), &msg.base);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1073,14 +1008,18 @@ ToastyFS_Result toastyfs_get_result(ToastyFS *tfs)
|
|||||||
static int wait_until_result(ToastyFS *tfs, ToastyFS_Result *res)
|
static int wait_until_result(ToastyFS *tfs, ToastyFS_Result *res)
|
||||||
{
|
{
|
||||||
for (;;) {
|
for (;;) {
|
||||||
void *ctxs[TCP_POLL_CAPACITY];
|
void *ctxs[POLL_CAPACITY];
|
||||||
struct pollfd arr[TCP_POLL_CAPACITY];
|
struct pollfd arr[POLL_CAPACITY];
|
||||||
int poll_timeout;
|
int poll_timeout;
|
||||||
int num = toastyfs_register_events(tfs, ctxs, arr, TCP_POLL_CAPACITY, &poll_timeout);
|
int num = toastyfs_register_events(tfs, ctxs, arr, POLL_CAPACITY, &poll_timeout);
|
||||||
if (num < 0)
|
if (num < 0)
|
||||||
return num;
|
return num;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSAPoll(arr, num, poll_timeout);
|
||||||
|
#else
|
||||||
poll(arr, num, poll_timeout);
|
poll(arr, num, poll_timeout);
|
||||||
|
#endif
|
||||||
|
|
||||||
toastyfs_process_events(tfs, ctxs, arr, num);
|
toastyfs_process_events(tfs, ctxs, arr, num);
|
||||||
*res = toastyfs_get_result(tfs);
|
*res = toastyfs_get_result(tfs);
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ ClientTableEntry *client_table_find(ClientTable *client_table, uint64_t client_i
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int client_table_add(ClientTable *client_table, uint64_t client_id, uint64_t request_id, int conn_tag)
|
int client_table_add(ClientTable *client_table, uint64_t client_id, uint64_t request_id, void *pending_message)
|
||||||
{
|
{
|
||||||
if (client_table->count == client_table->capacity) {
|
if (client_table->count == client_table->capacity) {
|
||||||
int n = 2 * client_table->capacity;
|
int n = 2 * client_table->capacity;
|
||||||
@@ -44,7 +44,7 @@ int client_table_add(ClientTable *client_table, uint64_t client_id, uint64_t req
|
|||||||
.client_id = client_id,
|
.client_id = client_id,
|
||||||
.last_request_id = request_id,
|
.last_request_id = request_id,
|
||||||
.pending = true,
|
.pending = true,
|
||||||
.conn_tag = conn_tag,
|
.pending_message = pending_message,
|
||||||
};
|
};
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -13,7 +13,7 @@ typedef struct {
|
|||||||
// the REQUEST. After a view change, a new leader
|
// the REQUEST. After a view change, a new leader
|
||||||
// may find stale entries with pending=false from
|
// may find stale entries with pending=false from
|
||||||
// a previous view when it was leader before.
|
// a previous view when it was leader before.
|
||||||
int conn_tag;
|
void *pending_message; // Raw message pointer for deferred reply
|
||||||
} ClientTableEntry;
|
} ClientTableEntry;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -25,7 +25,7 @@ typedef struct {
|
|||||||
void client_table_init(ClientTable *client_table);
|
void client_table_init(ClientTable *client_table);
|
||||||
void client_table_free(ClientTable *client_table);
|
void client_table_free(ClientTable *client_table);
|
||||||
ClientTableEntry *client_table_find(ClientTable *client_table, uint64_t client_id);
|
ClientTableEntry *client_table_find(ClientTable *client_table, uint64_t client_id);
|
||||||
int client_table_add(ClientTable *client_table, uint64_t client_id, uint64_t request_id, int conn_tag);
|
int client_table_add(ClientTable *client_table, uint64_t client_id, uint64_t request_id, void *pending_message);
|
||||||
int client_table_insert(ClientTable *client_table, uint64_t client_id, uint64_t request_id);
|
int client_table_insert(ClientTable *client_table, uint64_t client_id, uint64_t request_id);
|
||||||
|
|
||||||
#endif // CLIENT_TABLE_INCLUDED
|
#endif // CLIENT_TABLE_INCLUDED
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef CONFIG_INCLUDED
|
#ifndef CONFIG_INCLUDED
|
||||||
#define CONFIG_INCLUDED
|
#define CONFIG_INCLUDED
|
||||||
|
|
||||||
|
#define CEIL(a, b) (((a) + (b) - 1) / (b))
|
||||||
|
|
||||||
#define NODE_LIMIT 32
|
#define NODE_LIMIT 32
|
||||||
#define FUTURE_LIMIT 32
|
#define FUTURE_LIMIT 32
|
||||||
#define HEARTBEAT_INTERVAL_SEC 1
|
#define HEARTBEAT_INTERVAL_SEC 1
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#include "http_proxy.h"
|
||||||
|
|
||||||
|
int http_proxy_init(void *state, int argc, char **argv,
|
||||||
|
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||||
|
int *timeout)
|
||||||
|
{
|
||||||
|
HTTPProxy *proxy = state;
|
||||||
|
|
||||||
|
if (http_server_init(&proxy->http_server) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (http_server_listen_tcp(&proxy->http_server, xxx, yyy) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_proxy_tick(void *state, void **ctxs,
|
||||||
|
struct pollfd *pdata, int pcap, int *pnum, int *timeout)
|
||||||
|
{
|
||||||
|
http_server_process_events(&proxy->http_server, ctxs, pdata, *pnum);
|
||||||
|
|
||||||
|
HTTP_Request *request;
|
||||||
|
HTTP_ResponseBuilder builder;
|
||||||
|
while (http_server_next_request(&proxy->http_server, &request, &builder)) {
|
||||||
|
// TODO
|
||||||
|
http_response_builder_status(builder, 200);
|
||||||
|
http_response_builder_submit(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
*timeout = -1;
|
||||||
|
*pnum = http_server_register_events(&proxy->http_server, ctxs, pdata, pcap);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_proxy_free(void *state)
|
||||||
|
{
|
||||||
|
HTTPProxy *proxy = state;
|
||||||
|
|
||||||
|
http_server_free(&proxy->http_server);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef HTTP_PROXY_INCLUDED
|
||||||
|
#define HTTP_PROXY_INCLUDED
|
||||||
|
|
||||||
|
#include <toastyfs.h>
|
||||||
|
#include <lib/http_server.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HTTP_Server http_server;
|
||||||
|
ToastyFS *toastyfs;
|
||||||
|
} HTTPProxy;
|
||||||
|
|
||||||
|
struct pollfd;
|
||||||
|
|
||||||
|
int http_proxy_init(void *state, int argc, char **argv,
|
||||||
|
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||||
|
int *timeout);
|
||||||
|
|
||||||
|
int http_proxy_tick(void *state, void **ctxs,
|
||||||
|
struct pollfd *pdata, int pcap, int *pnum, int *timeout);
|
||||||
|
|
||||||
|
int http_proxy_free(void *state);
|
||||||
|
|
||||||
|
#endif // HTTP_PROXY_INCLUDED
|
||||||
+14
-7
@@ -9,16 +9,23 @@
|
|||||||
#include "chunk_store.h"
|
#include "chunk_store.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
// Restore real allocators (see checker/linearizability.c for precedent).
|
// Restore real allocators (see checker/linearizability.c for precedent).
|
||||||
#undef malloc
|
#undef malloc
|
||||||
#undef realloc
|
#undef realloc
|
||||||
#undef free
|
#undef free
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// Forward declarations for quakey host context functions
|
||||||
|
// (we can't include quakey.h because it would re-mock malloc/realloc/free)
|
||||||
|
void quakey_enter_host(unsigned long long node);
|
||||||
|
void quakey_leave_host(void);
|
||||||
|
|
||||||
// These helpers are static in node.c; duplicated here for the checker.
|
// These helpers are static in node.c; duplicated here for the checker.
|
||||||
|
|
||||||
static int self_idx(ServerState *state)
|
static int self_idx(Server *state)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < state->num_nodes; i++)
|
for (int i = 0; i < state->num_nodes; i++)
|
||||||
if (addr_eql(state->node_addrs[i], state->self_addr))
|
if (addr_eql(state->node_addrs[i], state->self_addr))
|
||||||
@@ -26,12 +33,12 @@ static int self_idx(ServerState *state)
|
|||||||
UNREACHABLE;
|
UNREACHABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int leader_idx(ServerState *state)
|
static int leader_idx(Server *state)
|
||||||
{
|
{
|
||||||
return state->view_number % state->num_nodes;
|
return state->view_number % state->num_nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_leader(ServerState *state)
|
static bool is_leader(Server *state)
|
||||||
{
|
{
|
||||||
if (state->status == STATUS_RECOVERY)
|
if (state->status == STATUS_RECOVERY)
|
||||||
return false;
|
return false;
|
||||||
@@ -72,7 +79,7 @@ void invariant_checker_free(InvariantChecker *ic)
|
|||||||
free(ic->shadow_log);
|
free(ic->shadow_log);
|
||||||
}
|
}
|
||||||
|
|
||||||
void invariant_checker_run(InvariantChecker *ic, ServerState **nodes, int num_nodes,
|
void invariant_checker_run(InvariantChecker *ic, Server **nodes, int num_nodes,
|
||||||
unsigned long long *node_handles)
|
unsigned long long *node_handles)
|
||||||
{
|
{
|
||||||
int min_commit = -1;
|
int min_commit = -1;
|
||||||
@@ -90,7 +97,7 @@ void invariant_checker_run(InvariantChecker *ic, ServerState **nodes, int num_no
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < num_nodes; i++) {
|
for (int i = 0; i < num_nodes; i++) {
|
||||||
ServerState *n = nodes[i];
|
Server *n = nodes[i];
|
||||||
if (n == NULL || n->status == STATUS_RECOVERY)
|
if (n == NULL || n->status == STATUS_RECOVERY)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -158,7 +165,7 @@ void invariant_checker_run(InvariantChecker *ic, ServerState **nodes, int num_no
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < num_nodes; i++) {
|
for (int i = 0; i < num_nodes; i++) {
|
||||||
ServerState *s = nodes[i];
|
Server *s = nodes[i];
|
||||||
if (s == NULL)
|
if (s == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -297,7 +304,7 @@ void invariant_checker_run(InvariantChecker *ic, ServerState **nodes, int num_no
|
|||||||
// Phase 2: Append newly committed entries to the shadow log.
|
// Phase 2: Append newly committed entries to the shadow log.
|
||||||
if (source_node_idx >= 0 && observed_max_commit > ic->shadow_count) {
|
if (source_node_idx >= 0 && observed_max_commit > ic->shadow_count) {
|
||||||
|
|
||||||
ServerState *source = nodes[source_node_idx];
|
Server *source = nodes[source_node_idx];
|
||||||
assert(source->log.count >= observed_max_commit);
|
assert(source->log.count >= observed_max_commit);
|
||||||
|
|
||||||
for (int k = ic->shadow_count; k < observed_max_commit; k++) {
|
for (int k = ic->shadow_count; k < observed_max_commit; k++) {
|
||||||
|
|||||||
+17
-13
@@ -52,7 +52,6 @@ int main(int argc, char **argv)
|
|||||||
.addrs = (char*[]) { "127.0.0.2" },
|
.addrs = (char*[]) { "127.0.0.2" },
|
||||||
.num_addrs = 1,
|
.num_addrs = 1,
|
||||||
.disk_size = 10<<20,
|
.disk_size = 10<<20,
|
||||||
.platform = QUAKEY_LINUX,
|
|
||||||
};
|
};
|
||||||
(void) quakey_spawn(quakey, config, "cli --server 127.0.0.4:8080 --server 127.0.0.5:8080 --server 127.0.0.6:8080");
|
(void) quakey_spawn(quakey, config, "cli --server 127.0.0.4:8080 --server 127.0.0.5:8080 --server 127.0.0.6:8080");
|
||||||
}
|
}
|
||||||
@@ -68,7 +67,6 @@ int main(int argc, char **argv)
|
|||||||
.addrs = (char*[]) { "127.0.0.3" },
|
.addrs = (char*[]) { "127.0.0.3" },
|
||||||
.num_addrs = 1,
|
.num_addrs = 1,
|
||||||
.disk_size = 10<<20,
|
.disk_size = 10<<20,
|
||||||
.platform = QUAKEY_LINUX,
|
|
||||||
};
|
};
|
||||||
(void) quakey_spawn(quakey, config, "cli --server 127.0.0.4:8080 --server 127.0.0.5:8080 --server 127.0.0.6:8080");
|
(void) quakey_spawn(quakey, config, "cli --server 127.0.0.4:8080 --server 127.0.0.5:8080 --server 127.0.0.6:8080");
|
||||||
}
|
}
|
||||||
@@ -84,7 +82,6 @@ int main(int argc, char **argv)
|
|||||||
.addrs = (char*[]) { "127.0.0.7" },
|
.addrs = (char*[]) { "127.0.0.7" },
|
||||||
.num_addrs = 1,
|
.num_addrs = 1,
|
||||||
.disk_size = 10<<20,
|
.disk_size = 10<<20,
|
||||||
.platform = QUAKEY_LINUX,
|
|
||||||
};
|
};
|
||||||
(void) quakey_spawn(quakey, config, "cli --server 127.0.0.4:8080 --server 127.0.0.5:8080 --server 127.0.0.6:8080");
|
(void) quakey_spawn(quakey, config, "cli --server 127.0.0.4:8080 --server 127.0.0.5:8080 --server 127.0.0.6:8080");
|
||||||
}
|
}
|
||||||
@@ -93,14 +90,13 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
QuakeySpawn config = {
|
QuakeySpawn config = {
|
||||||
.name = "server1",
|
.name = "server1",
|
||||||
.state_size = sizeof(ServerState),
|
.state_size = sizeof(Server),
|
||||||
.init_func = server_init,
|
.init_func = server_init,
|
||||||
.tick_func = server_tick,
|
.tick_func = server_tick,
|
||||||
.free_func = server_free,
|
.free_func = server_free,
|
||||||
.addrs = (char*[]) { "127.0.0.4" },
|
.addrs = (char*[]) { "127.0.0.4" },
|
||||||
.num_addrs = 1,
|
.num_addrs = 1,
|
||||||
.disk_size = 10<<20,
|
.disk_size = 10<<20,
|
||||||
.platform = QUAKEY_LINUX,
|
|
||||||
};
|
};
|
||||||
node_1 = quakey_spawn(quakey, config, "nd --addr 127.0.0.4:8080 --peer 127.0.0.5:8080 --peer 127.0.0.6:8080");
|
node_1 = quakey_spawn(quakey, config, "nd --addr 127.0.0.4:8080 --peer 127.0.0.5:8080 --peer 127.0.0.6:8080");
|
||||||
}
|
}
|
||||||
@@ -109,14 +105,13 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
QuakeySpawn config = {
|
QuakeySpawn config = {
|
||||||
.name = "server2",
|
.name = "server2",
|
||||||
.state_size = sizeof(ServerState),
|
.state_size = sizeof(Server),
|
||||||
.init_func = server_init,
|
.init_func = server_init,
|
||||||
.tick_func = server_tick,
|
.tick_func = server_tick,
|
||||||
.free_func = server_free,
|
.free_func = server_free,
|
||||||
.addrs = (char*[]) { "127.0.0.5" },
|
.addrs = (char*[]) { "127.0.0.5" },
|
||||||
.num_addrs = 1,
|
.num_addrs = 1,
|
||||||
.disk_size = 10<<20,
|
.disk_size = 10<<20,
|
||||||
.platform = QUAKEY_LINUX,
|
|
||||||
};
|
};
|
||||||
node_2 = quakey_spawn(quakey, config, "nd --peer 127.0.0.4:8080 --addr 127.0.0.5:8080 --peer 127.0.0.6:8080");
|
node_2 = quakey_spawn(quakey, config, "nd --peer 127.0.0.4:8080 --addr 127.0.0.5:8080 --peer 127.0.0.6:8080");
|
||||||
}
|
}
|
||||||
@@ -125,14 +120,13 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
QuakeySpawn config = {
|
QuakeySpawn config = {
|
||||||
.name = "server3",
|
.name = "server3",
|
||||||
.state_size = sizeof(ServerState),
|
.state_size = sizeof(Server),
|
||||||
.init_func = server_init,
|
.init_func = server_init,
|
||||||
.tick_func = server_tick,
|
.tick_func = server_tick,
|
||||||
.free_func = server_free,
|
.free_func = server_free,
|
||||||
.addrs = (char*[]) { "127.0.0.6" },
|
.addrs = (char*[]) { "127.0.0.6" },
|
||||||
.num_addrs = 1,
|
.num_addrs = 1,
|
||||||
.disk_size = 10<<20,
|
.disk_size = 10<<20,
|
||||||
.platform = QUAKEY_LINUX,
|
|
||||||
};
|
};
|
||||||
node_3 = quakey_spawn(quakey, config, "nd --peer 127.0.0.4:8080 --peer 127.0.0.5:8080 --addr 127.0.0.6:8080");
|
node_3 = quakey_spawn(quakey, config, "nd --peer 127.0.0.4:8080 --peer 127.0.0.5:8080 --addr 127.0.0.6:8080");
|
||||||
}
|
}
|
||||||
@@ -151,7 +145,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
quakey_schedule_one(quakey);
|
quakey_schedule_one(quakey);
|
||||||
|
|
||||||
ServerState *arr[] = {
|
Server *arr[] = {
|
||||||
quakey_node_state(node_1),
|
quakey_node_state(node_1),
|
||||||
quakey_node_state(node_2),
|
quakey_node_state(node_2),
|
||||||
quakey_node_state(node_3),
|
quakey_node_state(node_3),
|
||||||
@@ -182,7 +176,11 @@ int main(int argc, char **argv)
|
|||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
#ifdef MAIN_CLIENT
|
#ifdef MAIN_CLIENT
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#else
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
#endif
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -246,8 +244,14 @@ int main(int argc, char **argv)
|
|||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
#ifdef MAIN_SERVER
|
#ifdef MAIN_SERVER
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#else
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
#define POLL_CAPACITY 1024
|
#define POLL_CAPACITY 1024
|
||||||
@@ -256,11 +260,11 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
// ServerState is ~40 MB (MetaStore holds 4096 ObjectMeta entries),
|
// Server is ~40 MB (MetaStore holds 4096 ObjectMeta entries),
|
||||||
// which exceeds the default stack size. Heap-allocate it.
|
// which exceeds the default stack size. Heap-allocate it.
|
||||||
ServerState *state = malloc(sizeof(ServerState));
|
Server *state = malloc(sizeof(Server));
|
||||||
if (state == NULL) {
|
if (state == NULL) {
|
||||||
fprintf(stderr, "Failed to allocate ServerState\n");
|
fprintf(stderr, "Failed to allocate Server\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
|
||||||
#define QUAKEY_ENABLE_MOCKS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <quakey.h>
|
|
||||||
|
|
||||||
#include "message.h"
|
|
||||||
|
|
||||||
bool binary_read(BinaryReader *reader, void *dst, int len)
|
|
||||||
{
|
|
||||||
if (reader->len - reader->cur < len)
|
|
||||||
return false;
|
|
||||||
if (dst)
|
|
||||||
memcpy(dst, reader->src + reader->cur, len);
|
|
||||||
reader->cur += len;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void message_writer_init(MessageWriter *writer, ByteQueue *output, uint16_t type)
|
|
||||||
{
|
|
||||||
uint16_t version = MESSAGE_VERSION;
|
|
||||||
uint32_t dummy = 0; // Dummy value
|
|
||||||
writer->output = output;
|
|
||||||
writer->start = byte_queue_offset(output);
|
|
||||||
byte_queue_write(output, &version, sizeof(version));
|
|
||||||
byte_queue_write(output, &type, sizeof(type));
|
|
||||||
writer->patch = byte_queue_offset(output);
|
|
||||||
byte_queue_write(output, &dummy, sizeof(dummy));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool message_writer_free(MessageWriter *writer)
|
|
||||||
{
|
|
||||||
uint32_t length = byte_queue_size_from_offset(writer->output, writer->start);
|
|
||||||
byte_queue_patch(writer->output, writer->patch, &length, sizeof(length));
|
|
||||||
if (byte_queue_error(writer->output)) // TODO: is it possible to restore the state of the queue to before the failure?
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void message_write(MessageWriter *writer, void *mem, int len)
|
|
||||||
{
|
|
||||||
byte_queue_write(writer->output, mem, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void message_write_u8(MessageWriter *writer, uint8_t value)
|
|
||||||
{
|
|
||||||
message_write(writer, &value, (int) sizeof(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void message_write_u32(MessageWriter *writer, uint32_t value)
|
|
||||||
{
|
|
||||||
message_write(writer, &value, (int) sizeof(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void message_write_hash(MessageWriter *writer, SHA256 value)
|
|
||||||
{
|
|
||||||
message_write(writer, &value, (int) sizeof(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
int message_peek(ByteView msg, uint16_t *type, uint32_t *len)
|
|
||||||
{
|
|
||||||
if (msg.len < (int) sizeof(MessageHeader))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
MessageHeader header;
|
|
||||||
memcpy(&header, msg.ptr, sizeof(header));
|
|
||||||
|
|
||||||
// (We ignore endianess for now)
|
|
||||||
|
|
||||||
if (header.version != MESSAGE_VERSION)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (header.length > msg.len)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (type) *type = header.type;
|
|
||||||
if (len) *len = header.length;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#ifndef MESSAGE_INCLUDED
|
|
||||||
#define MESSAGE_INCLUDED
|
|
||||||
|
|
||||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
|
||||||
#define QUAKEY_ENABLE_MOCKS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <quakey.h>
|
|
||||||
|
|
||||||
#include "basic.h"
|
|
||||||
#include "byte_queue.h"
|
|
||||||
|
|
||||||
#define MESSAGE_VERSION 1
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t *src;
|
|
||||||
int len;
|
|
||||||
int cur;
|
|
||||||
} BinaryReader;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint16_t version;
|
|
||||||
uint16_t type;
|
|
||||||
uint32_t length;
|
|
||||||
} MessageHeader;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
ByteQueue *output;
|
|
||||||
ByteQueueOffset start;
|
|
||||||
ByteQueueOffset patch;
|
|
||||||
} MessageWriter;
|
|
||||||
|
|
||||||
bool binary_read(BinaryReader *reader, void *dst, int len);
|
|
||||||
|
|
||||||
void message_writer_init(MessageWriter *writer, ByteQueue *output, uint16_t type);
|
|
||||||
bool message_writer_free(MessageWriter *writer);
|
|
||||||
void message_write(MessageWriter *writer, void *mem, int len);
|
|
||||||
void message_write_u8(MessageWriter *writer, uint8_t value);
|
|
||||||
void message_write_u32(MessageWriter *writer, uint32_t value);
|
|
||||||
void message_write_hash(MessageWriter *writer, SHA256 value);
|
|
||||||
|
|
||||||
int message_peek(ByteView msg, uint16_t *type, uint32_t *len);
|
|
||||||
void message_dump(FILE *stream, ByteView msg);
|
|
||||||
|
|
||||||
#endif // MESSAGE_INCLUDED
|
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "basic.h"
|
#include <lib/basic.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
SHA256 hash;
|
SHA256 hash;
|
||||||
|
|||||||
+1
-4
@@ -82,10 +82,7 @@ int random_client_init(void *state_, int argc, char **argv,
|
|||||||
return -1;
|
return -1;
|
||||||
state->started = false;
|
state->started = false;
|
||||||
|
|
||||||
if (pcap < TCP_POLL_CAPACITY) {
|
(void) pcap;
|
||||||
fprintf(stderr, "Random client :: Not enough poll capacity\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*pnum = toastyfs_register_events(state->tfs, ctxs, pdata, pcap, timeout);
|
*pnum = toastyfs_register_events(state->tfs, ctxs, pdata, pcap, timeout);
|
||||||
*timeout = 0; // Ensure first tick fires immediately to start an operation
|
*timeout = 0; // Ensure first tick fires immediately to start an operation
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+2
-2
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
#include <toastyfs.h>
|
#include <toastyfs.h>
|
||||||
|
|
||||||
#include "tcp.h"
|
#include <lib/tcp.h>
|
||||||
#include "basic.h"
|
#include <lib/basic.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
|
|||||||
+252
-595
File diff suppressed because it is too large
Load Diff
+62
-62
@@ -1,15 +1,16 @@
|
|||||||
#ifndef NODE_INCLUDED
|
#ifndef SERVER_INCLUDED
|
||||||
#define NODE_INCLUDED
|
#define SERVER_INCLUDED
|
||||||
|
|
||||||
|
#include <lib/message.h>
|
||||||
|
|
||||||
#include "tcp.h"
|
|
||||||
#include "basic.h"
|
|
||||||
#include "message.h"
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
#include "chunk_store.h"
|
#include "chunk_store.h"
|
||||||
#include "client_table.h"
|
#include "client_table.h"
|
||||||
|
|
||||||
|
#define MESSAGE_VERSION 1
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
|
||||||
// Normal Protocol
|
// Normal Protocol
|
||||||
@@ -48,107 +49,107 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
MetaOper oper;
|
MetaOper oper;
|
||||||
uint64_t client_id;
|
uint64_t client_id;
|
||||||
uint64_t request_id;
|
uint64_t request_id;
|
||||||
} RequestMessage;
|
} RequestMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
MetaOper oper;
|
MetaOper oper;
|
||||||
int sender_idx;
|
int sender_idx;
|
||||||
int log_index;
|
int log_index;
|
||||||
int commit_index;
|
int commit_index;
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
uint64_t client_id;
|
uint64_t client_id;
|
||||||
uint64_t request_id;
|
uint64_t request_id;
|
||||||
} PrepareMessage;
|
} PrepareMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
int sender_idx;
|
int sender_idx;
|
||||||
int log_index;
|
int log_index;
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
} PrepareOKMessage;
|
} PrepareOKMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
int sender_idx;
|
int sender_idx;
|
||||||
int commit_index;
|
int commit_index;
|
||||||
} CommitMessage;
|
} CommitMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
bool rejected;
|
bool rejected;
|
||||||
MetaResult result;
|
MetaResult result;
|
||||||
uint64_t request_id;
|
uint64_t request_id;
|
||||||
} ReplyMessage;
|
} VsrReplyMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
int sender_idx;
|
int sender_idx;
|
||||||
} BeginViewChangeMessage;
|
} BeginViewChangeMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
uint64_t view_number; // The new view number
|
uint64_t view_number; // The new view number
|
||||||
uint64_t old_view_number; // Last view number when replica was in normal status
|
uint64_t old_view_number; // Last view number when replica was in normal status
|
||||||
int op_number; // Number of entries in the log
|
int op_number; // Number of entries in the log
|
||||||
int commit_index;
|
int commit_index;
|
||||||
int sender_idx;
|
int sender_idx;
|
||||||
// Followed by: LogEntry log[op_number]
|
// Followed by: LogEntry log[op_number]
|
||||||
} DoViewChangeMessage;
|
} DoViewChangeMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
int commit_index;
|
int commit_index;
|
||||||
int op_number; // Number of log entries that follow
|
int op_number; // Number of log entries that follow
|
||||||
// Followed by: LogEntry log[op_number]
|
// Followed by: LogEntry log[op_number]
|
||||||
} BeginViewMessage;
|
} BeginViewMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
int sender_idx;
|
int sender_idx;
|
||||||
uint64_t nonce;
|
uint64_t nonce;
|
||||||
} RecoveryMessage;
|
} RecoveryMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
int op_number;
|
int op_number;
|
||||||
uint64_t nonce;
|
uint64_t nonce;
|
||||||
int commit_index;
|
int commit_index;
|
||||||
int sender_idx;
|
int sender_idx;
|
||||||
} RecoveryResponseMessage;
|
} RecoveryResponseMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
int op_number; // Requester's current log count
|
int op_number; // Requester's current log count
|
||||||
int sender_idx;
|
int sender_idx;
|
||||||
} GetStateMessage;
|
} GetStateMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
int op_number; // Number of log entries that follow
|
int op_number; // Number of log entries that follow
|
||||||
int commit_index;
|
int commit_index;
|
||||||
int start_index; // Global log index of the first entry in the suffix
|
int start_index; // Global log index of the first entry in the suffix
|
||||||
// Followed by: LogEntry log[op_number]
|
// Followed by: LogEntry log[op_number]
|
||||||
} NewStateMessage;
|
} NewStateMessage;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
} RedirectMessage;
|
} RedirectMessage;
|
||||||
|
|
||||||
// StoreChunk: client -> any server. Carries chunk data as trailing bytes.
|
// StoreChunk: client -> any server. Carries chunk data as trailing bytes.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
SHA256 hash;
|
SHA256 hash;
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
// Followed by: uint8_t data[size]
|
// Followed by: uint8_t data[size]
|
||||||
@@ -156,21 +157,21 @@ typedef struct {
|
|||||||
|
|
||||||
// StoreChunkAck: server -> client.
|
// StoreChunkAck: server -> client.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
SHA256 hash;
|
SHA256 hash;
|
||||||
bool success;
|
bool success;
|
||||||
} StoreChunkAckMessage;
|
} StoreChunkAckMessage;
|
||||||
|
|
||||||
// FetchChunk: client/server -> server. Request a chunk by hash.
|
// FetchChunk: client/server -> server. Request a chunk by hash.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
SHA256 hash;
|
SHA256 hash;
|
||||||
int sender_idx; // -1 if from a client
|
int sender_idx; // -1 if from a client
|
||||||
} FetchChunkMessage;
|
} FetchChunkMessage;
|
||||||
|
|
||||||
// FetchChunkResponse: server -> client/server. Chunk data as trailing bytes.
|
// FetchChunkResponse: server -> client/server. Chunk data as trailing bytes.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
SHA256 hash;
|
SHA256 hash;
|
||||||
uint32_t size; // 0 if chunk not found
|
uint32_t size; // 0 if chunk not found
|
||||||
// Followed by: uint8_t data[size]
|
// Followed by: uint8_t data[size]
|
||||||
@@ -179,7 +180,7 @@ typedef struct {
|
|||||||
// CommitPut: blob client -> leader. Commits metadata after chunks uploaded.
|
// CommitPut: blob client -> leader. Commits metadata after chunks uploaded.
|
||||||
// Processed like a REQUEST (goes through VSR log).
|
// Processed like a REQUEST (goes through VSR log).
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
MetaOper oper;
|
MetaOper oper;
|
||||||
uint64_t client_id;
|
uint64_t client_id;
|
||||||
uint64_t request_id;
|
uint64_t request_id;
|
||||||
@@ -187,14 +188,14 @@ typedef struct {
|
|||||||
|
|
||||||
// GetBlob: client -> any server. Request metadata for a blob.
|
// GetBlob: client -> any server. Request metadata for a blob.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
char bucket[META_BUCKET_MAX];
|
char bucket[META_BUCKET_MAX];
|
||||||
char key[META_KEY_MAX];
|
char key[META_KEY_MAX];
|
||||||
} GetBlobMessage;
|
} GetBlobMessage;
|
||||||
|
|
||||||
// GetBlobResponse: server -> client. Returns blob metadata.
|
// GetBlobResponse: server -> client. Returns blob metadata.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MessageHeader base;
|
Message base;
|
||||||
bool found;
|
bool found;
|
||||||
uint64_t size;
|
uint64_t size;
|
||||||
SHA256 content_hash;
|
SHA256 content_hash;
|
||||||
@@ -210,7 +211,7 @@ typedef enum {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
||||||
TCP tcp;
|
MessageSystem msys;
|
||||||
|
|
||||||
Address self_addr;
|
Address self_addr;
|
||||||
Address node_addrs[NODE_LIMIT];
|
Address node_addrs[NODE_LIMIT];
|
||||||
@@ -219,10 +220,9 @@ typedef struct {
|
|||||||
Status status;
|
Status status;
|
||||||
|
|
||||||
ClientTable client_table;
|
ClientTable client_table;
|
||||||
int next_client_tag;
|
|
||||||
|
|
||||||
uint64_t view_number;
|
uint64_t view_number;
|
||||||
uint64_t last_normal_view; // Latest view where status was NORMAL
|
uint64_t last_normal_view; // Latest view where status was NORMAL
|
||||||
|
|
||||||
// These fields are used in recovery mode
|
// These fields are used in recovery mode
|
||||||
uint32_t recovery_votes;
|
uint32_t recovery_votes;
|
||||||
@@ -265,7 +265,7 @@ typedef struct {
|
|||||||
// Set at each wakeup
|
// Set at each wakeup
|
||||||
Time now;
|
Time now;
|
||||||
|
|
||||||
} ServerState;
|
} Server;
|
||||||
|
|
||||||
struct pollfd;
|
struct pollfd;
|
||||||
|
|
||||||
@@ -294,7 +294,7 @@ void invariant_checker_free(InvariantChecker *ic);
|
|||||||
|
|
||||||
// node_handles: opaque QuakeyNode handles for entering host context.
|
// node_handles: opaque QuakeyNode handles for entering host context.
|
||||||
// Pass NULL when running outside the simulation (real mode).
|
// Pass NULL when running outside the simulation (real mode).
|
||||||
void invariant_checker_run(InvariantChecker *ic, ServerState **nodes, int num_nodes,
|
void invariant_checker_run(InvariantChecker *ic, Server **nodes, int num_nodes,
|
||||||
unsigned long long *node_handles);
|
unsigned long long *node_handles);
|
||||||
|
|
||||||
#endif // NODE_INCLUDED
|
#endif // SERVER_INCLUDED
|
||||||
|
|||||||
@@ -1,504 +0,0 @@
|
|||||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
|
||||||
#define QUAKEY_ENABLE_MOCKS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <quakey.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include "tcp.h"
|
|
||||||
#include "message.h"
|
|
||||||
|
|
||||||
static int set_socket_blocking(SOCKET sock, bool value)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
u_long mode = !value;
|
|
||||||
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
|
|
||||||
return -1;
|
|
||||||
#else
|
|
||||||
int flags = fcntl(sock, F_GETFL, 0);
|
|
||||||
if (flags < 0)
|
|
||||||
return -1;
|
|
||||||
if (value) flags &= ~O_NONBLOCK;
|
|
||||||
else flags |= O_NONBLOCK;
|
|
||||||
if (fcntl(sock, F_SETFL, flags) < 0)
|
|
||||||
return -1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SOCKET create_listen_socket(Address addr)
|
|
||||||
{
|
|
||||||
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (fd == INVALID_SOCKET)
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
|
|
||||||
if (set_socket_blocking(fd, false) < 0) {
|
|
||||||
CLOSE_SOCKET(fd);
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef MAIN_SIMULATION
|
|
||||||
int reuse = 1;
|
|
||||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!addr.is_ipv4) {
|
|
||||||
assert(0); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_in bind_buf;
|
|
||||||
bind_buf.sin_family = AF_INET;
|
|
||||||
bind_buf.sin_port = htons(addr.port);
|
|
||||||
memcpy(&bind_buf.sin_addr, &addr.ipv4, sizeof(IPv4));
|
|
||||||
if (bind(fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf))) {
|
|
||||||
CLOSE_SOCKET(fd);
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
int backlog = 32;
|
|
||||||
if (listen(fd, backlog) < 0) {
|
|
||||||
CLOSE_SOCKET(fd);
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int create_socket_pair(SOCKET *a, SOCKET *b)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
||||||
if (sock == INVALID_SOCKET)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Bind to loopback address with port 0 (dynamic port assignment)
|
|
||||||
struct sockaddr_in addr;
|
|
||||||
int addr_len = sizeof(addr);
|
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sin_family = AF_INET;
|
|
||||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1
|
|
||||||
addr.sin_port = 0; // Let system choose port
|
|
||||||
|
|
||||||
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
|
||||||
closesocket(sock);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) {
|
|
||||||
closesocket(sock);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
|
||||||
closesocket(sock);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
*a = sock;
|
|
||||||
*b = sock;
|
|
||||||
|
|
||||||
// Optional: Set socket to non-blocking mode
|
|
||||||
// This prevents send() from blocking if the receive buffer is full
|
|
||||||
u_long mode = 1;
|
|
||||||
ioctlsocket(sock, FIONBIO, &mode); // TODO: does this fail?
|
|
||||||
return 0;
|
|
||||||
#else
|
|
||||||
int fds[2];
|
|
||||||
if (pipe(fds) < 0)
|
|
||||||
return -1;
|
|
||||||
*a = fds[0];
|
|
||||||
*b = fds[1];
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void close_socket_pair(SOCKET a, SOCKET b)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
closesocket(a);
|
|
||||||
(void) b;
|
|
||||||
#else
|
|
||||||
close(a);
|
|
||||||
close(b);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void conn_init(Connection *conn, SOCKET fd, bool connecting)
|
|
||||||
{
|
|
||||||
conn->fd = fd;
|
|
||||||
conn->tag = -1;
|
|
||||||
conn->connecting = connecting;
|
|
||||||
conn->closing = false;
|
|
||||||
conn->msglen = 0;
|
|
||||||
byte_queue_init(&conn->input, 1<<30);
|
|
||||||
byte_queue_init(&conn->output, 1<<30);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void conn_free(Connection *conn)
|
|
||||||
{
|
|
||||||
CLOSE_SOCKET(conn->fd);
|
|
||||||
byte_queue_free(&conn->input);
|
|
||||||
byte_queue_free(&conn->output);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int conn_events(Connection *conn)
|
|
||||||
{
|
|
||||||
int events = 0;
|
|
||||||
|
|
||||||
if (conn->connecting)
|
|
||||||
events |= POLLOUT;
|
|
||||||
else {
|
|
||||||
|
|
||||||
assert(!byte_queue_full(&conn->input));
|
|
||||||
if (!conn->closing)
|
|
||||||
events |= POLLIN;
|
|
||||||
|
|
||||||
if (!byte_queue_empty(&conn->output))
|
|
||||||
events |= POLLOUT;
|
|
||||||
}
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tcp_context_init(TCP *tcp)
|
|
||||||
{
|
|
||||||
tcp->listen_fd = INVALID_SOCKET;
|
|
||||||
tcp->num_conns = 0;
|
|
||||||
|
|
||||||
if (create_socket_pair(&tcp->wait_fd, &tcp->signal_fd) < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tcp_context_free(TCP *tcp)
|
|
||||||
{
|
|
||||||
// Free all connection byte queues without closing sockets
|
|
||||||
// (sockets are managed by the simulation and will be cleaned up separately)
|
|
||||||
for (int i = 0; i < tcp->num_conns; i++) {
|
|
||||||
byte_queue_free(&tcp->conns[i].input);
|
|
||||||
byte_queue_free(&tcp->conns[i].output);
|
|
||||||
}
|
|
||||||
tcp->num_conns = 0;
|
|
||||||
|
|
||||||
if (tcp->listen_fd != INVALID_SOCKET)
|
|
||||||
CLOSE_SOCKET(tcp->listen_fd);
|
|
||||||
|
|
||||||
close_socket_pair(tcp->wait_fd, tcp->signal_fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
int tcp_wakeup(TCP *tcp)
|
|
||||||
{
|
|
||||||
send(tcp->signal_fd, "0", 1, 0); // TODO: Handle error
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tcp_index_from_tag(TCP *tcp, int tag)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < tcp->num_conns; i++)
|
|
||||||
if (tcp->conns[i].tag == tag)
|
|
||||||
return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tcp_listen(TCP *tcp, Address addr)
|
|
||||||
{
|
|
||||||
SOCKET listen_fd = create_listen_socket(addr);
|
|
||||||
if (listen_fd == INVALID_SOCKET)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
tcp->listen_fd = listen_fd;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tcp_next_message(TCP *tcp, int conn_idx, ByteView *msg, uint16_t *type)
|
|
||||||
{
|
|
||||||
*msg = byte_queue_read_buf(&tcp->conns[conn_idx].input);
|
|
||||||
|
|
||||||
uint32_t len;
|
|
||||||
int ret = message_peek(*msg, type, &len);
|
|
||||||
|
|
||||||
// Invalid message?
|
|
||||||
if (ret < 0) {
|
|
||||||
byte_queue_read_ack(&tcp->conns[conn_idx].input, 0);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Still buffering header?
|
|
||||||
if (ret == 0) {
|
|
||||||
byte_queue_read_ack(&tcp->conns[conn_idx].input, 0);
|
|
||||||
if (byte_queue_full(&tcp->conns[conn_idx].input))
|
|
||||||
return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message received
|
|
||||||
assert(ret > 0);
|
|
||||||
msg->len = len;
|
|
||||||
tcp->conns[conn_idx].msglen = len;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tcp_consume_message(TCP *tcp, int conn_idx)
|
|
||||||
{
|
|
||||||
byte_queue_read_ack(&tcp->conns[conn_idx].input, tcp->conns[conn_idx].msglen);
|
|
||||||
tcp->conns[conn_idx].msglen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tcp_register_events(TCP *tcp, void **contexts, struct pollfd *polled)
|
|
||||||
{
|
|
||||||
int num_polled = 0;
|
|
||||||
|
|
||||||
polled[num_polled].fd = tcp->wait_fd;
|
|
||||||
polled[num_polled].events = POLLIN;
|
|
||||||
polled[num_polled].revents = 0;
|
|
||||||
contexts[num_polled] = NULL;
|
|
||||||
num_polled++;
|
|
||||||
|
|
||||||
if (tcp->listen_fd != INVALID_SOCKET && tcp->num_conns < TCP_CONNECTION_LIMIT) {
|
|
||||||
polled[num_polled].fd = tcp->listen_fd;
|
|
||||||
polled[num_polled].events = POLLIN;
|
|
||||||
polled[num_polled].revents = 0;
|
|
||||||
contexts[num_polled] = NULL;
|
|
||||||
num_polled++;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < tcp->num_conns; i++) {
|
|
||||||
int events = conn_events(&tcp->conns[i]);
|
|
||||||
if (events) {
|
|
||||||
polled[num_polled].fd = tcp->conns[i].fd;
|
|
||||||
polled[num_polled].events = events;
|
|
||||||
polled[num_polled].revents = 0;
|
|
||||||
contexts[num_polled] = &tcp->conns[i];
|
|
||||||
num_polled++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return num_polled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The "events" array must be an array of capacity TCP_EVENT_CAPACITY,
|
|
||||||
// while "contexts" and "polled" must have capacity TCP_POLL_CAPACITY.
|
|
||||||
int tcp_translate_events(TCP *tcp, Event *events, void **contexts, struct pollfd *polled, int num_polled)
|
|
||||||
{
|
|
||||||
bool removed[TCP_POLL_CAPACITY];
|
|
||||||
for (int i = 0; i < TCP_POLL_CAPACITY; i++)
|
|
||||||
removed[i] = false;
|
|
||||||
|
|
||||||
int num_events = 0;
|
|
||||||
for (int i = 1; i < num_polled; i++) {
|
|
||||||
|
|
||||||
if (polled[i].fd == tcp->wait_fd) {
|
|
||||||
|
|
||||||
if (polled[i].revents & POLLIN) {
|
|
||||||
char buf[100];
|
|
||||||
recv(tcp->wait_fd, buf, sizeof(buf), 0); // TODO: Make sure all bytes are consumed
|
|
||||||
events[num_events++] = (Event) { EVENT_WAKEUP, -1, -1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (polled[i].fd == tcp->listen_fd) {
|
|
||||||
|
|
||||||
assert(contexts[i] == NULL);
|
|
||||||
|
|
||||||
if (polled[i].revents & POLLIN) {
|
|
||||||
SOCKET new_fd = accept(tcp->listen_fd, NULL, NULL);
|
|
||||||
if (new_fd != INVALID_SOCKET) {
|
|
||||||
|
|
||||||
if (set_socket_blocking(new_fd, false) < 0)
|
|
||||||
CLOSE_SOCKET(new_fd);
|
|
||||||
else {
|
|
||||||
conn_init(&tcp->conns[tcp->num_conns++], new_fd, false);
|
|
||||||
events[num_events++] = (Event) { EVENT_CONNECT, tcp->num_conns-1, tcp->conns[tcp->num_conns-1].tag };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
removed[i] = false;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Connection *conn = contexts[i];
|
|
||||||
bool defer_close = false;
|
|
||||||
bool defer_ready = false;
|
|
||||||
|
|
||||||
if (conn->connecting) {
|
|
||||||
|
|
||||||
// Check for error conditions on the socket
|
|
||||||
if (polled[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
|
||||||
defer_close = true;
|
|
||||||
} else if (polled[i].revents & POLLOUT) {
|
|
||||||
|
|
||||||
int err = 0;
|
|
||||||
socklen_t len = sizeof(err);
|
|
||||||
if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0 || err != 0)
|
|
||||||
defer_close = true;
|
|
||||||
else {
|
|
||||||
conn->connecting = false;
|
|
||||||
events[num_events++] = (Event) { EVENT_CONNECT, conn - tcp->conns, conn->tag };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (polled[i].revents & POLLIN) {
|
|
||||||
byte_queue_write_setmincap(&conn->input, 1<<9);
|
|
||||||
ByteView buf = byte_queue_write_buf(&conn->input);
|
|
||||||
int num = recv(conn->fd, (char*) buf.ptr, buf.len, 0);
|
|
||||||
if (num == 0)
|
|
||||||
defer_close = true;
|
|
||||||
else if (num < 0) {
|
|
||||||
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN) // TODO: does Windows return these error codes or not?
|
|
||||||
defer_close = true;
|
|
||||||
num = 0;
|
|
||||||
}
|
|
||||||
byte_queue_write_ack(&conn->input, num);
|
|
||||||
ByteView msg = byte_queue_read_buf(&conn->input);
|
|
||||||
int ret = message_peek(msg, NULL, NULL);
|
|
||||||
byte_queue_read_ack(&conn->input, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
// Invalid message
|
|
||||||
defer_close = true;
|
|
||||||
} else if (ret == 0) {
|
|
||||||
// Still buffering
|
|
||||||
if (byte_queue_full(&conn->input))
|
|
||||||
defer_close = true;
|
|
||||||
} else {
|
|
||||||
// Message received
|
|
||||||
assert(ret > 0);
|
|
||||||
defer_ready = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (polled[i].revents & POLLOUT) {
|
|
||||||
ByteView buf = byte_queue_read_buf(&conn->output);
|
|
||||||
int num = send(conn->fd, (char*) buf.ptr, buf.len, 0);
|
|
||||||
if (num < 0) {
|
|
||||||
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN)
|
|
||||||
defer_close = true;
|
|
||||||
num = 0;
|
|
||||||
}
|
|
||||||
byte_queue_read_ack(&conn->output, num);
|
|
||||||
if (conn->closing && byte_queue_empty(&conn->output))
|
|
||||||
defer_close = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: byte_queue_error here?
|
|
||||||
|
|
||||||
removed[i] = defer_close;
|
|
||||||
if (0) {}
|
|
||||||
else if (defer_close) events[num_events++] = (Event) { EVENT_DISCONNECT, conn - tcp->conns, conn->tag };
|
|
||||||
else if (defer_ready) events[num_events++] = (Event) { EVENT_MESSAGE, conn - tcp->conns, conn->tag };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 1; i < num_polled; i++) {
|
|
||||||
if (removed[i]) {
|
|
||||||
Connection *conn = contexts[i];
|
|
||||||
assert(conn);
|
|
||||||
int removed_idx = conn - tcp->conns;
|
|
||||||
conn_free(conn);
|
|
||||||
int last_idx = --tcp->num_conns;
|
|
||||||
if (removed_idx != last_idx) {
|
|
||||||
*conn = tcp->conns[last_idx];
|
|
||||||
// Update event conn_idx values to reflect the swap
|
|
||||||
for (int j = 0; j < num_events; j++) {
|
|
||||||
if (events[j].conn_idx == last_idx)
|
|
||||||
events[j].conn_idx = removed_idx;
|
|
||||||
}
|
|
||||||
// Update contexts pointers for remaining iterations
|
|
||||||
for (int j = i + 1; j < num_polled; j++) {
|
|
||||||
if (contexts[j] == &tcp->conns[last_idx])
|
|
||||||
contexts[j] = conn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return num_events;
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteQueue *tcp_output_buffer(TCP *tcp, int conn_idx)
|
|
||||||
{
|
|
||||||
return &tcp->conns[conn_idx].output;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tcp_connect(TCP *tcp, Address addr, int tag, ByteQueue **output)
|
|
||||||
{
|
|
||||||
if (tcp->num_conns == TCP_CONNECTION_LIMIT)
|
|
||||||
return -1;
|
|
||||||
int conn_idx = tcp->num_conns;
|
|
||||||
|
|
||||||
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (fd == INVALID_SOCKET)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (set_socket_blocking(fd, false) < 0) {
|
|
||||||
CLOSE_SOCKET(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
if (addr.is_ipv4) {
|
|
||||||
struct sockaddr_in buf;
|
|
||||||
buf.sin_family = AF_INET;
|
|
||||||
buf.sin_port = htons(addr.port);
|
|
||||||
memcpy(&buf.sin_addr, &addr.ipv4, sizeof(IPv4));
|
|
||||||
ret = connect(fd, (struct sockaddr*) &buf, sizeof(buf));
|
|
||||||
} else {
|
|
||||||
struct sockaddr_in6 buf;
|
|
||||||
buf.sin6_family = AF_INET6;
|
|
||||||
buf.sin6_port = htons(addr.port);
|
|
||||||
memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(IPv6));
|
|
||||||
ret = connect(fd, (struct sockaddr*) &buf, sizeof(buf));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool connecting;
|
|
||||||
if (ret == 0) {
|
|
||||||
connecting = false;
|
|
||||||
} else {
|
|
||||||
if (errno != EINPROGRESS) {
|
|
||||||
CLOSE_SOCKET(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
connecting = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that this tag wasn't already used
|
|
||||||
for (int i = 0; i < tcp->num_conns; i++)
|
|
||||||
assert(tcp->conns[i].tag != tag);
|
|
||||||
|
|
||||||
conn_init(&tcp->conns[conn_idx], fd, connecting);
|
|
||||||
tcp->conns[conn_idx].tag = tag;
|
|
||||||
|
|
||||||
if (output)
|
|
||||||
*output = &tcp->conns[conn_idx].output;
|
|
||||||
|
|
||||||
tcp->num_conns++;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tcp_close(TCP *tcp, int conn_idx)
|
|
||||||
{
|
|
||||||
tcp->conns[conn_idx].closing = true;
|
|
||||||
tcp->conns[conn_idx].tag = -1; // Clear tag so new sends create a fresh connection
|
|
||||||
// TODO: if no event will be triggered, the connection will not be closed
|
|
||||||
// if the output buffer is empty, the connection should be closed here.
|
|
||||||
}
|
|
||||||
|
|
||||||
void tcp_set_tag(TCP *tcp, int conn_idx, int tag, bool unique)
|
|
||||||
{
|
|
||||||
assert(tag != -1);
|
|
||||||
|
|
||||||
if (unique) {
|
|
||||||
for (int i = 0; i < tcp->num_conns; i++)
|
|
||||||
assert(tcp->conns[i].tag != tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
tcp->conns[conn_idx].tag = tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
int tcp_get_tag(TCP *tcp, int conn_idx)
|
|
||||||
{
|
|
||||||
return tcp->conns[conn_idx].tag;
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#ifndef TCP_INCLUDED
|
|
||||||
#define TCP_INCLUDED
|
|
||||||
|
|
||||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
|
||||||
# define QUAKEY_ENABLE_MOCKS
|
|
||||||
# include <quakey.h>
|
|
||||||
#else
|
|
||||||
# ifdef _WIN32
|
|
||||||
# include <winsock2.h>
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "byte_queue.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define CLOSE_SOCKET closesocket
|
|
||||||
#else
|
|
||||||
#define SOCKET int
|
|
||||||
#define INVALID_SOCKET -1
|
|
||||||
#define CLOSE_SOCKET close
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef TCP_CONNECTION_LIMIT
|
|
||||||
// Maximum number of connections that can be managed
|
|
||||||
// simultaneously.
|
|
||||||
#define TCP_CONNECTION_LIMIT 512
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// This is the maximum number of descriptors that the
|
|
||||||
// TCP system will want to wait at any given time.
|
|
||||||
// One descriptor per connection plus a listener socket
|
|
||||||
// and a self-pipe handle for wakeup.
|
|
||||||
#define TCP_POLL_CAPACITY (TCP_CONNECTION_LIMIT+2)
|
|
||||||
|
|
||||||
// Number of TCP events that can be returned at a given
|
|
||||||
// time by "tcp_translate_events". There may be a single
|
|
||||||
// event per connection (MESSAGE, CONNECT, DISCONNECT)
|
|
||||||
// plus a general WAKEUP event.
|
|
||||||
#define TCP_EVENT_CAPACITY (TCP_CONNECTION_LIMIT+1)
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
EVENT_WAKEUP,
|
|
||||||
EVENT_MESSAGE,
|
|
||||||
EVENT_CONNECT,
|
|
||||||
EVENT_DISCONNECT,
|
|
||||||
} EventType;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
EventType type;
|
|
||||||
int conn_idx;
|
|
||||||
int tag;
|
|
||||||
} Event;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
SOCKET fd;
|
|
||||||
int tag;
|
|
||||||
bool connecting;
|
|
||||||
bool closing;
|
|
||||||
uint32_t msglen;
|
|
||||||
ByteQueue input;
|
|
||||||
ByteQueue output;
|
|
||||||
} Connection;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
SOCKET listen_fd;
|
|
||||||
SOCKET wait_fd;
|
|
||||||
SOCKET signal_fd;
|
|
||||||
int num_conns;
|
|
||||||
Connection conns[TCP_CONNECTION_LIMIT];
|
|
||||||
} TCP;
|
|
||||||
|
|
||||||
int tcp_context_init(TCP *tcp);
|
|
||||||
void tcp_context_free(TCP *tcp);
|
|
||||||
int tcp_wakeup(TCP *tcp);
|
|
||||||
int tcp_index_from_tag(TCP *tcp, int tag);
|
|
||||||
int tcp_listen(TCP *tcp, Address addr);
|
|
||||||
int tcp_next_message(TCP *tcp, int conn_idx, ByteView *msg, uint16_t *type);
|
|
||||||
void tcp_consume_message(TCP *tcp, int conn_idx);
|
|
||||||
int tcp_translate_events(TCP *tcp, Event *events, void **contexts, struct pollfd *polled, int num_polled);
|
|
||||||
int tcp_register_events(TCP *tcp, void **contexts, struct pollfd *polled);
|
|
||||||
ByteQueue *tcp_output_buffer(TCP *tcp, int conn_idx);
|
|
||||||
int tcp_connect(TCP *tcp, Address addr, int tag, ByteQueue **output);
|
|
||||||
void tcp_close(TCP *tcp, int conn_idx);
|
|
||||||
void tcp_set_tag(TCP *tcp, int conn_idx, int tag, bool unique);
|
|
||||||
int tcp_get_tag(TCP *tcp, int conn_idx);
|
|
||||||
|
|
||||||
#endif // TCP_INCLUDED
|
|
||||||
Reference in New Issue
Block a user