new makefile
This commit is contained in:
Submodule 3p/packetdrill deleted from 8595c94c9c
Submodule 3p/tinycthread deleted from 6957fc8383
@@ -4,7 +4,18 @@ MicroTCP is a TCP/IP network stack which I started building as a learning exerci
|
||||
At this moment MicroTCP implements ARP (RFC 826, complete), IPv4 (no fragmentation), ICMP (minimum necessary to reply to pings) and TCP (complete but not stress-tested). Note that "complete" should be not intended as "fully compliant" but just as a measure of progress on all of the major features. For instance, it's complete enough to handle HTTP traffic on a local network (Look into examples/microhttp to know more).
|
||||
|
||||
## Where does it run?
|
||||
MicroTCP can run on Windows and Linux alongside the OS's network stack. To route the network traffic to MicroTCP, the process running it behaves as a virtual host with its own IP address. This is done using a TAP device, which comes built-in on Linux but needs installing on Windows. It should be very easy to adapt to run on microcontrollers but haven't tried yet, the dream is to serve my [blog](https://cozis.github.io/) from an STM32 board.
|
||||
MicroTCP can run on Windows and Linux alongside the OS's network stack. To route the network traffic to MicroTCP, the process running it behaves as a virtual host with its own IP address. This is done using a TAP device, which comes built-in on Linux but needs installing on Windows. It should be very easy to adapt to run MicroTCP on microcontrollers but haven't tried yet. The dream is to serve my [blog](https://cozis.github.io/) from an STM32 board!
|
||||
|
||||
## Build and Install
|
||||
If you are on Windows, you need to install the TAP driver provided by OpenVPN and instanciate a virtual NIC so that MicroTCP can connect to it when started. To build the project from source, make sure you cloned the repository with submodules
|
||||
```sh
|
||||
git clone https://github.com/cozis/microtcp.git --recursive
|
||||
```
|
||||
and then run
|
||||
```sh
|
||||
make
|
||||
```
|
||||
You'll need both `make` and `cmake` for it to work. If all goes well, you'll find the library files `libtuntap.a`, `libmicrotcp.a` and header files `tuntap.h`, `tuntap-export.h`, `microtcp.h` in `out/`.
|
||||
|
||||
## Usage
|
||||
MicroTCP's uses the usual socket interface any network programmer is familiar with, the main difference being you need to esplicitly instanciate the network stack and pass its handle around. Without further ado, here's a simple echo server that shows the basic usage:
|
||||
@@ -42,8 +53,11 @@ int main(void)
|
||||
```
|
||||
This should be pretty straight forward to understand. One thing may be worth noting is that `microtcp_open` behaves as the BSD's `socket+bind+listen` all at once to setup a listening TCP server.
|
||||
|
||||
There are more than one way to set up the stack, the main way being `microtcp_create` which creates a virtual network inferface on the host OS with IP 10.0.0.5/24 and a virtual host for the MicroTCP process with the 10.0.0.4/24 IP. You can open Wireshark on the virtual NIC to inspect the traffic between the host and the process.
|
||||
There is more than one way to set up the stack, the main way being `microtcp_create` which creates a virtual network inferface on the host OS with IP 10.0.0.5/24 and a virtual host for the MicroTCP process at 10.0.0.4/24. You can open Wireshark on the virtual NIC to inspect the traffic between the host and the process.
|
||||
|
||||
It's also possible to configure the stack using the `microtcp_create_using_callbacks`, which lets you explicitly provide the input L2 frames to it and receive the frames in a buffer. This is how one would run the stack on a microcontroller.
|
||||
It's also possible to configure the stack using the `microtcp_create_using_callbacks`, which lets you explicitly provide the input L2 frames to it and receive the frames in a buffer. This is how one would configure the stack to run on a microcontroller.
|
||||
|
||||
Each instance of MicroTCP (without considering the callbacks) is completely isolated from the others, therefore, if your specific callback implementation allows it, you can have as many instances as you like! Usually the callbacks introduce a dependency between the stacks because the system is one big global state.
|
||||
Each instance of MicroTCP (without considering the callbacks) is completely isolated from the others, therefore, if your specific callback implementation allows it, you can have as many instances as you like!
|
||||
|
||||
## Testing
|
||||
There is still no testing infractructure. The way I'm testing it is by setting up an HTTP or echo server and stressing it until something breaks while capturing what happened using Wireshark.
|
||||
@@ -3,13 +3,28 @@
|
||||
|
||||
int main(void)
|
||||
{
|
||||
microtcp_errcode_t err;
|
||||
microtcp_t *mtcp = microtcp_create("10.0.0.5", "10.0.0.4", NULL, NULL);
|
||||
|
||||
if (mtcp == NULL) {
|
||||
fprintf(stderr, "Error: Couldn't create MicroTCP instance\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint16_t port = 8081;
|
||||
microtcp_socket_t *server = microtcp_open(mtcp, port, NULL);
|
||||
microtcp_socket_t *server = microtcp_open(mtcp, port, &err);
|
||||
if (server == NULL) {
|
||||
fprintf(stderr, "Error: %s\n", microtcp_strerror(err));
|
||||
microtcp_destroy(mtcp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
microtcp_socket_t *client = microtcp_accept(server, false, NULL);
|
||||
microtcp_socket_t *client = microtcp_accept(server, false, &err);
|
||||
if (client == NULL) {
|
||||
fprintf(stderr, "Error: %s\n", microtcp_strerror(err));
|
||||
break;
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
size_t num = microtcp_recv(client, buffer, sizeof(buffer), false, NULL);
|
||||
microtcp_send(client, "echo: ", 6, false, NULL);
|
||||
@@ -1,147 +1,85 @@
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
OSNAME = windows
|
||||
ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
|
||||
ARCH = AMD64
|
||||
else ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
|
||||
ARCH = AMD64
|
||||
else ifeq ($(PROCESSOR_ARCHITECTURE),x86)
|
||||
ARCH = IA32
|
||||
endif
|
||||
OSTAG = WIN
|
||||
EXT = .exe
|
||||
CMAKE_GENERATOR = "MinGW Makefiles"
|
||||
else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
OSNAME = linux
|
||||
OSTAG = LIN
|
||||
EXT =
|
||||
CMAKE_GENERATOR = "Unix Makefiles"
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
$(error OSX isn't supported)
|
||||
OSNAME = osx
|
||||
endif
|
||||
|
||||
UNAME_P := $(shell uname -p)
|
||||
ifeq ($(UNAME_P),x86_64)
|
||||
ARCH = AMD64
|
||||
endif
|
||||
ifneq ($(filter %86,$(UNAME_P)),)
|
||||
ARCH = IA32
|
||||
endif
|
||||
ifneq ($(filter arm%,$(UNAME_P)),)
|
||||
ARCH = ARM
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(OSNAME),windows)
|
||||
CC = gcc.exe
|
||||
CXX = g++.exe
|
||||
CFLAGS_PLATFORM =
|
||||
LFLAGS_PLATFORM = -lws2_32
|
||||
CMAKE_GENERATOR = "MinGW Makefiles"
|
||||
else ifeq ($(OSNAME),linux)
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
CFLAGS_PLATFORM = -pthread
|
||||
LFLAGS_PLATFORM =
|
||||
CMAKE_GENERATOR = "Unix Makefiles"
|
||||
else
|
||||
endif
|
||||
AR = ar$(EXT)
|
||||
CC = gcc$(EXT)
|
||||
CXX = g++$(EXT)
|
||||
|
||||
# One of: drmemory, valgrind
|
||||
MEMDBG=valgrind
|
||||
SRCDIR = src
|
||||
OBJDIR = obj
|
||||
OUTDIR = out
|
||||
|
||||
LIBDIR = 3p/lib
|
||||
INCDIR = 3p/include
|
||||
CFILES = $(wildcard $(SRCDIR)/*.c)
|
||||
HFILES = $(wildcard $(SRCDIR)/*.h)
|
||||
OFILES = $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(CFILES))
|
||||
|
||||
CFLAGS = $(CUSTOM_CFLAGS) $(CFLAGS_PLATFORM) -I$(INCDIR) -Ibuild/ -Wall -Wextra
|
||||
LFLAGS = $(CUSTOM_LFLAGS) -ltuntap $(LFLAGS_PLATFORM) -L$(LIBDIR)
|
||||
CFLAGS_WIN =
|
||||
CFLAGS_LIN = -pthread
|
||||
CFLAGS = -Wall -Wextra -g $(CFLAGS_$(OSTAG)) -DMICROTCP_DEBUG -DTCP_DEBUG
|
||||
|
||||
ifeq ($(MEMDBG),drmemory)
|
||||
CFLAGS += -gdwarf-2
|
||||
else
|
||||
CFLAGS += -g3
|
||||
endif
|
||||
LIBFILE = $(OUTDIR)/libmicrotcp.a
|
||||
|
||||
.PHONY: all clean
|
||||
TAP_AFILES = $(OUTDIR)/libtuntap.a
|
||||
TAP_HFILES = $(OUTDIR)/tuntap.h $(OUTDIR)/tuntap-export.h
|
||||
|
||||
all: build/microtcp.h build/microtcp.c build/echo_tcp build/echo_http build/test
|
||||
EXAMPLE_0 = $(OUTDIR)/http$(EXT)
|
||||
EXAMPLE_1 = $(OUTDIR)/echo$(EXT)
|
||||
|
||||
3p/lib/libtuntap.a: 3p/libtuntap/build/lib/libtuntap.a
|
||||
cp 3p/libtuntap/build/lib/libtuntap.a $(LIBDIR)
|
||||
EXAMPLE_CFILES_0 = $(wildcard examples/http/*.c)
|
||||
EXAMPLE_HFILES_0 = $(wildcard examples/http/*.h)
|
||||
|
||||
3p/include/tuntap.h: 3p/libtuntap/tuntap.h
|
||||
cp 3p/libtuntap/tuntap.h $(INCDIR)
|
||||
EXAMPLE_CFILES_1 = examples/echo.c
|
||||
EXAMPLE_HFILES_1 =
|
||||
|
||||
3p/include/tuntap-export.h: 3p/libtuntap/build/tuntap-export.h
|
||||
cp 3p/libtuntap/build/tuntap-export.h $(INCDIR)
|
||||
EXAMPLE_LFLAGS_WIN = -lws2_32
|
||||
EXAMPLE_LFLAGS_LIN =
|
||||
EXAMPLE_LFLAGS = -lmicrotcp -ltuntap $(EXAMPLE_LFLAGS_$(OSTAG))
|
||||
|
||||
3p/libtuntap/build/lib/libtuntap.a 3p/libtuntap/tuntap.h 3p/libtuntap/build/tuntap-export.h:
|
||||
cd 3p/libtuntap/ \
|
||||
&& mkdir -p build \
|
||||
&& cd build \
|
||||
&& cmake .. -G $(CMAKE_GENERATOR) \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DCMAKE_C_COMPILER=$(CC) \
|
||||
-DCMAKE_CXX_COMPILER=$(CXX) \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
&& make
|
||||
.PHONY: all exm lib clean
|
||||
|
||||
3p/include/tinycthread.h: 3p/tinycthread/source/tinycthread.h
|
||||
cp 3p/tinycthread/source/tinycthread.h 3p/include
|
||||
all: lib exm
|
||||
|
||||
3p/src/tinycthread.c: 3p/tinycthread/source/tinycthread.c
|
||||
cp 3p/tinycthread/source/tinycthread.c 3p/src
|
||||
exm: $(EXAMPLE_0) $(EXAMPLE_1)
|
||||
|
||||
build/echo_tcp: examples/echo_tcp.c $(LIBDIR)/libtuntap.a 3p/include/tuntap.h 3p/include/tuntap-export.h build/microtcp.c build/microtcp.h
|
||||
mkdir -p $(@D)
|
||||
gcc build/microtcp.c examples/echo_tcp.c -o $@ $(CFLAGS) $(LFLAGS) -DDEBUG=1 -DARP_DEBUG -DMICROTCP_DEBUG -DIP_DEBUG -DICMP_DEBUG -DTCP_DEBUG -DMICROTCP_BACKGROUND_THREAD -DMICROTCP_USING_TAP -DMICROTCP_USING_MUX
|
||||
lib: $(LIBFILE) $(TAP_HFILES) $(TAP_AFILES)
|
||||
cp $(SRCDIR)/microtcp.h $(OUTDIR)/microtcp.h
|
||||
|
||||
build/microtcp.h: src/microtcp.h
|
||||
mkdir -p $(@D)
|
||||
[ ! -e $@ ] || rm $@
|
||||
echo "#define MICROTCP_AMALGAMATION" >> $@
|
||||
cat src/microtcp.h >> $@
|
||||
$(OBJDIR)/%.o: $(SRCDIR)/%.c $(HFILES) $(TAP_HFILES)
|
||||
$(CC) -c -o $@ $< $(CFLAGS) -I$(OUTDIR)
|
||||
|
||||
build/microtcp.c: 3p/include/tinycthread.h 3p/src/tinycthread.c $(wildcard src/*.c src/*.h)
|
||||
mkdir -p $(@D)
|
||||
[ ! -e $@ ] || rm $@
|
||||
printf "#include \"microtcp.h\"\n" > $@
|
||||
printf "#ifdef MICROTCP_BACKGROUND_THREAD" >> $@
|
||||
printf "\n#line 1 \"3p/include/tinycthread.h\"\n" >> $@
|
||||
cat 3p/include/tinycthread.h >> $@
|
||||
printf "\n#line 1 \"3p/src/tinycthread.c\"\n" >> $@
|
||||
cat 3p/src/tinycthread.c >> $@
|
||||
printf "\n#endif /* MICROTCP_BACKGROUND_THREAD */" >> $@
|
||||
printf "\n#line 1 \"src/defs.h\"\n" >> $@
|
||||
cat src/defs.h >> $@
|
||||
printf "\n#line 1 \"src/utils.h\"\n" >> $@
|
||||
cat src/utils.h >> $@
|
||||
printf "\n#line 1 \"src/endian.h\"\n" >> $@
|
||||
cat src/endian.h >> $@
|
||||
printf "\n#line 1 \"src/arp.h\"\n" >> $@
|
||||
cat src/arp.h >> $@
|
||||
printf "\n#line 1 \"src/icmp.h\"\n" >> $@
|
||||
cat src/icmp.h >> $@
|
||||
printf "\n#line 1 \"src/ip.h\"\n" >> $@
|
||||
cat src/ip.h >> $@
|
||||
printf "\n#line 1 \"src/tcp_timer.h\"\n" >> $@
|
||||
cat src/tcp_timer.h >> $@
|
||||
printf "\n#line 1 \"src/tcp.h\"\n" >> $@
|
||||
cat src/tcp.h >> $@
|
||||
printf "\n#line 1 \"src/utils.c\"\n" >> $@
|
||||
cat src/utils.c >> $@
|
||||
printf "\n#line 1 \"src/endian.c\"\n" >> $@
|
||||
cat src/endian.c >> $@
|
||||
printf "\n#line 1 \"src/arp.c\"\n" >> $@
|
||||
cat src/arp.c >> $@
|
||||
printf "\n#line 1 \"src/icmp.c\"\n" >> $@
|
||||
cat src/icmp.c >> $@
|
||||
printf "\n#line 1 \"src/ip.c\"\n" >> $@
|
||||
cat src/ip.c >> $@
|
||||
printf "\n#line 1 \"src/tcp_timer.c\"\n" >> $@
|
||||
cat src/tcp_timer.c >> $@
|
||||
printf "\n#line 1 \"src/tcp.c\"\n" >> $@
|
||||
cat src/tcp.c >> $@
|
||||
printf "\n#line 1 \"src/microtcp.c\"\n" >> $@
|
||||
cat src/microtcp.c >> $@
|
||||
$(LIBFILE): $(OFILES)
|
||||
$(AR) rcs $@ $^
|
||||
|
||||
build/echo_http: examples/microhttp/main.c $(LIBDIR)/libtuntap.a 3p/include/tuntap.h 3p/include/tuntap-export.h build/microtcp.h build/microtcp.c
|
||||
gcc examples/microhttp/main.c examples/microhttp/xhttp.c build/microtcp.c -o $@ $(CFLAGS) $(LFLAGS) -DDEBUG=1 -DARP_DEBUG -DMICROTCP_DEBUG -DIP_DEBUG -DICMP_DEBUG -DTCP_DEBUG -DMICROTCP_USING_MUX
|
||||
$(TAP_AFILES) $(TAP_HFILES):
|
||||
cd 3p/libtuntap/ && \
|
||||
mkdir -p build && \
|
||||
cd build && \
|
||||
cmake .. -G $(CMAKE_GENERATOR) -DBUILD_TESTING=OFF -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX) -DCMAKE_BUILD_TYPE=Debug && \
|
||||
make
|
||||
cp 3p/libtuntap/build/lib/libtuntap.a $(OUTDIR)/libtuntap.a
|
||||
cp 3p/libtuntap/tuntap.h $(OUTDIR)/tuntap.h
|
||||
cp 3p/libtuntap/build/tuntap-export.h $(OUTDIR)/tuntap-export.h
|
||||
|
||||
$(EXAMPLE_0): lib $(EXAMPLE_CFILES_0) $(EXAMPLE_HFILES_0)
|
||||
$(CC) -o $@ $(EXAMPLE_CFILES_0) $(CFLAGS) $(EXAMPLE_LFLAGS) -I$(OUTDIR) -L$(OUTDIR)
|
||||
|
||||
$(EXAMPLE_1): lib $(EXAMPLE_CFILES_1) $(EXAMPLE_HFILES_1)
|
||||
$(CC) -o $@ $(EXAMPLE_CFILES_1) $(CFLAGS) $(EXAMPLE_LFLAGS) -I$(OUTDIR) -L$(OUTDIR)
|
||||
|
||||
clean:
|
||||
rm -fr build 3p/libtuntap/build 3p/lib/* 3p/include/*
|
||||
rm -fr $(OBJDIR) $(OUTDIR)
|
||||
mkdir obj out
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
#ifndef MICROTCP_H
|
||||
#define MICROTCP_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct microtcp_t microtcp_t;
|
||||
typedef struct microtcp_socket_t microtcp_socket_t;
|
||||
|
||||
#define MICROTCP_MAX_BUFFERS 8
|
||||
#define MICROTCP_MAX_SOCKETS 32
|
||||
#define MICROTCP_MAX_MUX_ENTRIES 32
|
||||
|
||||
typedef enum {
|
||||
|
||||
MICROTCP_ERRCODE_NONE = 0,
|
||||
|
||||
// Returned by microtcp_open and microtcp_accept
|
||||
MICROTCP_ERRCODE_SOCKETLIMIT,
|
||||
|
||||
// Returned by microtcp_open
|
||||
MICROTCP_ERRCODE_TCPERROR,
|
||||
MICROTCP_ERRCODE_BADCONDVAR,
|
||||
|
||||
// Returned by microtcp_accept
|
||||
MICROTCP_ERRCODE_NOTLISTENER,
|
||||
MICROTCP_ERRCODE_CANTBLOCK,
|
||||
|
||||
// Returned by microtcp_recv and microtcp_send
|
||||
MICROTCP_ERRCODE_NOTCONNECTION,
|
||||
|
||||
// Returned by microtcp_accept, microtcp_recv and microtcp_send
|
||||
MICROTCP_ERRCODE_WOULDBLOCK,
|
||||
|
||||
// Returned by microtcp_recv, microtcp_send
|
||||
MICROTCP_ERRCODE_PEERCLOSED,
|
||||
|
||||
} microtcp_errcode_t;
|
||||
|
||||
typedef struct {
|
||||
void *data;
|
||||
void (*free)(void *data);
|
||||
int (*send)(void *data, const void *src, size_t len);
|
||||
int (*recv)(void *data, void *dst, size_t len);
|
||||
} microtcp_callbacks_t;
|
||||
|
||||
bool microtcp_callbacks_create_for_tap(const char *ip, const char *mac, microtcp_callbacks_t *callbacks);
|
||||
microtcp_t *microtcp_create(const char *tap_ip, const char *stack_ip, const char *tap_mac, const char *stack_mac);
|
||||
|
||||
microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac, microtcp_callbacks_t callbacks);
|
||||
void microtcp_destroy(microtcp_t *mtcp);
|
||||
const char *microtcp_strerror(microtcp_errcode_t errcode);
|
||||
microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port, microtcp_errcode_t *errcode);
|
||||
microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket, bool no_block, microtcp_errcode_t *errcode);
|
||||
void microtcp_close(microtcp_socket_t *socket);
|
||||
size_t microtcp_send(microtcp_socket_t *socket, const void *src, size_t len, bool no_block, microtcp_errcode_t *errcode);
|
||||
size_t microtcp_recv(microtcp_socket_t *socket, void *dst, size_t len, bool no_block, microtcp_errcode_t *errcode);
|
||||
void microtcp_step(microtcp_t *mtcp);
|
||||
void microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len);
|
||||
|
||||
typedef enum {
|
||||
MICROTCP_MUX_ACCEPT = 1,
|
||||
MICROTCP_MUX_RECV = 2,
|
||||
MICROTCP_MUX_SEND = 4,
|
||||
} microtcp_muxeventid_t;
|
||||
|
||||
typedef struct {
|
||||
void *userp;
|
||||
int events;
|
||||
microtcp_socket_t *socket;
|
||||
} microtcp_muxevent_t;
|
||||
|
||||
typedef struct microtcp_mux_t microtcp_mux_t;
|
||||
microtcp_mux_t *microtcp_mux_create(microtcp_t *mtcp);
|
||||
void microtcp_mux_destroy(microtcp_mux_t *mux);
|
||||
bool microtcp_mux_register(microtcp_mux_t *mux, microtcp_socket_t *sock, int events, void *userp);
|
||||
bool microtcp_mux_unregister(microtcp_mux_t *mux, microtcp_socket_t *sock, int events);
|
||||
bool microtcp_mux_wait(microtcp_mux_t *mux, microtcp_muxevent_t *ev);
|
||||
|
||||
#endif
|
||||
@@ -1,125 +1,6 @@
|
||||
/* Il protocollo ARP (Address Resolution Protocol)
|
||||
* permette di tradurre gli indirizzi di livello
|
||||
* rete (come IP) a quelli del livello inferiore
|
||||
* data-link (come ethernet).
|
||||
* Per grandi linee, i messaggi ARP possono essere
|
||||
* REQUEST o REPLY. Quando un host vuole comunicare
|
||||
* con un altro host dato il suo IP (o qualsiasi
|
||||
* indirizzo di livello 3), manda un messaggio di
|
||||
* REQUEST in broadcast (MAC di destinazione
|
||||
* ff:ff:ff:ff:ff:ff) contenente l'indirizzo di
|
||||
* livello 3 del quale si vuole conoscere quello di
|
||||
* livello 2. Ciascun host della rete riceve il
|
||||
* messaggio e controlla se la richiesta è relativa
|
||||
* al proprio IP. Se il controllo risulta positivo
|
||||
* risponde con un messaggio di REPLY contenente
|
||||
* il proprio MAC. Il messaggio di REPLY, a differenza
|
||||
* della REQUEST è un unicast dato che è noto il
|
||||
* destinatario.
|
||||
*
|
||||
* Il messaggio ARP è grande 28 byte ed, indipendentemente
|
||||
* dal tipo di richiesta, ha questa struttura:
|
||||
*
|
||||
* (16 bits per row)
|
||||
* +-----------------------------------+
|
||||
* 0 | hardware_type |
|
||||
* +-----------------------------------+
|
||||
* 2 | protocol_type |
|
||||
* +-----------------+-----------------+
|
||||
* 4 | hardware_length | protocol_length |
|
||||
* +-----------------+-----------------+
|
||||
* 6 | operation_type |
|
||||
* +-----------------------------------+
|
||||
* 8 | sender_hardware_address |
|
||||
* +- - - - - - - - - - - - - - - - - -+
|
||||
* | |
|
||||
* +- - - - - - - - - - - - - - - - - -+
|
||||
* | |
|
||||
* +-----------------------------------+
|
||||
* 14 | sender_protocol_address |
|
||||
* +- - - - - - - - - - - - - - - - - -+
|
||||
* | |
|
||||
* +-----------------------------------+
|
||||
* 18 | target_hardware_address |
|
||||
* +- - - - - - - - - - - - - - - - - -+
|
||||
* | |
|
||||
* +- - - - - - - - - - - - - - - - - -+
|
||||
* | |
|
||||
* +-----------------------------------+
|
||||
* 24 | target_protocol_address |
|
||||
* +- - - - - - - - - - - - - - - - - -+
|
||||
* | |
|
||||
* +-----------------------------------+
|
||||
*
|
||||
* I campi hardware_type e protocol_type indicano
|
||||
* il protocollo di livello 2 e quello di livello 3.
|
||||
* Nel caso di IP su Ethernet si ha hardware_type=1
|
||||
* e protocol_type=0x800.
|
||||
*
|
||||
* I campi hardware_length e protocol_length
|
||||
* indicano la dimensione in byte degli indirizzi
|
||||
* dei due protocolli. Per IP e Ethernet questi
|
||||
* sono ridondanti dato che ogni indirizzo Ethernet
|
||||
* è di 6 byte ed ogni indirizzo IP di 4.
|
||||
*
|
||||
* Il campo operation_type indica la finalità del
|
||||
* messaggio. Può essere uno di:
|
||||
* ARP REQUEST -> operation_type=1
|
||||
* ARP REPLY -> operation_type=2
|
||||
* RARP REQUEST -> operation_type=3
|
||||
* RARP REPLY -> operation_type=4
|
||||
* (possiamo ignorare RARP per ora)
|
||||
*
|
||||
* Il campo sender_hardware_address e sender_protocol_address
|
||||
* contengono MAC e IP di chi ha inviato il messaggio.
|
||||
*
|
||||
* Il campo target_hardware_address cambia di significato
|
||||
* a seconda del tipo di operazione:
|
||||
* ARP REQUEST -> è vuoto, perchè non è noto il MAC di
|
||||
* destinazione (vogliamo determinarlo)
|
||||
* ARP REPLY -> indirizzo MAC di chi ha fatto la REQUEST
|
||||
*
|
||||
* Il campo target_protocol_address contiene l'indirizzo IP
|
||||
* di chi ha inviato il messaggio.
|
||||
*/
|
||||
|
||||
/*
|
||||
struct iphdr
|
||||
{
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
unsigned int ihl:4;
|
||||
unsigned int version:4;
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
unsigned int version:4;
|
||||
unsigned int ihl:4;
|
||||
#else
|
||||
# error "Please fix <bits/endian.h>"
|
||||
#endif
|
||||
uint8_t tos;
|
||||
uint16_t tot_len;
|
||||
uint16_t id;
|
||||
uint16_t frag_off;
|
||||
uint8_t ttl;
|
||||
uint8_t protocol;
|
||||
uint16_t check;
|
||||
uint32_t saddr;
|
||||
uint32_t daddr;
|
||||
// The options start here.
|
||||
};
|
||||
|
||||
struct ethhdr {
|
||||
unsigned char h_dest[ETH_ALEN]; // destination eth addr
|
||||
unsigned char h_source[ETH_ALEN]; // source ether addr
|
||||
__be16 h_proto; // packet type ID field
|
||||
} __attribute__((packed));
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "endian.h"
|
||||
#include "arp.h"
|
||||
#endif
|
||||
|
||||
#ifdef ARP_DEBUG
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "defs.h"
|
||||
#endif
|
||||
|
||||
#define ARP_MAX_PENDING_REQUESTS 32
|
||||
#define ARP_TRANSLATION_TABLE_SIZE 128
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "endian.h"
|
||||
#endif
|
||||
|
||||
bool cpu_is_little_endian(void)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#include <string.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "endian.h"
|
||||
#include "icmp.h"
|
||||
#endif
|
||||
|
||||
#ifdef ICMP_DEBUG
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "defs.h"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
void *output_ptr;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#include <string.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "endian.h"
|
||||
#include "ip.h"
|
||||
#endif
|
||||
|
||||
#ifdef IP_DEBUG
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "defs.h"
|
||||
#include "icmp.h"
|
||||
#endif
|
||||
|
||||
#define IP_PLUGGED_PROTOCOLS_MAX 4
|
||||
|
||||
|
||||
+55
-147
@@ -3,22 +3,14 @@
|
||||
#include <string.h> // strerror()
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
# include "ip.h"
|
||||
# include "arp.h"
|
||||
# include "tcp.h"
|
||||
# include "utils.h"
|
||||
# include "endian.h"
|
||||
# include "microtcp.h"
|
||||
# ifdef MICROTCP_BACKGROUND_THREAD
|
||||
# include "tinycthread.h"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef MICROTCP_USING_TAP
|
||||
#include <tuntap.h>
|
||||
#endif
|
||||
#include "ip.h"
|
||||
#include "arp.h"
|
||||
#include "tcp.h"
|
||||
#include "utils.h"
|
||||
#include "endian.h"
|
||||
#include "microtcp.h"
|
||||
#include "tinycthread.h"
|
||||
|
||||
#ifdef MICROTCP_DEBUG
|
||||
#include <stdio.h>
|
||||
@@ -27,15 +19,7 @@
|
||||
#define MICROTCP_DEBUG_LOG(...) do {} while (0);
|
||||
#endif
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
#define LOCK_WHEN_THREADED(mtcp) do { mtx_lock(&(mtcp)->lock); } while (0);
|
||||
#define UNLOCK_WHEN_THREADED(mtcp) do { mtx_unlock(&(mtcp)->lock); } while (0);
|
||||
#else
|
||||
#define LOCK_WHEN_THREADED(mtcp) do { (void) (mtcp); } while (0);
|
||||
#define UNLOCK_WHEN_THREADED(mtcp) do { (void) (mtcp); } while (0);
|
||||
#endif
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
typedef struct mux_entry_t mux_entry_t;
|
||||
struct mux_entry_t {
|
||||
mux_entry_t **mux_prev;
|
||||
@@ -53,16 +37,13 @@ struct mux_entry_t {
|
||||
|
||||
struct microtcp_mux_t {
|
||||
microtcp_t *mtcp;
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
cnd_t queue_not_empty;
|
||||
#endif
|
||||
mux_entry_t *free_list;
|
||||
mux_entry_t *idle_list;
|
||||
mux_entry_t *ready_queue_head;
|
||||
mux_entry_t *ready_queue_tail;
|
||||
mux_entry_t entries[MICROTCP_MAX_MUX_ENTRIES];
|
||||
};
|
||||
#endif
|
||||
|
||||
typedef struct buffer_t buffer_t;
|
||||
struct buffer_t {
|
||||
@@ -88,7 +69,6 @@ struct microtcp_socket_t {
|
||||
tcp_connection_t *connection;
|
||||
};
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
union {
|
||||
cnd_t something_to_accept;
|
||||
struct {
|
||||
@@ -96,22 +76,17 @@ struct microtcp_socket_t {
|
||||
cnd_t something_to_send;
|
||||
};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
mux_entry_t *mux_list;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct microtcp_t {
|
||||
|
||||
uint64_t last_update_time_ms;
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
bool thread_should_stop;
|
||||
thrd_t thread_id;
|
||||
mtx_t lock;
|
||||
#endif
|
||||
|
||||
microtcp_callbacks_t callbacks;
|
||||
|
||||
@@ -393,9 +368,9 @@ process_packet(microtcp_t *mtcp, const void *packet, size_t len)
|
||||
|
||||
void microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len)
|
||||
{
|
||||
LOCK_WHEN_THREADED(mtcp);
|
||||
mtx_lock(&mtcp->lock);
|
||||
process_packet(mtcp, packet, len);
|
||||
UNLOCK_WHEN_THREADED(mtcp);
|
||||
mtx_unlock(&mtcp->lock);
|
||||
}
|
||||
|
||||
static uint64_t get_time_in_ms(void)
|
||||
@@ -419,7 +394,7 @@ void microtcp_step(microtcp_t *mtcp)
|
||||
|
||||
uint64_t current_time_ms = get_time_in_ms();
|
||||
|
||||
LOCK_WHEN_THREADED(mtcp);
|
||||
mtx_lock(&mtcp->lock);
|
||||
{
|
||||
process_packet(mtcp, packet, size);
|
||||
|
||||
@@ -432,10 +407,9 @@ void microtcp_step(microtcp_t *mtcp)
|
||||
mtcp->last_update_time_ms = current_time_ms;
|
||||
}
|
||||
}
|
||||
UNLOCK_WHEN_THREADED(mtcp);
|
||||
mtx_unlock(&mtcp->lock);
|
||||
}
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
static int loop(void *data)
|
||||
{
|
||||
microtcp_t *mtcp = data;
|
||||
@@ -443,7 +417,6 @@ static int loop(void *data)
|
||||
microtcp_step(mtcp);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac,
|
||||
microtcp_callbacks_t callbacks)
|
||||
@@ -492,7 +465,7 @@ microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac,
|
||||
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].mtcp = NULL;
|
||||
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].prev = NULL;
|
||||
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].next = NULL;
|
||||
|
||||
|
||||
ip_init(&mtcp->ip_state, parsed_ip, mtcp, send_ip_packet);
|
||||
if (!ip_plug_protocol(&mtcp->ip_state, IP_PROTOCOL_TCP, &mtcp->tcp_state, tcp_process_segment_wrapper)) {
|
||||
free(mtcp);
|
||||
@@ -508,9 +481,8 @@ microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac,
|
||||
|
||||
use_a_buffer(mtcp);
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
{
|
||||
if (mtx_init(&mtcp->lock, mtx_plain) != thrd_success) {
|
||||
if (mtx_init(&mtcp->lock, mtx_recursive) != thrd_success) {
|
||||
ip_free(&mtcp->ip_state);
|
||||
arp_free(&mtcp->arp_state);
|
||||
tcp_free(&mtcp->tcp_state);
|
||||
@@ -527,7 +499,6 @@ microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac,
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
MICROTCP_DEBUG_LOG("Instanciated ("
|
||||
"debug="
|
||||
@@ -535,21 +506,12 @@ microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac,
|
||||
"yes"
|
||||
#else
|
||||
"no"
|
||||
#endif
|
||||
", thread="
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
"yes"
|
||||
#else
|
||||
"no"
|
||||
#endif
|
||||
")");
|
||||
|
||||
return mtcp;
|
||||
}
|
||||
|
||||
|
||||
#ifdef MICROTCP_USING_TAP
|
||||
|
||||
static void log_callback_for_tuntap_library(int level, const char *errmsg)
|
||||
{
|
||||
const char *name;
|
||||
@@ -622,17 +584,14 @@ microtcp_t *microtcp_create(const char *tap_ip, const char *stack_ip,
|
||||
callbacks.free(callbacks.data);
|
||||
return mtcp;
|
||||
}
|
||||
#endif
|
||||
|
||||
void microtcp_destroy(microtcp_t *mtcp)
|
||||
{
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
MICROTCP_DEBUG_LOG("Stopping thread");
|
||||
mtcp->thread_should_stop = true;
|
||||
thrd_join(mtcp->thread_id, NULL);
|
||||
mtx_destroy(&mtcp->lock);
|
||||
MICROTCP_DEBUG_LOG("Thread stopped");
|
||||
#endif
|
||||
|
||||
ip_free(&mtcp->ip_state);
|
||||
arp_free(&mtcp->arp_state);
|
||||
@@ -687,30 +646,23 @@ push_unlinked_socket_into_free_list(microtcp_t *mtcp, microtcp_socket_t *socket)
|
||||
mtcp->free_socket_list = socket;
|
||||
}
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
static void
|
||||
signal_events_to_muxes_associated_to_socket(microtcp_socket_t *socket, int events);
|
||||
#endif
|
||||
|
||||
static void listener_event_callback(void *data, tcp_listenevent_t event)
|
||||
{
|
||||
microtcp_socket_t *socket = data;
|
||||
(void) socket;
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
switch (event) {
|
||||
case TCP_LISTENEVENT_ACCEPT: cnd_signal(&socket->something_to_accept); break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
|
||||
int flags = 0;
|
||||
switch (event) {
|
||||
case TCP_LISTENEVENT_ACCEPT: flags = MICROTCP_MUX_ACCEPT; MICROTCP_DEBUG_LOG("Signaling ACCEPT to muxes"); break;
|
||||
case TCP_LISTENEVENT_ACCEPT:
|
||||
cnd_signal(&socket->something_to_accept);
|
||||
flags = MICROTCP_MUX_ACCEPT;
|
||||
MICROTCP_DEBUG_LOG("Signaling ACCEPT to muxes");
|
||||
break;
|
||||
}
|
||||
if (flags)
|
||||
signal_events_to_muxes_associated_to_socket(socket, flags);
|
||||
#endif
|
||||
}
|
||||
|
||||
microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port,
|
||||
@@ -718,7 +670,7 @@ microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port,
|
||||
{
|
||||
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
|
||||
microtcp_socket_t *socket = NULL;
|
||||
LOCK_WHEN_THREADED(mtcp);
|
||||
mtx_lock(&mtcp->lock);
|
||||
{
|
||||
socket = pop_socket_struct_from_free_list(mtcp);
|
||||
if (!socket) {
|
||||
@@ -740,23 +692,19 @@ microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port,
|
||||
socket->next = NULL;
|
||||
socket->type = SOCKET_LISTENER;
|
||||
socket->listener = listener;
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
socket->mux_list = NULL;
|
||||
#endif
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
if (cnd_init(&socket->something_to_accept) != thrd_success) {
|
||||
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
|
||||
push_unlinked_socket_into_free_list(mtcp, socket);
|
||||
tcp_listener_destroy(listener);
|
||||
goto unlock_and_exit;
|
||||
}
|
||||
#endif
|
||||
|
||||
push_unlinked_socket_into_used_list(socket);
|
||||
}
|
||||
unlock_and_exit:
|
||||
UNLOCK_WHEN_THREADED(mtcp);
|
||||
mtx_unlock(&mtcp->lock);
|
||||
if (errcode)
|
||||
*errcode = errcode2;
|
||||
return socket;
|
||||
@@ -769,10 +717,8 @@ void microtcp_close(microtcp_socket_t *socket)
|
||||
|
||||
microtcp_t *mtcp = socket->mtcp;
|
||||
|
||||
LOCK_WHEN_THREADED(mtcp);
|
||||
mtx_lock(&mtcp->lock);
|
||||
{
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
// Unregister from all multiplexers
|
||||
while (socket->mux_list) {
|
||||
// The unregister operation only has
|
||||
@@ -783,14 +729,11 @@ void microtcp_close(microtcp_socket_t *socket)
|
||||
socket->mux_list->triggered_events = 0;
|
||||
microtcp_mux_unregister(socket->mux_list->mux, socket, ~0);
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (socket->type) {
|
||||
|
||||
case SOCKET_LISTENER:
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
cnd_destroy(&socket->something_to_accept);
|
||||
#endif
|
||||
tcp_listener_destroy(socket->listener);
|
||||
break;
|
||||
|
||||
@@ -804,38 +747,36 @@ void microtcp_close(microtcp_socket_t *socket)
|
||||
unlink_socket_from_used_socket_list(socket);
|
||||
push_unlinked_socket_into_free_list(mtcp, socket);
|
||||
}
|
||||
UNLOCK_WHEN_THREADED(mtcp);
|
||||
mtx_unlock(&mtcp->lock);
|
||||
}
|
||||
|
||||
static void conn_event_callback(void *data, tcp_connevent_t event)
|
||||
{
|
||||
microtcp_socket_t *socket = data;
|
||||
(void) socket;
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
int flags;
|
||||
switch (event) {
|
||||
|
||||
case TCP_CONNEVENT_RECV: MICROTCP_DEBUG_LOG("Signal RECV"); cnd_signal(&socket->something_to_recv); break;
|
||||
case TCP_CONNEVENT_SEND: MICROTCP_DEBUG_LOG("Signal SEND"); cnd_signal(&socket->something_to_send); break;
|
||||
case TCP_CONNEVENT_RECV:
|
||||
MICROTCP_DEBUG_LOG("Signal RECV");
|
||||
cnd_signal(&socket->something_to_recv);
|
||||
flags = MICROTCP_MUX_RECV;
|
||||
break;
|
||||
|
||||
case TCP_CONNEVENT_SEND:
|
||||
MICROTCP_DEBUG_LOG("Signal SEND");
|
||||
cnd_signal(&socket->something_to_send);
|
||||
flags = MICROTCP_MUX_SEND;
|
||||
break;
|
||||
|
||||
case TCP_CONNEVENT_RESET:
|
||||
case TCP_CONNEVENT_CLOSE:
|
||||
socket->connection = NULL;
|
||||
flags = 0; // TODO: Maybe signal closing and reset events to muxes?
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
// TODO: Maybe signal closing and reset events to muxes?
|
||||
int flags;
|
||||
switch (event) {
|
||||
case TCP_CONNEVENT_RECV: flags = MICROTCP_MUX_RECV; MICROTCP_DEBUG_LOG("Signaling RECV to muxes"); break;
|
||||
case TCP_CONNEVENT_SEND: flags = MICROTCP_MUX_SEND; MICROTCP_DEBUG_LOG("Signaling SEND to muxes"); break;
|
||||
default: flags = 0; break;
|
||||
}
|
||||
if (flags)
|
||||
signal_events_to_muxes_associated_to_socket(socket, flags);
|
||||
#endif
|
||||
}
|
||||
|
||||
microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
|
||||
@@ -846,7 +787,7 @@ microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
|
||||
microtcp_t *mtcp = socket->mtcp;
|
||||
microtcp_socket_t *socket2 = NULL;
|
||||
|
||||
LOCK_WHEN_THREADED(mtcp);
|
||||
mtx_lock(&mtcp->lock);
|
||||
{
|
||||
if (socket->type != SOCKET_LISTENER) {
|
||||
errcode2 = MICROTCP_ERRCODE_NOTLISTENER;
|
||||
@@ -861,7 +802,6 @@ microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
|
||||
|
||||
tcp_connection_t *connection = tcp_listener_accept(socket->listener, socket2, conn_event_callback);
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
while (!connection && !no_block) {
|
||||
if (cnd_wait(&socket->something_to_accept, &mtcp->lock) != thrd_success) {
|
||||
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
|
||||
@@ -870,28 +810,14 @@ microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
|
||||
}
|
||||
connection = tcp_listener_accept(socket->listener, socket2, conn_event_callback);
|
||||
}
|
||||
#else
|
||||
if (!connection) {
|
||||
if (no_block)
|
||||
errcode2 = MICROTCP_ERRCODE_WOULDBLOCK;
|
||||
else
|
||||
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
|
||||
push_unlinked_socket_into_free_list(mtcp, socket2);
|
||||
goto unlock_and_exit;
|
||||
}
|
||||
#endif
|
||||
|
||||
socket2->mtcp = mtcp;
|
||||
socket2->prev = NULL;
|
||||
socket2->next = NULL;
|
||||
socket2->type = SOCKET_CONNECTION;
|
||||
socket2->connection = connection;
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
socket2->mux_list = NULL;
|
||||
#endif
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
if (cnd_init(&socket2->something_to_recv) != thrd_success) {
|
||||
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
|
||||
push_unlinked_socket_into_free_list(mtcp, socket2);
|
||||
@@ -903,13 +829,12 @@ microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
|
||||
push_unlinked_socket_into_free_list(mtcp, socket2);
|
||||
goto unlock_and_exit;
|
||||
}
|
||||
#endif
|
||||
|
||||
push_unlinked_socket_into_used_list(socket2);
|
||||
}
|
||||
|
||||
unlock_and_exit:
|
||||
UNLOCK_WHEN_THREADED(mtcp);
|
||||
mtx_unlock(&mtcp->lock);
|
||||
|
||||
if (errcode)
|
||||
*errcode = errcode2;
|
||||
@@ -938,11 +863,10 @@ size_t microtcp_recv(microtcp_socket_t *socket,
|
||||
microtcp_t *mtcp = socket->mtcp;
|
||||
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
|
||||
|
||||
LOCK_WHEN_THREADED(mtcp);
|
||||
mtx_lock(&mtcp->lock);
|
||||
{
|
||||
num = tcp_connection_recv(socket->connection, dst, len);
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
while (num == 0 && !no_block) {
|
||||
if (cnd_wait(&socket->something_to_recv, &mtcp->lock) != thrd_success) {
|
||||
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
|
||||
@@ -950,7 +874,7 @@ size_t microtcp_recv(microtcp_socket_t *socket,
|
||||
}
|
||||
num = tcp_connection_recv(socket->connection, dst, len);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (num == 0) {
|
||||
if (no_block)
|
||||
errcode2 = MICROTCP_ERRCODE_WOULDBLOCK;
|
||||
@@ -962,7 +886,7 @@ size_t microtcp_recv(microtcp_socket_t *socket,
|
||||
goto unlock_and_exit; // Warning
|
||||
unlock_and_exit:
|
||||
|
||||
UNLOCK_WHEN_THREADED(mtcp);
|
||||
mtx_unlock(&mtcp->lock);
|
||||
|
||||
if (errcode)
|
||||
*errcode = errcode2;
|
||||
@@ -990,11 +914,10 @@ size_t microtcp_send(microtcp_socket_t *socket,
|
||||
microtcp_t *mtcp = socket->mtcp;
|
||||
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
|
||||
|
||||
LOCK_WHEN_THREADED(mtcp);
|
||||
mtx_lock(&mtcp->lock);
|
||||
{
|
||||
num = tcp_connection_send(socket->connection, src, len);
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
while (num == 0 && !no_block) {
|
||||
if (cnd_wait(&socket->something_to_send, &mtcp->lock) != thrd_success) {
|
||||
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
|
||||
@@ -1002,7 +925,7 @@ size_t microtcp_send(microtcp_socket_t *socket,
|
||||
}
|
||||
num = tcp_connection_send(socket->connection, src, len);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (num == 0) {
|
||||
if (no_block)
|
||||
errcode2 = MICROTCP_ERRCODE_WOULDBLOCK;
|
||||
@@ -1012,14 +935,13 @@ size_t microtcp_send(microtcp_socket_t *socket,
|
||||
}
|
||||
goto unlock_and_exit; // Warning
|
||||
unlock_and_exit:
|
||||
UNLOCK_WHEN_THREADED(mtcp);
|
||||
mtx_unlock(&mtcp->lock);
|
||||
|
||||
if (errcode)
|
||||
*errcode = errcode2;
|
||||
return num;
|
||||
}
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
microtcp_mux_t *microtcp_mux_create(microtcp_t *mtcp)
|
||||
{
|
||||
microtcp_mux_t *mux = malloc(sizeof(microtcp_mux_t));
|
||||
@@ -1048,12 +970,10 @@ microtcp_mux_t *microtcp_mux_create(microtcp_t *mtcp)
|
||||
mux->ready_queue_head = NULL;
|
||||
mux->ready_queue_tail = NULL;
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
if (cnd_init(&mux->queue_not_empty) != thrd_success) {
|
||||
free(mux);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
return mux;
|
||||
}
|
||||
@@ -1084,10 +1004,7 @@ void microtcp_mux_destroy(microtcp_mux_t *mux)
|
||||
assert(entry != mux->ready_queue_head);
|
||||
}
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
cnd_destroy(&mux->queue_not_empty);
|
||||
#endif
|
||||
|
||||
free(mux);
|
||||
}
|
||||
|
||||
@@ -1152,7 +1069,7 @@ move_mux_entry_to_idle_list(mux_entry_t *entry)
|
||||
|
||||
bool microtcp_mux_unregister(microtcp_mux_t *mux, microtcp_socket_t *sock, int events)
|
||||
{
|
||||
LOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_lock(&mux->mtcp->lock);
|
||||
|
||||
// There's no need to check that mux
|
||||
// and socket have the same mtcp because
|
||||
@@ -1163,7 +1080,7 @@ bool microtcp_mux_unregister(microtcp_mux_t *mux, microtcp_socket_t *sock, int e
|
||||
mux_entry_t *entry = find_socket_and_mux_entry(mux, sock);
|
||||
if (!entry) {
|
||||
// This socket wasn't registered into the mux
|
||||
UNLOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_unlock(&mux->mtcp->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1184,21 +1101,21 @@ bool microtcp_mux_unregister(microtcp_mux_t *mux, microtcp_socket_t *sock, int e
|
||||
// move the entry to the free list.
|
||||
move_mux_entry_to_free_list(entry);
|
||||
|
||||
UNLOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_unlock(&mux->mtcp->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool microtcp_mux_register(microtcp_mux_t *mux, microtcp_socket_t *sock, int events, void *userp)
|
||||
{
|
||||
LOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_lock(&mux->mtcp->lock);
|
||||
|
||||
if (mux->mtcp != sock->mtcp) {
|
||||
UNLOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_unlock(&mux->mtcp->lock);
|
||||
return false; // mux and socket are associated to different microtcp stacks
|
||||
}
|
||||
|
||||
if (events == 0) {
|
||||
UNLOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_unlock(&mux->mtcp->lock);
|
||||
return true; // Nothing to be done
|
||||
}
|
||||
|
||||
@@ -1209,7 +1126,7 @@ bool microtcp_mux_register(microtcp_mux_t *mux, microtcp_socket_t *sock, int eve
|
||||
if (mux->free_list == NULL) {
|
||||
// The entry limit was reached.
|
||||
// It's impossible to register the socket at this time
|
||||
UNLOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_unlock(&mux->mtcp->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1243,7 +1160,7 @@ bool microtcp_mux_register(microtcp_mux_t *mux, microtcp_socket_t *sock, int eve
|
||||
|
||||
entry->events_of_interest |= events;
|
||||
|
||||
UNLOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_unlock(&mux->mtcp->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1284,19 +1201,15 @@ static bool mux_poll(microtcp_mux_t *mux, microtcp_muxevent_t *ev)
|
||||
|
||||
bool microtcp_mux_wait(microtcp_mux_t *mux, microtcp_muxevent_t *ev)
|
||||
{
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
LOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_lock(&mux->mtcp->lock);
|
||||
while (!mux_poll(mux, ev)) {
|
||||
MICROTCP_DEBUG_LOG("Multiplexer waiting for an event");
|
||||
if (cnd_wait(&mux->queue_not_empty, &mux->mtcp->lock) != thrd_success)
|
||||
abort();
|
||||
MICROTCP_DEBUG_LOG("Multiplexer woke up for an event");
|
||||
}
|
||||
UNLOCK_WHEN_THREADED(mux->mtcp);
|
||||
mtx_unlock(&mux->mtcp->lock);
|
||||
return true;
|
||||
#else
|
||||
return mux_poll(mux, ev);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1350,17 +1263,12 @@ signal_events_to_muxes_associated_to_socket(microtcp_socket_t *socket, int event
|
||||
entry->mux_next = NULL;
|
||||
mux->ready_queue_tail = entry;
|
||||
|
||||
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||
MICROTCP_DEBUG_LOG("Signaling event to multiplexer");
|
||||
if (queue_was_empty)
|
||||
cnd_signal(&mux->queue_not_empty);
|
||||
MICROTCP_DEBUG_LOG("Signaled event to multiplexer");
|
||||
#else
|
||||
(void) queue_was_empty;
|
||||
#endif
|
||||
}
|
||||
entry = entry->sock_next;
|
||||
}
|
||||
MICROTCP_DEBUG_LOG("Socket signaled to multiplexers");
|
||||
}
|
||||
#endif
|
||||
+3
-3
@@ -1,3 +1,5 @@
|
||||
#ifndef MICROTCP_H
|
||||
#define MICROTCP_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -44,10 +46,8 @@ typedef struct {
|
||||
int (*recv)(void *data, void *dst, size_t len);
|
||||
} microtcp_callbacks_t;
|
||||
|
||||
#ifdef MICROTCP_USING_TAP
|
||||
bool microtcp_callbacks_create_for_tap(const char *ip, const char *mac, microtcp_callbacks_t *callbacks);
|
||||
microtcp_t *microtcp_create(const char *tap_ip, const char *stack_ip, const char *tap_mac, const char *stack_mac);
|
||||
#endif
|
||||
|
||||
microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac, microtcp_callbacks_t callbacks);
|
||||
void microtcp_destroy(microtcp_t *mtcp);
|
||||
@@ -60,7 +60,6 @@ size_t microtcp_recv(microtcp_socket_t *socket, void *dst, siz
|
||||
void microtcp_step(microtcp_t *mtcp);
|
||||
void microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len);
|
||||
|
||||
#ifdef MICROTCP_USING_MUX
|
||||
typedef enum {
|
||||
MICROTCP_MUX_ACCEPT = 1,
|
||||
MICROTCP_MUX_RECV = 2,
|
||||
@@ -79,4 +78,5 @@ void microtcp_mux_destroy(microtcp_mux_t *mux);
|
||||
bool microtcp_mux_register(microtcp_mux_t *mux, microtcp_socket_t *sock, int events, void *userp);
|
||||
bool microtcp_mux_unregister(microtcp_mux_t *mux, microtcp_socket_t *sock, int events);
|
||||
bool microtcp_mux_wait(microtcp_mux_t *mux, microtcp_muxevent_t *ev);
|
||||
|
||||
#endif
|
||||
@@ -1,11 +1,8 @@
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdalign.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
# include "endian.h"
|
||||
# include "tcp.h"
|
||||
#endif
|
||||
#include "endian.h"
|
||||
#include "tcp.h"
|
||||
|
||||
#ifdef TCP_DEBUG
|
||||
# include <stdio.h>
|
||||
@@ -444,8 +441,8 @@ static void retransmit(tcp_connection_t *c)
|
||||
|
||||
size_t retr_queue_bytes = c->snd_nxt - c->snd_una;
|
||||
|
||||
assert(retr_queue_bytes > 0); // If there were no bytes to ACK,
|
||||
// there would be no active timer.
|
||||
//assert(retr_queue_bytes > 0); // If there were no bytes to ACK,
|
||||
// there would be no active timer.
|
||||
|
||||
size_t retr_queue_ghost = 0;
|
||||
if (c->waiting_ack_for_syn) retr_queue_ghost++;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "defs.h"
|
||||
#include "tcp_timer.h"
|
||||
#endif
|
||||
|
||||
#define TCP_TIMEOUT_TIME_WAIT 1000
|
||||
//240000
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "tcp_timer.h"
|
||||
#endif
|
||||
|
||||
void tcp_timerset_init(tcp_timerset_t *set)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,931 @@
|
||||
/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*-
|
||||
Copyright (c) 2012 Marcus Geelnard
|
||||
Copyright (c) 2013-2016 Evan Nemerson
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#include "tinycthread.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Platform specific includes */
|
||||
#if defined(_TTHREAD_POSIX_)
|
||||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <errno.h>
|
||||
#elif defined(_TTHREAD_WIN32_)
|
||||
#include <process.h>
|
||||
#include <sys/timeb.h>
|
||||
#endif
|
||||
|
||||
/* Standard, good-to-have defines */
|
||||
#ifndef NULL
|
||||
#define NULL (void*)0
|
||||
#endif
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
int mtx_init(mtx_t *mtx, int type)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
mtx->mAlreadyLocked = FALSE;
|
||||
mtx->mRecursive = type & mtx_recursive;
|
||||
mtx->mTimed = type & mtx_timed;
|
||||
if (!mtx->mTimed)
|
||||
{
|
||||
InitializeCriticalSection(&(mtx->mHandle.cs));
|
||||
}
|
||||
else
|
||||
{
|
||||
mtx->mHandle.mut = CreateMutex(NULL, FALSE, NULL);
|
||||
if (mtx->mHandle.mut == NULL)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
return thrd_success;
|
||||
#else
|
||||
int ret;
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
if (type & mtx_recursive)
|
||||
{
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
}
|
||||
ret = pthread_mutex_init(mtx, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
return ret == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mtx_destroy(mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
if (!mtx->mTimed)
|
||||
{
|
||||
DeleteCriticalSection(&(mtx->mHandle.cs));
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseHandle(mtx->mHandle.mut);
|
||||
}
|
||||
#else
|
||||
pthread_mutex_destroy(mtx);
|
||||
#endif
|
||||
}
|
||||
|
||||
int mtx_lock(mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
if (!mtx->mTimed)
|
||||
{
|
||||
EnterCriticalSection(&(mtx->mHandle.cs));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (WaitForSingleObject(mtx->mHandle.mut, INFINITE))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
break;
|
||||
case WAIT_ABANDONED:
|
||||
default:
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mtx->mRecursive)
|
||||
{
|
||||
while(mtx->mAlreadyLocked) Sleep(1); /* Simulate deadlock... */
|
||||
mtx->mAlreadyLocked = TRUE;
|
||||
}
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_mutex_lock(mtx) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
int mtx_timedlock(mtx_t *mtx, const struct timespec *ts)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct timespec current_ts;
|
||||
DWORD timeoutMs;
|
||||
|
||||
if (!mtx->mTimed)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
timespec_get(¤t_ts, TIME_UTC);
|
||||
|
||||
if ((current_ts.tv_sec > ts->tv_sec) || ((current_ts.tv_sec == ts->tv_sec) && (current_ts.tv_nsec >= ts->tv_nsec)))
|
||||
{
|
||||
timeoutMs = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
timeoutMs = (DWORD)(ts->tv_sec - current_ts.tv_sec) * 1000;
|
||||
timeoutMs += (ts->tv_nsec - current_ts.tv_nsec) / 1000000;
|
||||
timeoutMs += 1;
|
||||
}
|
||||
|
||||
/* TODO: the timeout for WaitForSingleObject doesn't include time
|
||||
while the computer is asleep. */
|
||||
switch (WaitForSingleObject(mtx->mHandle.mut, timeoutMs))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
break;
|
||||
case WAIT_TIMEOUT:
|
||||
return thrd_timedout;
|
||||
case WAIT_ABANDONED:
|
||||
default:
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
if (!mtx->mRecursive)
|
||||
{
|
||||
while(mtx->mAlreadyLocked) Sleep(1); /* Simulate deadlock... */
|
||||
mtx->mAlreadyLocked = TRUE;
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
#elif defined(_POSIX_TIMEOUTS) && (_POSIX_TIMEOUTS >= 200112L) && defined(_POSIX_THREADS) && (_POSIX_THREADS >= 200112L)
|
||||
switch (pthread_mutex_timedlock(mtx, ts)) {
|
||||
case 0:
|
||||
return thrd_success;
|
||||
case ETIMEDOUT:
|
||||
return thrd_timedout;
|
||||
default:
|
||||
return thrd_error;
|
||||
}
|
||||
#else
|
||||
int rc;
|
||||
struct timespec cur, dur;
|
||||
|
||||
/* Try to acquire the lock and, if we fail, sleep for 5ms. */
|
||||
while ((rc = pthread_mutex_trylock (mtx)) == EBUSY) {
|
||||
timespec_get(&cur, TIME_UTC);
|
||||
|
||||
if ((cur.tv_sec > ts->tv_sec) || ((cur.tv_sec == ts->tv_sec) && (cur.tv_nsec >= ts->tv_nsec)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
dur.tv_sec = ts->tv_sec - cur.tv_sec;
|
||||
dur.tv_nsec = ts->tv_nsec - cur.tv_nsec;
|
||||
if (dur.tv_nsec < 0)
|
||||
{
|
||||
dur.tv_sec--;
|
||||
dur.tv_nsec += 1000000000;
|
||||
}
|
||||
|
||||
if ((dur.tv_sec != 0) || (dur.tv_nsec > 5000000))
|
||||
{
|
||||
dur.tv_sec = 0;
|
||||
dur.tv_nsec = 5000000;
|
||||
}
|
||||
|
||||
nanosleep(&dur, NULL);
|
||||
}
|
||||
|
||||
switch (rc) {
|
||||
case 0:
|
||||
return thrd_success;
|
||||
case ETIMEDOUT:
|
||||
case EBUSY:
|
||||
return thrd_timedout;
|
||||
default:
|
||||
return thrd_error;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int mtx_trylock(mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
int ret;
|
||||
|
||||
if (!mtx->mTimed)
|
||||
{
|
||||
ret = TryEnterCriticalSection(&(mtx->mHandle.cs)) ? thrd_success : thrd_busy;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = (WaitForSingleObject(mtx->mHandle.mut, 0) == WAIT_OBJECT_0) ? thrd_success : thrd_busy;
|
||||
}
|
||||
|
||||
if ((!mtx->mRecursive) && (ret == thrd_success))
|
||||
{
|
||||
if (mtx->mAlreadyLocked)
|
||||
{
|
||||
LeaveCriticalSection(&(mtx->mHandle.cs));
|
||||
ret = thrd_busy;
|
||||
}
|
||||
else
|
||||
{
|
||||
mtx->mAlreadyLocked = TRUE;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
#else
|
||||
return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy;
|
||||
#endif
|
||||
}
|
||||
|
||||
int mtx_unlock(mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
mtx->mAlreadyLocked = FALSE;
|
||||
if (!mtx->mTimed)
|
||||
{
|
||||
LeaveCriticalSection(&(mtx->mHandle.cs));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ReleaseMutex(mtx->mHandle.mut))
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_mutex_unlock(mtx) == 0 ? thrd_success : thrd_error;;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
#define _CONDITION_EVENT_ONE 0
|
||||
#define _CONDITION_EVENT_ALL 1
|
||||
#endif
|
||||
|
||||
int cnd_init(cnd_t *cond)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
cond->mWaitersCount = 0;
|
||||
|
||||
/* Init critical section */
|
||||
InitializeCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* Init events */
|
||||
cond->mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (cond->mEvents[_CONDITION_EVENT_ONE] == NULL)
|
||||
{
|
||||
cond->mEvents[_CONDITION_EVENT_ALL] = NULL;
|
||||
return thrd_error;
|
||||
}
|
||||
cond->mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (cond->mEvents[_CONDITION_EVENT_ALL] == NULL)
|
||||
{
|
||||
CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]);
|
||||
cond->mEvents[_CONDITION_EVENT_ONE] = NULL;
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_cond_init(cond, NULL) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
void cnd_destroy(cnd_t *cond)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
if (cond->mEvents[_CONDITION_EVENT_ONE] != NULL)
|
||||
{
|
||||
CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]);
|
||||
}
|
||||
if (cond->mEvents[_CONDITION_EVENT_ALL] != NULL)
|
||||
{
|
||||
CloseHandle(cond->mEvents[_CONDITION_EVENT_ALL]);
|
||||
}
|
||||
DeleteCriticalSection(&cond->mWaitersCountLock);
|
||||
#else
|
||||
pthread_cond_destroy(cond);
|
||||
#endif
|
||||
}
|
||||
|
||||
int cnd_signal(cnd_t *cond)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
int haveWaiters;
|
||||
|
||||
/* Are there any waiters? */
|
||||
EnterCriticalSection(&cond->mWaitersCountLock);
|
||||
haveWaiters = (cond->mWaitersCount > 0);
|
||||
LeaveCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* If we have any waiting threads, send them a signal */
|
||||
if(haveWaiters)
|
||||
{
|
||||
if (SetEvent(cond->mEvents[_CONDITION_EVENT_ONE]) == 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
int cnd_broadcast(cnd_t *cond)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
int haveWaiters;
|
||||
|
||||
/* Are there any waiters? */
|
||||
EnterCriticalSection(&cond->mWaitersCountLock);
|
||||
haveWaiters = (cond->mWaitersCount > 0);
|
||||
LeaveCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* If we have any waiting threads, send them a signal */
|
||||
if(haveWaiters)
|
||||
{
|
||||
if (SetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
#else
|
||||
return pthread_cond_broadcast(cond) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
static int _cnd_timedwait_win32(cnd_t *cond, mtx_t *mtx, DWORD timeout)
|
||||
{
|
||||
DWORD result;
|
||||
int lastWaiter;
|
||||
|
||||
/* Increment number of waiters */
|
||||
EnterCriticalSection(&cond->mWaitersCountLock);
|
||||
++ cond->mWaitersCount;
|
||||
LeaveCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* Release the mutex while waiting for the condition (will decrease
|
||||
the number of waiters when done)... */
|
||||
mtx_unlock(mtx);
|
||||
|
||||
/* Wait for either event to become signaled due to cnd_signal() or
|
||||
cnd_broadcast() being called */
|
||||
result = WaitForMultipleObjects(2, cond->mEvents, FALSE, timeout);
|
||||
if (result == WAIT_TIMEOUT)
|
||||
{
|
||||
/* The mutex is locked again before the function returns, even if an error occurred */
|
||||
mtx_lock(mtx);
|
||||
return thrd_timedout;
|
||||
}
|
||||
else if (result == WAIT_FAILED)
|
||||
{
|
||||
/* The mutex is locked again before the function returns, even if an error occurred */
|
||||
mtx_lock(mtx);
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
/* Check if we are the last waiter */
|
||||
EnterCriticalSection(&cond->mWaitersCountLock);
|
||||
-- cond->mWaitersCount;
|
||||
lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) &&
|
||||
(cond->mWaitersCount == 0);
|
||||
LeaveCriticalSection(&cond->mWaitersCountLock);
|
||||
|
||||
/* If we are the last waiter to be notified to stop waiting, reset the event */
|
||||
if (lastWaiter)
|
||||
{
|
||||
if (ResetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0)
|
||||
{
|
||||
/* The mutex is locked again before the function returns, even if an error occurred */
|
||||
mtx_lock(mtx);
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Re-acquire the mutex */
|
||||
mtx_lock(mtx);
|
||||
|
||||
return thrd_success;
|
||||
}
|
||||
#endif
|
||||
|
||||
int cnd_wait(cnd_t *cond, mtx_t *mtx)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
return _cnd_timedwait_win32(cond, mtx, INFINITE);
|
||||
#else
|
||||
return pthread_cond_wait(cond, mtx) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct timespec now;
|
||||
if (timespec_get(&now, TIME_UTC) == TIME_UTC)
|
||||
{
|
||||
unsigned long long nowInMilliseconds = now.tv_sec * 1000 + now.tv_nsec / 1000000;
|
||||
unsigned long long tsInMilliseconds = ts->tv_sec * 1000 + ts->tv_nsec / 1000000;
|
||||
DWORD delta = (tsInMilliseconds > nowInMilliseconds) ?
|
||||
(DWORD)(tsInMilliseconds - nowInMilliseconds) : 0;
|
||||
return _cnd_timedwait_win32(cond, mtx, delta);
|
||||
}
|
||||
else
|
||||
return thrd_error;
|
||||
#else
|
||||
int ret;
|
||||
ret = pthread_cond_timedwait(cond, mtx, ts);
|
||||
if (ret == ETIMEDOUT)
|
||||
{
|
||||
return thrd_timedout;
|
||||
}
|
||||
return ret == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct TinyCThreadTSSData {
|
||||
void* value;
|
||||
tss_t key;
|
||||
struct TinyCThreadTSSData* next;
|
||||
};
|
||||
|
||||
static tss_dtor_t _tinycthread_tss_dtors[1088] = { NULL, };
|
||||
|
||||
static _Thread_local struct TinyCThreadTSSData* _tinycthread_tss_head = NULL;
|
||||
static _Thread_local struct TinyCThreadTSSData* _tinycthread_tss_tail = NULL;
|
||||
|
||||
static void _tinycthread_tss_cleanup (void);
|
||||
|
||||
static void _tinycthread_tss_cleanup (void) {
|
||||
struct TinyCThreadTSSData* data;
|
||||
int iteration;
|
||||
unsigned int again = 1;
|
||||
void* value;
|
||||
|
||||
for (iteration = 0 ; iteration < TSS_DTOR_ITERATIONS && again > 0 ; iteration++)
|
||||
{
|
||||
again = 0;
|
||||
for (data = _tinycthread_tss_head ; data != NULL ; data = data->next)
|
||||
{
|
||||
if (data->value != NULL)
|
||||
{
|
||||
value = data->value;
|
||||
data->value = NULL;
|
||||
|
||||
if (_tinycthread_tss_dtors[data->key] != NULL)
|
||||
{
|
||||
again = 1;
|
||||
_tinycthread_tss_dtors[data->key](value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (_tinycthread_tss_head != NULL) {
|
||||
data = _tinycthread_tss_head->next;
|
||||
free (_tinycthread_tss_head);
|
||||
_tinycthread_tss_head = data;
|
||||
}
|
||||
_tinycthread_tss_head = NULL;
|
||||
_tinycthread_tss_tail = NULL;
|
||||
}
|
||||
|
||||
static void NTAPI _tinycthread_tss_callback(PVOID h, DWORD dwReason, PVOID pv)
|
||||
{
|
||||
(void)h;
|
||||
(void)pv;
|
||||
|
||||
if (_tinycthread_tss_head != NULL && (dwReason == DLL_THREAD_DETACH || dwReason == DLL_PROCESS_DETACH))
|
||||
{
|
||||
_tinycthread_tss_cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#ifdef _M_X64
|
||||
#pragma const_seg(".CRT$XLB")
|
||||
#else
|
||||
#pragma data_seg(".CRT$XLB")
|
||||
#endif
|
||||
PIMAGE_TLS_CALLBACK p_thread_callback = _tinycthread_tss_callback;
|
||||
#ifdef _M_X64
|
||||
#pragma data_seg()
|
||||
#else
|
||||
#pragma const_seg()
|
||||
#endif
|
||||
#else
|
||||
PIMAGE_TLS_CALLBACK p_thread_callback __attribute__((section(".CRT$XLB"))) = _tinycthread_tss_callback;
|
||||
#endif
|
||||
|
||||
#endif /* defined(_TTHREAD_WIN32_) */
|
||||
|
||||
/** Information to pass to the new thread (what to run). */
|
||||
typedef struct {
|
||||
thrd_start_t mFunction; /**< Pointer to the function to be executed. */
|
||||
void * mArg; /**< Function argument for the thread function. */
|
||||
} _thread_start_info;
|
||||
|
||||
/* Thread wrapper function. */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
static DWORD WINAPI _thrd_wrapper_function(LPVOID aArg)
|
||||
#elif defined(_TTHREAD_POSIX_)
|
||||
static void * _thrd_wrapper_function(void * aArg)
|
||||
#endif
|
||||
{
|
||||
thrd_start_t fun;
|
||||
void *arg;
|
||||
int res;
|
||||
|
||||
/* Get thread startup information */
|
||||
_thread_start_info *ti = (_thread_start_info *) aArg;
|
||||
fun = ti->mFunction;
|
||||
arg = ti->mArg;
|
||||
|
||||
/* The thread is responsible for freeing the startup information */
|
||||
free((void *)ti);
|
||||
|
||||
/* Call the actual client thread function */
|
||||
res = fun(arg);
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
if (_tinycthread_tss_head != NULL)
|
||||
{
|
||||
_tinycthread_tss_cleanup();
|
||||
}
|
||||
|
||||
return (DWORD)res;
|
||||
#else
|
||||
return (void*)(intptr_t)res;
|
||||
#endif
|
||||
}
|
||||
|
||||
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg)
|
||||
{
|
||||
/* Fill out the thread startup information (passed to the thread wrapper,
|
||||
which will eventually free it) */
|
||||
_thread_start_info* ti = (_thread_start_info*)malloc(sizeof(_thread_start_info));
|
||||
if (ti == NULL)
|
||||
{
|
||||
return thrd_nomem;
|
||||
}
|
||||
ti->mFunction = func;
|
||||
ti->mArg = arg;
|
||||
|
||||
/* Create the thread */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
*thr = CreateThread(NULL, 0, _thrd_wrapper_function, (LPVOID) ti, 0, NULL);
|
||||
#elif defined(_TTHREAD_POSIX_)
|
||||
if(pthread_create(thr, NULL, _thrd_wrapper_function, (void *)ti) != 0)
|
||||
{
|
||||
*thr = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Did we fail to create the thread? */
|
||||
if(!*thr)
|
||||
{
|
||||
free(ti);
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
thrd_t thrd_current(void)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
return GetCurrentThread();
|
||||
#else
|
||||
return pthread_self();
|
||||
#endif
|
||||
}
|
||||
|
||||
int thrd_detach(thrd_t thr)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
/* https://stackoverflow.com/questions/12744324/how-to-detach-a-thread-on-windows-c#answer-12746081 */
|
||||
return CloseHandle(thr) != 0 ? thrd_success : thrd_error;
|
||||
#else
|
||||
return pthread_detach(thr) == 0 ? thrd_success : thrd_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
int thrd_equal(thrd_t thr0, thrd_t thr1)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
return GetThreadId(thr0) == GetThreadId(thr1);
|
||||
#else
|
||||
return pthread_equal(thr0, thr1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void thrd_exit(int res)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
if (_tinycthread_tss_head != NULL)
|
||||
{
|
||||
_tinycthread_tss_cleanup();
|
||||
}
|
||||
|
||||
ExitThread((DWORD)res);
|
||||
#else
|
||||
pthread_exit((void*)(intptr_t)res);
|
||||
#endif
|
||||
}
|
||||
|
||||
int thrd_join(thrd_t thr, int *res)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
DWORD dwRes;
|
||||
|
||||
if (WaitForSingleObject(thr, INFINITE) == WAIT_FAILED)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
if (res != NULL)
|
||||
{
|
||||
if (GetExitCodeThread(thr, &dwRes) != 0)
|
||||
{
|
||||
*res = (int) dwRes;
|
||||
}
|
||||
else
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
CloseHandle(thr);
|
||||
#elif defined(_TTHREAD_POSIX_)
|
||||
void *pres;
|
||||
if (pthread_join(thr, &pres) != 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
if (res != NULL)
|
||||
{
|
||||
*res = (int)(intptr_t)pres;
|
||||
}
|
||||
#endif
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
int thrd_sleep(const struct timespec *duration, struct timespec *remaining)
|
||||
{
|
||||
#if !defined(_TTHREAD_WIN32_)
|
||||
int res = nanosleep(duration, remaining);
|
||||
if (res == 0) {
|
||||
return 0;
|
||||
} else if (errno == EINTR) {
|
||||
return -1;
|
||||
} else {
|
||||
return -2;
|
||||
}
|
||||
#else
|
||||
struct timespec start;
|
||||
DWORD t;
|
||||
|
||||
timespec_get(&start, TIME_UTC);
|
||||
|
||||
t = SleepEx((DWORD)(duration->tv_sec * 1000 +
|
||||
duration->tv_nsec / 1000000 +
|
||||
(((duration->tv_nsec % 1000000) == 0) ? 0 : 1)),
|
||||
TRUE);
|
||||
|
||||
if (t == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
if (remaining != NULL) {
|
||||
timespec_get(remaining, TIME_UTC);
|
||||
remaining->tv_sec -= start.tv_sec;
|
||||
remaining->tv_nsec -= start.tv_nsec;
|
||||
if (remaining->tv_nsec < 0)
|
||||
{
|
||||
remaining->tv_nsec += 1000000000;
|
||||
remaining->tv_sec -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return (t == WAIT_IO_COMPLETION) ? -1 : -2;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void thrd_yield(void)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
Sleep(0);
|
||||
#else
|
||||
sched_yield();
|
||||
#endif
|
||||
}
|
||||
|
||||
int tss_create(tss_t *key, tss_dtor_t dtor)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
*key = TlsAlloc();
|
||||
if (*key == TLS_OUT_OF_INDEXES)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
_tinycthread_tss_dtors[*key] = dtor;
|
||||
#else
|
||||
if (pthread_key_create(key, dtor) != 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
#endif
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
void tss_delete(tss_t key)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct TinyCThreadTSSData* data = (struct TinyCThreadTSSData*) TlsGetValue (key);
|
||||
struct TinyCThreadTSSData* prev = NULL;
|
||||
if (data != NULL)
|
||||
{
|
||||
if (data == _tinycthread_tss_head)
|
||||
{
|
||||
_tinycthread_tss_head = data->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = _tinycthread_tss_head;
|
||||
if (prev != NULL)
|
||||
{
|
||||
while (prev->next != data)
|
||||
{
|
||||
prev = prev->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data == _tinycthread_tss_tail)
|
||||
{
|
||||
_tinycthread_tss_tail = prev;
|
||||
}
|
||||
|
||||
free (data);
|
||||
}
|
||||
_tinycthread_tss_dtors[key] = NULL;
|
||||
TlsFree(key);
|
||||
#else
|
||||
pthread_key_delete(key);
|
||||
#endif
|
||||
}
|
||||
|
||||
void *tss_get(tss_t key)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct TinyCThreadTSSData* data = (struct TinyCThreadTSSData*)TlsGetValue(key);
|
||||
if (data == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return data->value;
|
||||
#else
|
||||
return pthread_getspecific(key);
|
||||
#endif
|
||||
}
|
||||
|
||||
int tss_set(tss_t key, void *val)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct TinyCThreadTSSData* data = (struct TinyCThreadTSSData*)TlsGetValue(key);
|
||||
if (data == NULL)
|
||||
{
|
||||
data = (struct TinyCThreadTSSData*)malloc(sizeof(struct TinyCThreadTSSData));
|
||||
if (data == NULL)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
data->value = NULL;
|
||||
data->key = key;
|
||||
data->next = NULL;
|
||||
|
||||
if (_tinycthread_tss_tail != NULL)
|
||||
{
|
||||
_tinycthread_tss_tail->next = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tinycthread_tss_tail = data;
|
||||
}
|
||||
|
||||
if (_tinycthread_tss_head == NULL)
|
||||
{
|
||||
_tinycthread_tss_head = data;
|
||||
}
|
||||
|
||||
if (!TlsSetValue(key, data))
|
||||
{
|
||||
free (data);
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
data->value = val;
|
||||
#else
|
||||
if (pthread_setspecific(key, val) != 0)
|
||||
{
|
||||
return thrd_error;
|
||||
}
|
||||
#endif
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
#if defined(_TTHREAD_EMULATE_TIMESPEC_GET_)
|
||||
int _tthread_timespec_get(struct timespec *ts, int base)
|
||||
{
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct _timeb tb;
|
||||
#elif !defined(CLOCK_REALTIME)
|
||||
struct timeval tv;
|
||||
#endif
|
||||
|
||||
if (base != TIME_UTC)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
_ftime_s(&tb);
|
||||
ts->tv_sec = (time_t)tb.time;
|
||||
ts->tv_nsec = 1000000L * (long)tb.millitm;
|
||||
#elif defined(CLOCK_REALTIME)
|
||||
base = (clock_gettime(CLOCK_REALTIME, ts) == 0) ? base : 0;
|
||||
#else
|
||||
gettimeofday(&tv, NULL);
|
||||
ts->tv_sec = (time_t)tv.tv_sec;
|
||||
ts->tv_nsec = 1000L * (long)tv.tv_usec;
|
||||
#endif
|
||||
|
||||
return base;
|
||||
}
|
||||
#endif /* _TTHREAD_EMULATE_TIMESPEC_GET_ */
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
void call_once(once_flag *flag, void (*func)(void))
|
||||
{
|
||||
/* The idea here is that we use a spin lock (via the
|
||||
InterlockedCompareExchange function) to restrict access to the
|
||||
critical section until we have initialized it, then we use the
|
||||
critical section to block until the callback has completed
|
||||
execution. */
|
||||
while (flag->status < 3)
|
||||
{
|
||||
switch (flag->status)
|
||||
{
|
||||
case 0:
|
||||
if (InterlockedCompareExchange (&(flag->status), 1, 0) == 0) {
|
||||
InitializeCriticalSection(&(flag->lock));
|
||||
EnterCriticalSection(&(flag->lock));
|
||||
flag->status = 2;
|
||||
func();
|
||||
flag->status = 3;
|
||||
LeaveCriticalSection(&(flag->lock));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
EnterCriticalSection(&(flag->lock));
|
||||
LeaveCriticalSection(&(flag->lock));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* defined(_TTHREAD_WIN32_) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,479 @@
|
||||
/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*-
|
||||
Copyright (c) 2012 Marcus Geelnard
|
||||
Copyright (c) 2013-2016 Evan Nemerson
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#ifndef _TINYCTHREAD_H_
|
||||
#define _TINYCTHREAD_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @mainpage TinyCThread API Reference
|
||||
*
|
||||
* @section intro_sec Introduction
|
||||
* TinyCThread is a minimal, portable implementation of basic threading
|
||||
* classes for C.
|
||||
*
|
||||
* They closely mimic the functionality and naming of the C11 standard, and
|
||||
* should be easily replaceable with the corresponding standard variants.
|
||||
*
|
||||
* @section port_sec Portability
|
||||
* The Win32 variant uses the native Win32 API for implementing the thread
|
||||
* classes, while for other systems, the POSIX threads API (pthread) is used.
|
||||
*
|
||||
* @section misc_sec Miscellaneous
|
||||
* The following special keywords are available: #_Thread_local.
|
||||
*
|
||||
* For more detailed information, browse the different sections of this
|
||||
* documentation. A good place to start is:
|
||||
* tinycthread.h.
|
||||
*/
|
||||
|
||||
/* Which platform are we on? */
|
||||
#if !defined(_TTHREAD_PLATFORM_DEFINED_)
|
||||
#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
|
||||
#define _TTHREAD_WIN32_
|
||||
#else
|
||||
#define _TTHREAD_POSIX_
|
||||
#endif
|
||||
#define _TTHREAD_PLATFORM_DEFINED_
|
||||
#endif
|
||||
|
||||
/* Activate some POSIX functionality (e.g. clock_gettime and recursive mutexes) */
|
||||
#if defined(_TTHREAD_POSIX_)
|
||||
#undef _FEATURES_H
|
||||
#if !defined(_GNU_SOURCE)
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#if !defined(_POSIX_C_SOURCE) || ((_POSIX_C_SOURCE - 0) < 199309L)
|
||||
#undef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
#endif
|
||||
#if !defined(_XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 500)
|
||||
#undef _XOPEN_SOURCE
|
||||
#define _XOPEN_SOURCE 500
|
||||
#endif
|
||||
#define _XPG6
|
||||
#endif
|
||||
|
||||
/* Generic includes */
|
||||
#include <time.h>
|
||||
|
||||
/* Platform specific includes */
|
||||
#if defined(_TTHREAD_POSIX_)
|
||||
#include <pthread.h>
|
||||
#elif defined(_TTHREAD_WIN32_)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define __UNDEF_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#ifdef __UNDEF_LEAN_AND_MEAN
|
||||
#undef WIN32_LEAN_AND_MEAN
|
||||
#undef __UNDEF_LEAN_AND_MEAN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Compiler-specific information */
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||
#define TTHREAD_NORETURN _Noreturn
|
||||
#elif defined(__GNUC__)
|
||||
#define TTHREAD_NORETURN __attribute__((__noreturn__))
|
||||
#else
|
||||
#define TTHREAD_NORETURN
|
||||
#endif
|
||||
|
||||
/* If TIME_UTC is missing, provide it and provide a wrapper for
|
||||
timespec_get. */
|
||||
#ifndef TIME_UTC
|
||||
#define TIME_UTC 1
|
||||
#define _TTHREAD_EMULATE_TIMESPEC_GET_
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
struct _tthread_timespec {
|
||||
time_t tv_sec;
|
||||
long tv_nsec;
|
||||
};
|
||||
#define timespec _tthread_timespec
|
||||
#endif
|
||||
|
||||
int _tthread_timespec_get(struct timespec *ts, int base);
|
||||
#define timespec_get _tthread_timespec_get
|
||||
#endif
|
||||
|
||||
/** TinyCThread version (major number). */
|
||||
#define TINYCTHREAD_VERSION_MAJOR 1
|
||||
/** TinyCThread version (minor number). */
|
||||
#define TINYCTHREAD_VERSION_MINOR 2
|
||||
/** TinyCThread version (full version). */
|
||||
#define TINYCTHREAD_VERSION (TINYCTHREAD_VERSION_MAJOR * 100 + TINYCTHREAD_VERSION_MINOR)
|
||||
|
||||
/**
|
||||
* @def _Thread_local
|
||||
* Thread local storage keyword.
|
||||
* A variable that is declared with the @c _Thread_local keyword makes the
|
||||
* value of the variable local to each thread (known as thread-local storage,
|
||||
* or TLS). Example usage:
|
||||
* @code
|
||||
* // This variable is local to each thread.
|
||||
* _Thread_local int variable;
|
||||
* @endcode
|
||||
* @note The @c _Thread_local keyword is a macro that maps to the corresponding
|
||||
* compiler directive (e.g. @c __declspec(thread)).
|
||||
* @note This directive is currently not supported on Mac OS X (it will give
|
||||
* a compiler error), since compile-time TLS is not supported in the Mac OS X
|
||||
* executable format. Also, some older versions of MinGW (before GCC 4.x) do
|
||||
* not support this directive, nor does the Tiny C Compiler.
|
||||
* @hideinitializer
|
||||
*/
|
||||
|
||||
#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) && !defined(_Thread_local)
|
||||
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__)
|
||||
#define _Thread_local __thread
|
||||
#else
|
||||
#define _Thread_local __declspec(thread)
|
||||
#endif
|
||||
#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && (((__GNUC__ << 8) | __GNUC_MINOR__) < ((4 << 8) | 9))
|
||||
#define _Thread_local __thread
|
||||
#endif
|
||||
|
||||
/* Macros */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
#define TSS_DTOR_ITERATIONS (4)
|
||||
#else
|
||||
#define TSS_DTOR_ITERATIONS PTHREAD_DESTRUCTOR_ITERATIONS
|
||||
#endif
|
||||
|
||||
/* Function return values */
|
||||
#define thrd_error 0 /**< The requested operation failed */
|
||||
#define thrd_success 1 /**< The requested operation succeeded */
|
||||
#define thrd_timedout 2 /**< The time specified in the call was reached without acquiring the requested resource */
|
||||
#define thrd_busy 3 /**< The requested operation failed because a tesource requested by a test and return function is already in use */
|
||||
#define thrd_nomem 4 /**< The requested operation failed because it was unable to allocate memory */
|
||||
|
||||
/* Mutex types */
|
||||
#define mtx_plain 0
|
||||
#define mtx_timed 1
|
||||
#define mtx_recursive 2
|
||||
|
||||
/* Mutex */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
typedef struct {
|
||||
union {
|
||||
CRITICAL_SECTION cs; /* Critical section handle (used for non-timed mutexes) */
|
||||
HANDLE mut; /* Mutex handle (used for timed mutex) */
|
||||
} mHandle; /* Mutex handle */
|
||||
int mAlreadyLocked; /* TRUE if the mutex is already locked */
|
||||
int mRecursive; /* TRUE if the mutex is recursive */
|
||||
int mTimed; /* TRUE if the mutex is timed */
|
||||
} mtx_t;
|
||||
#else
|
||||
typedef pthread_mutex_t mtx_t;
|
||||
#endif
|
||||
|
||||
/** Create a mutex object.
|
||||
* @param mtx A mutex object.
|
||||
* @param type Bit-mask that must have one of the following six values:
|
||||
* @li @c mtx_plain for a simple non-recursive mutex
|
||||
* @li @c mtx_timed for a non-recursive mutex that supports timeout
|
||||
* @li @c mtx_plain | @c mtx_recursive (same as @c mtx_plain, but recursive)
|
||||
* @li @c mtx_timed | @c mtx_recursive (same as @c mtx_timed, but recursive)
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int mtx_init(mtx_t *mtx, int type);
|
||||
|
||||
/** Release any resources used by the given mutex.
|
||||
* @param mtx A mutex object.
|
||||
*/
|
||||
void mtx_destroy(mtx_t *mtx);
|
||||
|
||||
/** Lock the given mutex.
|
||||
* Blocks until the given mutex can be locked. If the mutex is non-recursive, and
|
||||
* the calling thread already has a lock on the mutex, this call will block
|
||||
* forever.
|
||||
* @param mtx A mutex object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int mtx_lock(mtx_t *mtx);
|
||||
|
||||
/** Lock the given mutex, or block until a specific point in time.
|
||||
* Blocks until either the given mutex can be locked, or the specified TIME_UTC
|
||||
* based time.
|
||||
* @param mtx A mutex object.
|
||||
* @param ts A UTC based calendar time
|
||||
* @return @ref The mtx_timedlock function returns thrd_success on success, or
|
||||
* thrd_timedout if the time specified was reached without acquiring the
|
||||
* requested resource, or thrd_error if the request could not be honored.
|
||||
*/
|
||||
int mtx_timedlock(mtx_t *mtx, const struct timespec *ts);
|
||||
|
||||
/** Try to lock the given mutex.
|
||||
* The specified mutex shall support either test and return or timeout. If the
|
||||
* mutex is already locked, the function returns without blocking.
|
||||
* @param mtx A mutex object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_busy if the resource
|
||||
* requested is already in use, or @ref thrd_error if the request could not be
|
||||
* honored.
|
||||
*/
|
||||
int mtx_trylock(mtx_t *mtx);
|
||||
|
||||
/** Unlock the given mutex.
|
||||
* @param mtx A mutex object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int mtx_unlock(mtx_t *mtx);
|
||||
|
||||
/* Condition variable */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
typedef struct {
|
||||
HANDLE mEvents[2]; /* Signal and broadcast event HANDLEs. */
|
||||
unsigned int mWaitersCount; /* Count of the number of waiters. */
|
||||
CRITICAL_SECTION mWaitersCountLock; /* Serialize access to mWaitersCount. */
|
||||
} cnd_t;
|
||||
#else
|
||||
typedef pthread_cond_t cnd_t;
|
||||
#endif
|
||||
|
||||
/** Create a condition variable object.
|
||||
* @param cond A condition variable object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int cnd_init(cnd_t *cond);
|
||||
|
||||
/** Release any resources used by the given condition variable.
|
||||
* @param cond A condition variable object.
|
||||
*/
|
||||
void cnd_destroy(cnd_t *cond);
|
||||
|
||||
/** Signal a condition variable.
|
||||
* Unblocks one of the threads that are blocked on the given condition variable
|
||||
* at the time of the call. If no threads are blocked on the condition variable
|
||||
* at the time of the call, the function does nothing and return success.
|
||||
* @param cond A condition variable object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int cnd_signal(cnd_t *cond);
|
||||
|
||||
/** Broadcast a condition variable.
|
||||
* Unblocks all of the threads that are blocked on the given condition variable
|
||||
* at the time of the call. If no threads are blocked on the condition variable
|
||||
* at the time of the call, the function does nothing and return success.
|
||||
* @param cond A condition variable object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int cnd_broadcast(cnd_t *cond);
|
||||
|
||||
/** Wait for a condition variable to become signaled.
|
||||
* The function atomically unlocks the given mutex and endeavors to block until
|
||||
* the given condition variable is signaled by a call to cnd_signal or to
|
||||
* cnd_broadcast. When the calling thread becomes unblocked it locks the mutex
|
||||
* before it returns.
|
||||
* @param cond A condition variable object.
|
||||
* @param mtx A mutex object.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int cnd_wait(cnd_t *cond, mtx_t *mtx);
|
||||
|
||||
/** Wait for a condition variable to become signaled.
|
||||
* The function atomically unlocks the given mutex and endeavors to block until
|
||||
* the given condition variable is signaled by a call to cnd_signal or to
|
||||
* cnd_broadcast, or until after the specified time. When the calling thread
|
||||
* becomes unblocked it locks the mutex before it returns.
|
||||
* @param cond A condition variable object.
|
||||
* @param mtx A mutex object.
|
||||
* @param xt A point in time at which the request will time out (absolute time).
|
||||
* @return @ref thrd_success upon success, or @ref thrd_timeout if the time
|
||||
* specified in the call was reached without acquiring the requested resource, or
|
||||
* @ref thrd_error if the request could not be honored.
|
||||
*/
|
||||
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts);
|
||||
|
||||
/* Thread */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
typedef HANDLE thrd_t;
|
||||
#else
|
||||
typedef pthread_t thrd_t;
|
||||
#endif
|
||||
|
||||
/** Thread start function.
|
||||
* Any thread that is started with the @ref thrd_create() function must be
|
||||
* started through a function of this type.
|
||||
* @param arg The thread argument (the @c arg argument of the corresponding
|
||||
* @ref thrd_create() call).
|
||||
* @return The thread return value, which can be obtained by another thread
|
||||
* by using the @ref thrd_join() function.
|
||||
*/
|
||||
typedef int (*thrd_start_t)(void *arg);
|
||||
|
||||
/** Create a new thread.
|
||||
* @param thr Identifier of the newly created thread.
|
||||
* @param func A function pointer to the function that will be executed in
|
||||
* the new thread.
|
||||
* @param arg An argument to the thread function.
|
||||
* @return @ref thrd_success on success, or @ref thrd_nomem if no memory could
|
||||
* be allocated for the thread requested, or @ref thrd_error if the request
|
||||
* could not be honored.
|
||||
* @note A thread’s identifier may be reused for a different thread once the
|
||||
* original thread has exited and either been detached or joined to another
|
||||
* thread.
|
||||
*/
|
||||
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg);
|
||||
|
||||
/** Identify the calling thread.
|
||||
* @return The identifier of the calling thread.
|
||||
*/
|
||||
thrd_t thrd_current(void);
|
||||
|
||||
/** Dispose of any resources allocated to the thread when that thread exits.
|
||||
* @return thrd_success, or thrd_error on error
|
||||
*/
|
||||
int thrd_detach(thrd_t thr);
|
||||
|
||||
/** Compare two thread identifiers.
|
||||
* The function determines if two thread identifiers refer to the same thread.
|
||||
* @return Zero if the two thread identifiers refer to different threads.
|
||||
* Otherwise a nonzero value is returned.
|
||||
*/
|
||||
int thrd_equal(thrd_t thr0, thrd_t thr1);
|
||||
|
||||
/** Terminate execution of the calling thread.
|
||||
* @param res Result code of the calling thread.
|
||||
*/
|
||||
TTHREAD_NORETURN void thrd_exit(int res);
|
||||
|
||||
/** Wait for a thread to terminate.
|
||||
* The function joins the given thread with the current thread by blocking
|
||||
* until the other thread has terminated.
|
||||
* @param thr The thread to join with.
|
||||
* @param res If this pointer is not NULL, the function will store the result
|
||||
* code of the given thread in the integer pointed to by @c res.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int thrd_join(thrd_t thr, int *res);
|
||||
|
||||
/** Put the calling thread to sleep.
|
||||
* Suspend execution of the calling thread.
|
||||
* @param duration Interval to sleep for
|
||||
* @param remaining If non-NULL, this parameter will hold the remaining
|
||||
* time until time_point upon return. This will
|
||||
* typically be zero, but if the thread was woken up
|
||||
* by a signal that is not ignored before duration was
|
||||
* reached @c remaining will hold a positive time.
|
||||
* @return 0 (zero) on successful sleep, -1 if an interrupt occurred,
|
||||
* or a negative value if the operation fails.
|
||||
*/
|
||||
int thrd_sleep(const struct timespec *duration, struct timespec *remaining);
|
||||
|
||||
/** Yield execution to another thread.
|
||||
* Permit other threads to run, even if the current thread would ordinarily
|
||||
* continue to run.
|
||||
*/
|
||||
void thrd_yield(void);
|
||||
|
||||
/* Thread local storage */
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
typedef DWORD tss_t;
|
||||
#else
|
||||
typedef pthread_key_t tss_t;
|
||||
#endif
|
||||
|
||||
/** Destructor function for a thread-specific storage.
|
||||
* @param val The value of the destructed thread-specific storage.
|
||||
*/
|
||||
typedef void (*tss_dtor_t)(void *val);
|
||||
|
||||
/** Create a thread-specific storage.
|
||||
* @param key The unique key identifier that will be set if the function is
|
||||
* successful.
|
||||
* @param dtor Destructor function. This can be NULL.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
* @note On Windows, the @c dtor will definitely be called when
|
||||
* appropriate for threads created with @ref thrd_create. It will be
|
||||
* called for other threads in most cases, the possible exception being
|
||||
* for DLLs loaded with LoadLibraryEx. In order to be certain, you
|
||||
* should use @ref thrd_create whenever possible.
|
||||
*/
|
||||
int tss_create(tss_t *key, tss_dtor_t dtor);
|
||||
|
||||
/** Delete a thread-specific storage.
|
||||
* The function releases any resources used by the given thread-specific
|
||||
* storage.
|
||||
* @param key The key that shall be deleted.
|
||||
*/
|
||||
void tss_delete(tss_t key);
|
||||
|
||||
/** Get the value for a thread-specific storage.
|
||||
* @param key The thread-specific storage identifier.
|
||||
* @return The value for the current thread held in the given thread-specific
|
||||
* storage.
|
||||
*/
|
||||
void *tss_get(tss_t key);
|
||||
|
||||
/** Set the value for a thread-specific storage.
|
||||
* @param key The thread-specific storage identifier.
|
||||
* @param val The value of the thread-specific storage to set for the current
|
||||
* thread.
|
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could
|
||||
* not be honored.
|
||||
*/
|
||||
int tss_set(tss_t key, void *val);
|
||||
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
typedef struct {
|
||||
LONG volatile status;
|
||||
CRITICAL_SECTION lock;
|
||||
} once_flag;
|
||||
#define ONCE_FLAG_INIT {0,}
|
||||
#else
|
||||
#define once_flag pthread_once_t
|
||||
#define ONCE_FLAG_INIT PTHREAD_ONCE_INIT
|
||||
#endif
|
||||
|
||||
/** Invoke a callback exactly once
|
||||
* @param flag Flag used to ensure the callback is invoked exactly
|
||||
* once.
|
||||
* @param func Callback to invoke.
|
||||
*/
|
||||
#if defined(_TTHREAD_WIN32_)
|
||||
void call_once(once_flag *flag, void (*func)(void));
|
||||
#else
|
||||
#define call_once(flag,func) pthread_once(flag,func)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _TINYTHREAD_H_ */
|
||||
@@ -1,11 +1,8 @@
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h> // rand
|
||||
#include <string.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "utils.h"
|
||||
#include "endian.h"
|
||||
#endif
|
||||
|
||||
static bool is_hex_digit(char c)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef MICROTCP_AMALGAMATION
|
||||
#include "defs.h"
|
||||
#endif
|
||||
|
||||
bool parse_mac(const char *src, size_t len, mac_address_t *mac);
|
||||
bool parse_ip(const char *ip, ip_address_t *parsed_ip);
|
||||
|
||||
Reference in New Issue
Block a user