Udp
							parent
							
								
									ba902f8194
								
							
						
					
					
						commit
						3b40a92548
					
				@ -0,0 +1,155 @@
 | 
			
		||||
/* Copyright (C) Kasm
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This software is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this software; if not, write to the Free Software
 | 
			
		||||
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 | 
			
		||||
 * USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_CONFIG_H
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
 | 
			
		||||
#include <network/GetAPI.h>
 | 
			
		||||
#include <network/Udp.h>
 | 
			
		||||
#include <network/webudp/WuHost.h>
 | 
			
		||||
#include <network/webudp/Wu.h>
 | 
			
		||||
#include <network/websocket.h>
 | 
			
		||||
#include <rfb/LogWriter.h>
 | 
			
		||||
#include <rfb/ServerCore.h>
 | 
			
		||||
#include <rfb/xxhash.h>
 | 
			
		||||
 | 
			
		||||
using namespace network;
 | 
			
		||||
 | 
			
		||||
static rfb::LogWriter vlog("WebUdp");
 | 
			
		||||
static WuHost *host = NULL;
 | 
			
		||||
 | 
			
		||||
rfb::IntParameter udpSize("udpSize", "UDP packet data size", 1300, 500, 1400);
 | 
			
		||||
 | 
			
		||||
extern settings_t settings;
 | 
			
		||||
 | 
			
		||||
static void udperr(const char *msg, void *) {
 | 
			
		||||
	vlog.error("%s", msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void udpdebug(const char *msg, void *) {
 | 
			
		||||
	vlog.debug("%s", msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void *udpserver(void *nport) {
 | 
			
		||||
 | 
			
		||||
	WuHost *myhost = NULL;
 | 
			
		||||
	int ret = WuHostCreate(rfb::Server::publicIP, *(uint16_t *) nport, 16, &myhost);
 | 
			
		||||
	if (ret != WU_OK) {
 | 
			
		||||
		vlog.error("Failed to create WebUDP host");
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	__sync_bool_compare_and_swap(&host, host, myhost);
 | 
			
		||||
 | 
			
		||||
	GetAPIMessager *msgr = (GetAPIMessager *) settings.messager;
 | 
			
		||||
 | 
			
		||||
	WuHostSetErrorCallback(host, udperr);
 | 
			
		||||
	WuHostSetDebugCallback(host, udpdebug);
 | 
			
		||||
 | 
			
		||||
	while (1) {
 | 
			
		||||
		WuAddress addr;
 | 
			
		||||
		WuEvent e;
 | 
			
		||||
		if (!WuHostServe(host, &e, 2000))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		switch (e.type) {
 | 
			
		||||
			case WuEvent_ClientJoin:
 | 
			
		||||
				vlog.info("client join");
 | 
			
		||||
				addr = WuClientGetAddress(e.client);
 | 
			
		||||
				msgr->netUdpUpgrade(e.client, htonl(addr.host));
 | 
			
		||||
			break;
 | 
			
		||||
			case WuEvent_ClientLeave:
 | 
			
		||||
				vlog.info("client leave");
 | 
			
		||||
				WuHostRemoveClient(host, e.client);
 | 
			
		||||
			break;
 | 
			
		||||
			default:
 | 
			
		||||
				vlog.error("client sent data, this is unexpected");
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Send one packet, split into N UDP-sized pieces
 | 
			
		||||
static uint8_t udpsend(WuClient *client, const uint8_t *data, unsigned len, uint32_t *id) {
 | 
			
		||||
	const uint32_t DATA_MAX = udpSize;
 | 
			
		||||
 | 
			
		||||
	uint8_t buf[1400 + sizeof(uint32_t) * 4];
 | 
			
		||||
	const uint32_t pieces = (len / DATA_MAX) + ((len % DATA_MAX) ? 1 : 0);
 | 
			
		||||
 | 
			
		||||
	uint32_t i;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < pieces; i++) {
 | 
			
		||||
		const unsigned curlen = len > DATA_MAX ? DATA_MAX : len;
 | 
			
		||||
		const uint32_t hash = XXH64(data, curlen, 0);
 | 
			
		||||
 | 
			
		||||
		memcpy(buf, id, sizeof(uint32_t));
 | 
			
		||||
		memcpy(&buf[4], &i, sizeof(uint32_t));
 | 
			
		||||
		memcpy(&buf[8], &pieces, sizeof(uint32_t));
 | 
			
		||||
		memcpy(&buf[12], &hash, sizeof(uint32_t));
 | 
			
		||||
 | 
			
		||||
		memcpy(&buf[16], data, curlen);
 | 
			
		||||
		data += curlen;
 | 
			
		||||
		len -= curlen;
 | 
			
		||||
 | 
			
		||||
		if (WuHostSendBinary(host, client, buf, curlen + sizeof(uint32_t) * 4) < 0)
 | 
			
		||||
			return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	(*id)++;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
UdpStream::UdpStream(): OutStream(), client(NULL), total_len(0), id(0) {
 | 
			
		||||
	ptr = data;
 | 
			
		||||
	end = data + UDPSTREAM_BUFSIZE;
 | 
			
		||||
 | 
			
		||||
	srand(time(NULL));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UdpStream::flush() {
 | 
			
		||||
	const unsigned len = ptr - data;
 | 
			
		||||
	total_len += len;
 | 
			
		||||
 | 
			
		||||
	if (client) {
 | 
			
		||||
		if (udpsend(client, data, len, &id))
 | 
			
		||||
			vlog.error("Error sending udp, client gone?");
 | 
			
		||||
	} else {
 | 
			
		||||
		vlog.error("Tried to send udp without a client");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ptr = data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UdpStream::overrun(size_t needed) {
 | 
			
		||||
	vlog.error("Udp buffer overrun");
 | 
			
		||||
	abort();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void wuGotHttp(const char msg[], const uint32_t msglen, char resp[]) {
 | 
			
		||||
	WuGotHttp(host, msg, msglen, resp);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
/* Copyright (C) 2022 Kasm
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This software is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this software; if not, write to the Free Software
 | 
			
		||||
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 | 
			
		||||
 * USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __NETWORK_UDP_H__
 | 
			
		||||
#define __NETWORK_UDP_H__
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <rdr/OutStream.h>
 | 
			
		||||
 | 
			
		||||
void *udpserver(void *unused);
 | 
			
		||||
typedef struct WuClient WuClient;
 | 
			
		||||
 | 
			
		||||
namespace network {
 | 
			
		||||
 | 
			
		||||
	#define UDPSTREAM_BUFSIZE (1024 * 1024)
 | 
			
		||||
 | 
			
		||||
	class UdpStream: public rdr::OutStream {
 | 
			
		||||
		public:
 | 
			
		||||
			UdpStream();
 | 
			
		||||
			virtual void flush();
 | 
			
		||||
			virtual size_t length() { return total_len; }
 | 
			
		||||
			virtual void overrun(size_t needed);
 | 
			
		||||
 | 
			
		||||
			void setClient(WuClient *cli) {
 | 
			
		||||
				client = cli;
 | 
			
		||||
			}
 | 
			
		||||
		private:
 | 
			
		||||
			uint8_t data[UDPSTREAM_BUFSIZE];
 | 
			
		||||
			WuClient *client;
 | 
			
		||||
			size_t total_len;
 | 
			
		||||
			uint32_t id;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" void wuGotHttp(const char msg[], const uint32_t msglen, char resp[]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif // __NETWORK_UDP_H__
 | 
			
		||||
@ -0,0 +1,185 @@
 | 
			
		||||
/* Copyright (C) Kasm
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This software is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this software; if not, write to the Free Software
 | 
			
		||||
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 | 
			
		||||
 * USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_CONFIG_H
 | 
			
		||||
#include <config.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <limits.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <netinet/in.h>
 | 
			
		||||
#include <poll.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
 | 
			
		||||
#include <network/iceip.h>
 | 
			
		||||
#include <rfb/LogWriter.h>
 | 
			
		||||
#include <rfb/ServerCore.h>
 | 
			
		||||
 | 
			
		||||
static rfb::LogWriter vlog("ICE");
 | 
			
		||||
 | 
			
		||||
// Default port 3478
 | 
			
		||||
static const char * const servers[] = {
 | 
			
		||||
	"stun.l.google.com:19302",
 | 
			
		||||
	"stun1.l.google.com:19302",
 | 
			
		||||
	"stun2.l.google.com:19302",
 | 
			
		||||
	"stun3.l.google.com:19302",
 | 
			
		||||
	"stun4.l.google.com:19302",
 | 
			
		||||
	"stun.voipbuster.com",
 | 
			
		||||
	"stun.voipstunt.com",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static bool tryserver(const char * const srv, const int sock) {
 | 
			
		||||
 | 
			
		||||
	unsigned port = 3478;
 | 
			
		||||
	char addr[PATH_MAX];
 | 
			
		||||
	char buf[PATH_MAX];
 | 
			
		||||
 | 
			
		||||
	const char *colon = strchr(srv, ':');
 | 
			
		||||
	if (colon) {
 | 
			
		||||
		memcpy(addr, srv, colon - srv);
 | 
			
		||||
		addr[colon - srv] = '\0';
 | 
			
		||||
 | 
			
		||||
		colon++;
 | 
			
		||||
		port = atoi(colon);
 | 
			
		||||
	} else {
 | 
			
		||||
		strcpy(addr, srv);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vlog.debug("Trying '%s', port %u", addr, port);
 | 
			
		||||
 | 
			
		||||
	struct hostent *ent = gethostbyname2(addr, AF_INET);
 | 
			
		||||
	if (!ent)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	struct sockaddr_in dst;
 | 
			
		||||
	dst.sin_family = AF_INET;
 | 
			
		||||
	dst.sin_port = htons(port);
 | 
			
		||||
	memcpy(&dst.sin_addr, ent->h_addr, 4);
 | 
			
		||||
	//vlog.info("Got %s, addr %s", ent->h_name, inet_ntoa(in));
 | 
			
		||||
 | 
			
		||||
	// Build up a binding request packet
 | 
			
		||||
	buf[0] = 0;
 | 
			
		||||
	buf[1] = 1; // type
 | 
			
		||||
	buf[2] = buf[3] = 0; // length
 | 
			
		||||
 | 
			
		||||
	uint32_t tid[4]; // transaction id, 128 bits
 | 
			
		||||
	tid[0] = rand();
 | 
			
		||||
	tid[1] = rand();
 | 
			
		||||
	tid[2] = rand();
 | 
			
		||||
	tid[3] = rand();
 | 
			
		||||
 | 
			
		||||
	memcpy(&buf[4], &tid[0], 4);
 | 
			
		||||
	memcpy(&buf[8], &tid[1], 4);
 | 
			
		||||
	memcpy(&buf[12], &tid[2], 4);
 | 
			
		||||
	memcpy(&buf[16], &tid[3], 4);
 | 
			
		||||
 | 
			
		||||
	if (sendto(sock, buf, 20, 0, (const struct sockaddr *) &dst,
 | 
			
		||||
		sizeof(struct sockaddr_in)) != 20)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	// Wait up to 10s for a reply, standard says that's the wait
 | 
			
		||||
	struct pollfd pfd;
 | 
			
		||||
	pfd.fd = sock;
 | 
			
		||||
	pfd.events = POLLIN;
 | 
			
		||||
	if (poll(&pfd, 1, 10 * 1000) <= 0)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	struct sockaddr_in from;
 | 
			
		||||
	socklen_t socklen = sizeof(struct sockaddr_in);
 | 
			
		||||
	int len = recvfrom(sock, buf, PATH_MAX, 0, (struct sockaddr *) &from,
 | 
			
		||||
				&socklen);
 | 
			
		||||
	if (len < 20)
 | 
			
		||||
		return false;
 | 
			
		||||
	if (memcmp(&from.sin_addr, &dst.sin_addr, sizeof(struct in_addr)))
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	int i;
 | 
			
		||||
/*	vlog.info("Got %u bytes", len);
 | 
			
		||||
	for (i = 0; i < len; i++)
 | 
			
		||||
		vlog.info("0x%02x,", buf[i]);*/
 | 
			
		||||
 | 
			
		||||
	if (buf[0] != 1 || buf[1] != 1)
 | 
			
		||||
		return false; // type not binding response
 | 
			
		||||
 | 
			
		||||
	// Parse attrs
 | 
			
		||||
	for (i = 20; i < len;) {
 | 
			
		||||
		uint16_t type, attrlen;
 | 
			
		||||
		memcpy(&type, &buf[i], 2);
 | 
			
		||||
		i += 2;
 | 
			
		||||
		memcpy(&attrlen, &buf[i], 2);
 | 
			
		||||
		i += 2;
 | 
			
		||||
 | 
			
		||||
		type = ntohs(type);
 | 
			
		||||
		attrlen = ntohs(attrlen);
 | 
			
		||||
		if (type != 1) {
 | 
			
		||||
			// Not mapped-address
 | 
			
		||||
			i += attrlen;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Yay, we got a response
 | 
			
		||||
		i += 4;
 | 
			
		||||
		struct in_addr in;
 | 
			
		||||
		memcpy(&in.s_addr, &buf[i], 4);
 | 
			
		||||
 | 
			
		||||
		rfb::Server::publicIP.setParam(inet_ntoa(in));
 | 
			
		||||
 | 
			
		||||
		vlog.info("My public IP is %s", (const char *) rfb::Server::publicIP);
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void getPublicIP() {
 | 
			
		||||
	if (rfb::Server::publicIP[0]) {
 | 
			
		||||
		vlog.info("Using public IP %s from args",
 | 
			
		||||
				(const char *) rfb::Server::publicIP);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	srand(time(NULL));
 | 
			
		||||
 | 
			
		||||
	vlog.info("Querying public IP...");
 | 
			
		||||
 | 
			
		||||
	const int sock = socket(AF_INET, SOCK_DGRAM, 0);
 | 
			
		||||
	if (sock < 0)
 | 
			
		||||
		abort();
 | 
			
		||||
 | 
			
		||||
	unsigned i;
 | 
			
		||||
	for (i = 0; i < sizeof(servers) / sizeof(servers[0]); i++) {
 | 
			
		||||
		if (tryserver(servers[i], sock))
 | 
			
		||||
			break;
 | 
			
		||||
		vlog.info("STUN server %u didn't work, trying next...", i);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	close(sock);
 | 
			
		||||
 | 
			
		||||
	if (!rfb::Server::publicIP[0]) {
 | 
			
		||||
		vlog.error("Failed to get public IP, please specify it with -publicIP");
 | 
			
		||||
		exit(1);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
/* Copyright (C) 2022 Kasm
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This software is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this software; if not, write to the Free Software
 | 
			
		||||
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 | 
			
		||||
 * USA.
 | 
			
		||||
 */
 | 
			
		||||
#ifndef __ICEIP_H__
 | 
			
		||||
#define __ICEIP_H__
 | 
			
		||||
 | 
			
		||||
void getPublicIP();
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
## 0.6.1 (06.06.2020)
 | 
			
		||||
- Fixed WuHostNull build.
 | 
			
		||||
- Use 2048 bit RSA keys (fixes `SSL_CTX_use_certificate:ee key too small`).
 | 
			
		||||
 | 
			
		||||
## 0.6.0 (10.04.2020)
 | 
			
		||||
- Allow OpenSSL 1.1.
 | 
			
		||||
- Node addon: upgrade nan, now builds with node 12.
 | 
			
		||||
- Fixed compiler warnings.
 | 
			
		||||
 | 
			
		||||
## 0.5.0 (27.10.2018)
 | 
			
		||||
- Switch to a newer SDP format.
 | 
			
		||||
  Newer Chrome versions can connect to the server again.
 | 
			
		||||
- Update required CMake version to 3.7.
 | 
			
		||||
 | 
			
		||||
## 0.4.1 (06.10.2018)
 | 
			
		||||
- Fix compilation with g++ 7.
 | 
			
		||||
 | 
			
		||||
## 0.4.0 (02.10.2018)
 | 
			
		||||
- Add C API.
 | 
			
		||||
- WuHost now has an explicit timeout parameter.
 | 
			
		||||
- Remove ES6 'let' from wusocket.js.
 | 
			
		||||
 | 
			
		||||
## 0.3.0 (16.07.2018)
 | 
			
		||||
- Fix potential out of bounds read when sending SDP response.
 | 
			
		||||
 | 
			
		||||
## 0.2.0 (12.01.2018)
 | 
			
		||||
- Add DTLS 1.2 support. Requires at least OpenSSL 1.0.2.
 | 
			
		||||
 | 
			
		||||
## 0.1.1 (01.01.2018)
 | 
			
		||||
- Fix WuConf uninitialized maxClients parameter.
 | 
			
		||||
 | 
			
		||||
## 0.1.0 (30.12.2017)
 | 
			
		||||
- Remove the old default epoll implementation.
 | 
			
		||||
- Split the core logic into a separate library.
 | 
			
		||||
- Add a new epoll host.
 | 
			
		||||
- Add a Node.js host.
 | 
			
		||||
- Add fuzz tests.
 | 
			
		||||
- Add a Node.js example.
 | 
			
		||||
@ -0,0 +1,124 @@
 | 
			
		||||
#include "CRC32.h"
 | 
			
		||||
 | 
			
		||||
static const uint32_t crc32Stun[] = {
 | 
			
		||||
    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
 | 
			
		||||
    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
 | 
			
		||||
    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
 | 
			
		||||
    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
 | 
			
		||||
    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
 | 
			
		||||
    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
 | 
			
		||||
    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
 | 
			
		||||
    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
 | 
			
		||||
    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
 | 
			
		||||
    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
 | 
			
		||||
    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
 | 
			
		||||
    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
 | 
			
		||||
    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
 | 
			
		||||
    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
 | 
			
		||||
    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
 | 
			
		||||
    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
 | 
			
		||||
    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
 | 
			
		||||
    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
 | 
			
		||||
    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
 | 
			
		||||
    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
 | 
			
		||||
    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
 | 
			
		||||
    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
 | 
			
		||||
    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
 | 
			
		||||
    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
 | 
			
		||||
    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
 | 
			
		||||
    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
 | 
			
		||||
    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
 | 
			
		||||
    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
 | 
			
		||||
    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
 | 
			
		||||
    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
 | 
			
		||||
    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
 | 
			
		||||
    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
 | 
			
		||||
    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
 | 
			
		||||
    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
 | 
			
		||||
    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
 | 
			
		||||
    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
 | 
			
		||||
    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
 | 
			
		||||
    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
 | 
			
		||||
    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
 | 
			
		||||
    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
 | 
			
		||||
    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
 | 
			
		||||
    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
 | 
			
		||||
    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
 | 
			
		||||
 | 
			
		||||
static const unsigned long crc32Sctp[256] = {
 | 
			
		||||
    0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C,
 | 
			
		||||
    0x26A1E7E8, 0xD4CA64EB, 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B,
 | 
			
		||||
    0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, 0x105EC76F, 0xE235446C,
 | 
			
		||||
    0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
 | 
			
		||||
    0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC,
 | 
			
		||||
    0xBC267848, 0x4E4DFB4B, 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A,
 | 
			
		||||
    0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, 0xAA64D611, 0x580F5512,
 | 
			
		||||
    0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
 | 
			
		||||
    0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD,
 | 
			
		||||
    0x1642AE59, 0xE4292D5A, 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A,
 | 
			
		||||
    0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, 0x417B1DBC, 0xB3109EBF,
 | 
			
		||||
    0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
 | 
			
		||||
    0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F,
 | 
			
		||||
    0xED03A29B, 0x1F682198, 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927,
 | 
			
		||||
    0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, 0xDBFC821C, 0x2997011F,
 | 
			
		||||
    0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
 | 
			
		||||
    0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E,
 | 
			
		||||
    0x4767748A, 0xB50CF789, 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859,
 | 
			
		||||
    0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, 0x7198540D, 0x83F3D70E,
 | 
			
		||||
    0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
 | 
			
		||||
    0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE,
 | 
			
		||||
    0xDDE0EB2A, 0x2F8B6829, 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C,
 | 
			
		||||
    0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, 0x082F63B7, 0xFA44E0B4,
 | 
			
		||||
    0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
 | 
			
		||||
    0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B,
 | 
			
		||||
    0xB4091BFF, 0x466298FC, 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C,
 | 
			
		||||
    0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, 0xA24BB5A6, 0x502036A5,
 | 
			
		||||
    0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
 | 
			
		||||
    0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975,
 | 
			
		||||
    0x0E330A81, 0xFC588982, 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
 | 
			
		||||
    0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 0x38CC2A06, 0xCAA7A905,
 | 
			
		||||
    0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
 | 
			
		||||
    0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8,
 | 
			
		||||
    0xE52CC12C, 0x1747422F, 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF,
 | 
			
		||||
    0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, 0xD3D3E1AB, 0x21B862A8,
 | 
			
		||||
    0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
 | 
			
		||||
    0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78,
 | 
			
		||||
    0x7FAB5E8C, 0x8DC0DD8F, 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE,
 | 
			
		||||
    0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, 0x69E9F0D5, 0x9B8273D6,
 | 
			
		||||
    0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
 | 
			
		||||
    0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69,
 | 
			
		||||
    0xD5CF889D, 0x27A40B9E, 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E,
 | 
			
		||||
    0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351};
 | 
			
		||||
 | 
			
		||||
uint32_t StunCRC32(const void* data, int32_t len) {
 | 
			
		||||
  uint32_t crc = 0xffffffff;
 | 
			
		||||
 | 
			
		||||
  const uint8_t* p = (const uint8_t*)data;
 | 
			
		||||
 | 
			
		||||
  while (len--) {
 | 
			
		||||
    uint32_t lkp = crc32Stun[(crc ^ *p++) & 0xFF];
 | 
			
		||||
    crc = lkp ^ (crc >> 8);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return crc ^ 0xffffffff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define CRC32C(c, d) (c = (c >> 8) ^ (crc32Sctp)[(c ^ (d)) & 0xFF])
 | 
			
		||||
 | 
			
		||||
uint32_t SctpCRC32(const void* data, int32_t len) {
 | 
			
		||||
  uint32_t crc = 0xFFFFFFFF;
 | 
			
		||||
 | 
			
		||||
  const uint8_t* p = (const uint8_t*)data;
 | 
			
		||||
 | 
			
		||||
  for (int32_t i = 0; i < len; i++) {
 | 
			
		||||
    CRC32C(crc, p[i]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t result = ~crc;
 | 
			
		||||
  uint8_t byte0 = result & 0xff;
 | 
			
		||||
  uint8_t byte1 = (result >> 8) & 0xff;
 | 
			
		||||
  uint8_t byte2 = (result >> 16) & 0xff;
 | 
			
		||||
  uint8_t byte3 = (result >> 24) & 0xff;
 | 
			
		||||
  result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
uint32_t StunCRC32(const void* data, int32_t len);
 | 
			
		||||
uint32_t SctpCRC32(const void* data, int32_t len);
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2017 Siim Kallas
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
# WebUDP
 | 
			
		||||
WebRTC datachannel library and server
 | 
			
		||||
 | 
			
		||||
[Echo server demo](https://www.vektor.space/webudprtt.html) (Chrome, Firefox, Safari 11+)
 | 
			
		||||
 | 
			
		||||
The library implements a minimal subset of WebRTC to achieve unreliable and out of order UDP transfer for browser clients. The core library (Wu) is platform independent. Refer to WuHostEpoll or WuHostNode for usage.
 | 
			
		||||
 | 
			
		||||
## Building
 | 
			
		||||
```bash
 | 
			
		||||
mkdir build && cd build
 | 
			
		||||
cmake ..
 | 
			
		||||
make
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Host platforms
 | 
			
		||||
* Linux (epoll)
 | 
			
		||||
* Node.js ```-DWITH_NODE=ON```
 | 
			
		||||
 | 
			
		||||
### Issues
 | 
			
		||||
* Firefox doesn't connect to a server running on localhost. Bind a different interface.
 | 
			
		||||
@ -0,0 +1,771 @@
 | 
			
		||||
#include "Wu.h"
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <openssl/ec.h>
 | 
			
		||||
#include <openssl/err.h>
 | 
			
		||||
#include <openssl/ssl.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include "WuArena.h"
 | 
			
		||||
#include "WuClock.h"
 | 
			
		||||
#include "WuCrypto.h"
 | 
			
		||||
#include "WuMath.h"
 | 
			
		||||
#include "WuPool.h"
 | 
			
		||||
#include "WuQueue.h"
 | 
			
		||||
#include "WuRng.h"
 | 
			
		||||
#include "WuSctp.h"
 | 
			
		||||
#include "WuSdp.h"
 | 
			
		||||
#include "WuStun.h"
 | 
			
		||||
 | 
			
		||||
struct Wu {
 | 
			
		||||
  WuArena* arena;
 | 
			
		||||
  double time;
 | 
			
		||||
  double dt;
 | 
			
		||||
  char host[256];
 | 
			
		||||
  uint16_t port;
 | 
			
		||||
  WuQueue* pendingEvents;
 | 
			
		||||
  int32_t maxClients;
 | 
			
		||||
  int32_t numClients;
 | 
			
		||||
 | 
			
		||||
  WuPool* clientPool;
 | 
			
		||||
  WuClient** clients;
 | 
			
		||||
  ssl_ctx_st* sslCtx;
 | 
			
		||||
 | 
			
		||||
  char certFingerprint[96];
 | 
			
		||||
 | 
			
		||||
  char errBuf[512];
 | 
			
		||||
  void* userData;
 | 
			
		||||
  WuErrorFn errorCallback;
 | 
			
		||||
  WuErrorFn debugCallback;
 | 
			
		||||
  WuWriteFn writeUdpData;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const double kMaxClientTtl = 9.0;
 | 
			
		||||
const double heartbeatInterval = 4.0;
 | 
			
		||||
const int kDefaultMTU = 1400;
 | 
			
		||||
 | 
			
		||||
static void DefaultErrorCallback(const char*, void*) {}
 | 
			
		||||
static void WriteNothing(const uint8_t*, size_t, const WuClient*, void*) {}
 | 
			
		||||
 | 
			
		||||
enum DataChannelMessageType { DCMessage_Ack = 0x02, DCMessage_Open = 0x03 };
 | 
			
		||||
 | 
			
		||||
enum DataChanProtoIdentifier {
 | 
			
		||||
  DCProto_Control = 50,
 | 
			
		||||
  DCProto_String = 51,
 | 
			
		||||
  DCProto_Binary = 53,
 | 
			
		||||
  DCProto_EmptyString = 56,
 | 
			
		||||
  DCProto_EmptyBinary = 57
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct DataChannelPacket {
 | 
			
		||||
  uint8_t messageType;
 | 
			
		||||
 | 
			
		||||
  union {
 | 
			
		||||
    struct {
 | 
			
		||||
      uint8_t channelType;
 | 
			
		||||
      uint16_t priority;
 | 
			
		||||
      uint32_t reliability;
 | 
			
		||||
    } open;
 | 
			
		||||
  } as;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum WuClientState {
 | 
			
		||||
  WuClient_Dead,
 | 
			
		||||
  WuClient_WaitingRemoval,
 | 
			
		||||
  WuClient_DTLSHandshake,
 | 
			
		||||
  WuClient_SCTPEstablished,
 | 
			
		||||
  WuClient_DataChannelOpen
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int32_t ParseDataChannelControlPacket(const uint8_t* buf, size_t len,
 | 
			
		||||
                                             DataChannelPacket* packet) {
 | 
			
		||||
  ReadScalarSwapped(buf, &packet->messageType);
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuReportError(Wu* wu, const char* description) {
 | 
			
		||||
  wu->errorCallback(description, wu->userData);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuReportDebug(Wu* wu, const char* description) {
 | 
			
		||||
  wu->debugCallback(description, wu->userData);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct WuClient {
 | 
			
		||||
  StunUserIdentifier serverUser;
 | 
			
		||||
  StunUserIdentifier serverPassword;
 | 
			
		||||
  StunUserIdentifier remoteUser;
 | 
			
		||||
  StunUserIdentifier remoteUserPassword;
 | 
			
		||||
  WuAddress address;
 | 
			
		||||
  WuClientState state;
 | 
			
		||||
  uint16_t localSctpPort;
 | 
			
		||||
  uint16_t remoteSctpPort;
 | 
			
		||||
  uint32_t sctpVerificationTag;
 | 
			
		||||
  uint32_t remoteTsn;
 | 
			
		||||
  uint32_t tsn;
 | 
			
		||||
  double ttl;
 | 
			
		||||
  double nextHeartbeat;
 | 
			
		||||
 | 
			
		||||
  SSL* ssl;
 | 
			
		||||
  BIO* inBio;
 | 
			
		||||
  BIO* outBio;
 | 
			
		||||
 | 
			
		||||
  void* user;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void WuClientSetUserData(WuClient* client, void* user) { client->user = user; }
 | 
			
		||||
 | 
			
		||||
void* WuClientGetUserData(const WuClient* client) { return client->user; }
 | 
			
		||||
 | 
			
		||||
static void WuClientFinish(WuClient* client) {
 | 
			
		||||
  SSL_free(client->ssl);
 | 
			
		||||
  client->ssl = NULL;
 | 
			
		||||
  client->inBio = NULL;
 | 
			
		||||
  client->outBio = NULL;
 | 
			
		||||
  client->state = WuClient_Dead;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuClientStart(const Wu* wu, WuClient* client) {
 | 
			
		||||
  client->state = WuClient_DTLSHandshake;
 | 
			
		||||
  client->remoteSctpPort = 0;
 | 
			
		||||
  client->sctpVerificationTag = 0;
 | 
			
		||||
  client->remoteTsn = 0;
 | 
			
		||||
  client->tsn = 1;
 | 
			
		||||
  client->ttl = kMaxClientTtl;
 | 
			
		||||
  client->nextHeartbeat = heartbeatInterval;
 | 
			
		||||
  client->user = NULL;
 | 
			
		||||
 | 
			
		||||
  client->ssl = SSL_new(wu->sslCtx);
 | 
			
		||||
 | 
			
		||||
  client->inBio = BIO_new(BIO_s_mem());
 | 
			
		||||
  BIO_set_mem_eof_return(client->inBio, -1);
 | 
			
		||||
  client->outBio = BIO_new(BIO_s_mem());
 | 
			
		||||
  BIO_set_mem_eof_return(client->outBio, -1);
 | 
			
		||||
  SSL_set_bio(client->ssl, client->inBio, client->outBio);
 | 
			
		||||
  SSL_set_options(client->ssl, SSL_OP_SINGLE_ECDH_USE);
 | 
			
		||||
  SSL_set_options(client->ssl, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
 | 
			
		||||
  SSL_set_tmp_ecdh(client->ssl, EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
 | 
			
		||||
  SSL_set_accept_state(client->ssl);
 | 
			
		||||
  SSL_set_mtu(client->ssl, kDefaultMTU);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuSendSctp(const Wu* wu, WuClient* client, const SctpPacket* packet,
 | 
			
		||||
                       const SctpChunk* chunks, int32_t numChunks);
 | 
			
		||||
 | 
			
		||||
static WuClient* WuNewClient(Wu* wu) {
 | 
			
		||||
  WuClient* client = (WuClient*)WuPoolAcquire(wu->clientPool);
 | 
			
		||||
 | 
			
		||||
  if (client) {
 | 
			
		||||
    memset(client, 0, sizeof(WuClient));
 | 
			
		||||
    WuClientStart(wu, client);
 | 
			
		||||
    wu->clients[wu->numClients++] = client;
 | 
			
		||||
    return client;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuPushEvent(Wu* wu, WuEvent evt) {
 | 
			
		||||
  WuQueuePush(wu->pendingEvents, &evt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuSendSctpShutdown(Wu* wu, WuClient* client) {
 | 
			
		||||
  SctpPacket response;
 | 
			
		||||
  response.sourcePort = client->localSctpPort;
 | 
			
		||||
  response.destionationPort = client->remoteSctpPort;
 | 
			
		||||
  response.verificationTag = client->sctpVerificationTag;
 | 
			
		||||
 | 
			
		||||
  SctpChunk rc;
 | 
			
		||||
  rc.type = Sctp_Shutdown;
 | 
			
		||||
  rc.flags = 0;
 | 
			
		||||
  rc.length = SctpChunkLength(sizeof(rc.as.shutdown.cumulativeTsnAck));
 | 
			
		||||
  rc.as.shutdown.cumulativeTsnAck = client->remoteTsn;
 | 
			
		||||
 | 
			
		||||
  WuSendSctp(wu, client, &response, &rc, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuRemoveClient(Wu* wu, WuClient* client) {
 | 
			
		||||
  for (int32_t i = 0; i < wu->numClients; i++) {
 | 
			
		||||
    if (wu->clients[i] == client) {
 | 
			
		||||
      WuSendSctpShutdown(wu, client);
 | 
			
		||||
      WuClientFinish(client);
 | 
			
		||||
      WuPoolRelease(wu->clientPool, client);
 | 
			
		||||
      wu->clients[i] = wu->clients[wu->numClients - 1];
 | 
			
		||||
      wu->numClients--;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static WuClient* WuFindClient(Wu* wu, const WuAddress* address) {
 | 
			
		||||
  for (int32_t i = 0; i < wu->numClients; i++) {
 | 
			
		||||
    WuClient* client = wu->clients[i];
 | 
			
		||||
    if (client->address.host == address->host &&
 | 
			
		||||
        client->address.port == address->port) {
 | 
			
		||||
      return client;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static WuClient* WuFindClientByCreds(Wu* wu, const StunUserIdentifier* svUser,
 | 
			
		||||
                                     const StunUserIdentifier* clUser) {
 | 
			
		||||
  for (int32_t i = 0; i < wu->numClients; i++) {
 | 
			
		||||
    WuClient* client = wu->clients[i];
 | 
			
		||||
    if (StunUserIdentifierEqual(&client->serverUser, svUser) &&
 | 
			
		||||
        StunUserIdentifierEqual(&client->remoteUser, clUser)) {
 | 
			
		||||
      return client;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuClientSendPendingDTLS(const Wu* wu, WuClient* client) {
 | 
			
		||||
  uint8_t sendBuffer[4096];
 | 
			
		||||
 | 
			
		||||
  while (BIO_ctrl_pending(client->outBio) > 0) {
 | 
			
		||||
    int bytes = BIO_read(client->outBio, sendBuffer, sizeof(sendBuffer));
 | 
			
		||||
    if (bytes > 0) {
 | 
			
		||||
      wu->writeUdpData(sendBuffer, bytes, client, wu->userData);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void TLSSend(const Wu* wu, WuClient* client, const void* data,
 | 
			
		||||
                    int32_t length) {
 | 
			
		||||
  if (client->state < WuClient_DTLSHandshake ||
 | 
			
		||||
      !SSL_is_init_finished(client->ssl)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SSL_write(client->ssl, data, length);
 | 
			
		||||
  WuClientSendPendingDTLS(wu, client);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuSendSctp(const Wu* wu, WuClient* client, const SctpPacket* packet,
 | 
			
		||||
                       const SctpChunk* chunks, int32_t numChunks) {
 | 
			
		||||
  uint8_t outBuffer[4096];
 | 
			
		||||
  memset(outBuffer, 0, sizeof(outBuffer));
 | 
			
		||||
  size_t bytesWritten = SerializeSctpPacket(packet, chunks, numChunks,
 | 
			
		||||
                                            outBuffer, sizeof(outBuffer));
 | 
			
		||||
  TLSSend(wu, client, outBuffer, bytesWritten);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuHandleSctp(Wu* wu, WuClient* client, const uint8_t* buf,
 | 
			
		||||
                         int32_t len) {
 | 
			
		||||
  const size_t maxChunks = 8;
 | 
			
		||||
  SctpChunk chunks[maxChunks];
 | 
			
		||||
  SctpPacket sctpPacket;
 | 
			
		||||
  size_t nChunk = 0;
 | 
			
		||||
 | 
			
		||||
  if (!ParseSctpPacket(buf, len, &sctpPacket, chunks, maxChunks, &nChunk)) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t n = 0; n < nChunk; n++) {
 | 
			
		||||
    SctpChunk* chunk = &chunks[n];
 | 
			
		||||
    if (chunk->type == Sctp_Data) {
 | 
			
		||||
      auto* dataChunk = &chunk->as.data;
 | 
			
		||||
      const uint8_t* userDataBegin = dataChunk->userData;
 | 
			
		||||
      const int32_t userDataLength = dataChunk->userDataLength;
 | 
			
		||||
 | 
			
		||||
      client->remoteTsn = Max(chunk->as.data.tsn, client->remoteTsn);
 | 
			
		||||
      client->ttl = kMaxClientTtl;
 | 
			
		||||
 | 
			
		||||
      if (dataChunk->protoId == DCProto_Control) {
 | 
			
		||||
        DataChannelPacket packet;
 | 
			
		||||
        ParseDataChannelControlPacket(userDataBegin, userDataLength, &packet);
 | 
			
		||||
        if (packet.messageType == DCMessage_Open) {
 | 
			
		||||
          client->remoteSctpPort = sctpPacket.sourcePort;
 | 
			
		||||
          uint8_t outType = DCMessage_Ack;
 | 
			
		||||
          SctpPacket response;
 | 
			
		||||
          response.sourcePort = sctpPacket.destionationPort;
 | 
			
		||||
          response.destionationPort = sctpPacket.sourcePort;
 | 
			
		||||
          response.verificationTag = client->sctpVerificationTag;
 | 
			
		||||
 | 
			
		||||
          SctpChunk rc;
 | 
			
		||||
          rc.type = Sctp_Data;
 | 
			
		||||
          rc.flags = kSctpFlagCompleteUnreliable;
 | 
			
		||||
          rc.length = SctpDataChunkLength(1);
 | 
			
		||||
 | 
			
		||||
          auto* dc = &rc.as.data;
 | 
			
		||||
          dc->tsn = client->tsn++;
 | 
			
		||||
          dc->streamId = chunk->as.data.streamId;
 | 
			
		||||
          dc->streamSeq = 0;
 | 
			
		||||
          dc->protoId = DCProto_Control;
 | 
			
		||||
          dc->userData = &outType;
 | 
			
		||||
          dc->userDataLength = 1;
 | 
			
		||||
 | 
			
		||||
          if (client->state != WuClient_DataChannelOpen) {
 | 
			
		||||
            client->state = WuClient_DataChannelOpen;
 | 
			
		||||
            WuEvent event;
 | 
			
		||||
            event.type = WuEvent_ClientJoin;
 | 
			
		||||
            event.client = client;
 | 
			
		||||
            WuPushEvent(wu, event);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          WuSendSctp(wu, client, &response, &rc, 1);
 | 
			
		||||
        }
 | 
			
		||||
      } else if (dataChunk->protoId == DCProto_String) {
 | 
			
		||||
        WuEvent evt;
 | 
			
		||||
        evt.type = WuEvent_TextData;
 | 
			
		||||
        evt.client = client;
 | 
			
		||||
        evt.data = dataChunk->userData;
 | 
			
		||||
        evt.length = dataChunk->userDataLength;
 | 
			
		||||
        WuPushEvent(wu, evt);
 | 
			
		||||
      } else if (dataChunk->protoId == DCProto_Binary) {
 | 
			
		||||
        WuEvent evt;
 | 
			
		||||
        evt.type = WuEvent_BinaryData;
 | 
			
		||||
        evt.client = client;
 | 
			
		||||
        evt.data = dataChunk->userData;
 | 
			
		||||
        evt.length = dataChunk->userDataLength;
 | 
			
		||||
        WuPushEvent(wu, evt);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      SctpPacket sack;
 | 
			
		||||
      sack.sourcePort = sctpPacket.destionationPort;
 | 
			
		||||
      sack.destionationPort = sctpPacket.sourcePort;
 | 
			
		||||
      sack.verificationTag = client->sctpVerificationTag;
 | 
			
		||||
 | 
			
		||||
      SctpChunk rc;
 | 
			
		||||
      rc.type = Sctp_Sack;
 | 
			
		||||
      rc.flags = 0;
 | 
			
		||||
      rc.length = SctpChunkLength(12);
 | 
			
		||||
      rc.as.sack.cumulativeTsnAck = client->remoteTsn;
 | 
			
		||||
      rc.as.sack.advRecvWindow = kSctpDefaultBufferSpace;
 | 
			
		||||
      rc.as.sack.numGapAckBlocks = 0;
 | 
			
		||||
      rc.as.sack.numDupTsn = 0;
 | 
			
		||||
 | 
			
		||||
      WuSendSctp(wu, client, &sack, &rc, 1);
 | 
			
		||||
    } else if (chunk->type == Sctp_Init) {
 | 
			
		||||
      SctpPacket response;
 | 
			
		||||
      response.sourcePort = sctpPacket.destionationPort;
 | 
			
		||||
      response.destionationPort = sctpPacket.sourcePort;
 | 
			
		||||
      response.verificationTag = chunk->as.init.initiateTag;
 | 
			
		||||
      client->sctpVerificationTag = response.verificationTag;
 | 
			
		||||
      client->remoteTsn = chunk->as.init.initialTsn - 1;
 | 
			
		||||
 | 
			
		||||
      SctpChunk rc;
 | 
			
		||||
      rc.type = Sctp_InitAck;
 | 
			
		||||
      rc.flags = 0;
 | 
			
		||||
      rc.length = kSctpMinInitAckLength;
 | 
			
		||||
 | 
			
		||||
      rc.as.init.initiateTag = WuRandomU32();
 | 
			
		||||
      rc.as.init.windowCredit = kSctpDefaultBufferSpace;
 | 
			
		||||
      rc.as.init.numOutboundStreams = chunk->as.init.numInboundStreams;
 | 
			
		||||
      rc.as.init.numInboundStreams = chunk->as.init.numOutboundStreams;
 | 
			
		||||
      rc.as.init.initialTsn = client->tsn;
 | 
			
		||||
 | 
			
		||||
      WuSendSctp(wu, client, &response, &rc, 1);
 | 
			
		||||
      break;
 | 
			
		||||
    } else if (chunk->type == Sctp_CookieEcho) {
 | 
			
		||||
      if (client->state < WuClient_SCTPEstablished) {
 | 
			
		||||
        client->state = WuClient_SCTPEstablished;
 | 
			
		||||
      }
 | 
			
		||||
      SctpPacket response;
 | 
			
		||||
      response.sourcePort = sctpPacket.destionationPort;
 | 
			
		||||
      response.destionationPort = sctpPacket.sourcePort;
 | 
			
		||||
      response.verificationTag = client->sctpVerificationTag;
 | 
			
		||||
 | 
			
		||||
      SctpChunk rc;
 | 
			
		||||
      rc.type = Sctp_CookieAck;
 | 
			
		||||
      rc.flags = 0;
 | 
			
		||||
      rc.length = SctpChunkLength(0);
 | 
			
		||||
 | 
			
		||||
      WuSendSctp(wu, client, &response, &rc, 1);
 | 
			
		||||
    } else if (chunk->type == Sctp_Heartbeat) {
 | 
			
		||||
      SctpPacket response;
 | 
			
		||||
      response.sourcePort = sctpPacket.destionationPort;
 | 
			
		||||
      response.destionationPort = sctpPacket.sourcePort;
 | 
			
		||||
      response.verificationTag = client->sctpVerificationTag;
 | 
			
		||||
 | 
			
		||||
      SctpChunk rc;
 | 
			
		||||
      rc.type = Sctp_HeartbeatAck;
 | 
			
		||||
      rc.flags = 0;
 | 
			
		||||
      rc.length = chunk->length;
 | 
			
		||||
      rc.as.heartbeat.heartbeatInfoLen = chunk->as.heartbeat.heartbeatInfoLen;
 | 
			
		||||
      rc.as.heartbeat.heartbeatInfo = chunk->as.heartbeat.heartbeatInfo;
 | 
			
		||||
 | 
			
		||||
      client->ttl = kMaxClientTtl;
 | 
			
		||||
 | 
			
		||||
      WuSendSctp(wu, client, &response, &rc, 1);
 | 
			
		||||
    } else if (chunk->type == Sctp_HeartbeatAck) {
 | 
			
		||||
      client->ttl = kMaxClientTtl;
 | 
			
		||||
    } else if (chunk->type == Sctp_Abort) {
 | 
			
		||||
      client->state = WuClient_WaitingRemoval;
 | 
			
		||||
      return;
 | 
			
		||||
    } else if (chunk->type == Sctp_Sack) {
 | 
			
		||||
      client->ttl = kMaxClientTtl;
 | 
			
		||||
 | 
			
		||||
      auto* sack = &chunk->as.sack;
 | 
			
		||||
      if (sack->numGapAckBlocks > 0) {
 | 
			
		||||
        SctpPacket fwdResponse;
 | 
			
		||||
        fwdResponse.sourcePort = sctpPacket.destionationPort;
 | 
			
		||||
        fwdResponse.destionationPort = sctpPacket.sourcePort;
 | 
			
		||||
        fwdResponse.verificationTag = client->sctpVerificationTag;
 | 
			
		||||
 | 
			
		||||
        SctpChunk fwdTsnChunk;
 | 
			
		||||
        fwdTsnChunk.type = SctpChunk_ForwardTsn;
 | 
			
		||||
        fwdTsnChunk.flags = 0;
 | 
			
		||||
        fwdTsnChunk.length = SctpChunkLength(4);
 | 
			
		||||
        fwdTsnChunk.as.forwardTsn.newCumulativeTsn = client->tsn;
 | 
			
		||||
        WuSendSctp(wu, client, &fwdResponse, &fwdTsnChunk, 1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuReceiveDTLSPacket(Wu* wu, const uint8_t* data, size_t length,
 | 
			
		||||
                                const WuAddress* address) {
 | 
			
		||||
  WuClient* client = WuFindClient(wu, address);
 | 
			
		||||
  if (!client) {
 | 
			
		||||
    WuReportDebug(wu, "DTLS: No client found");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  BIO_write(client->inBio, data, length);
 | 
			
		||||
 | 
			
		||||
  if (!SSL_is_init_finished(client->ssl)) {
 | 
			
		||||
    int r = SSL_do_handshake(client->ssl);
 | 
			
		||||
 | 
			
		||||
    if (r <= 0) {
 | 
			
		||||
      r = SSL_get_error(client->ssl, r);
 | 
			
		||||
      if (SSL_ERROR_WANT_READ == r) {
 | 
			
		||||
        WuClientSendPendingDTLS(wu, client);
 | 
			
		||||
      } else if (SSL_ERROR_NONE != r) {
 | 
			
		||||
        char* error = ERR_error_string(r, NULL);
 | 
			
		||||
        if (error) {
 | 
			
		||||
          WuReportError(wu, error);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    WuClientSendPendingDTLS(wu, client);
 | 
			
		||||
 | 
			
		||||
    while (BIO_ctrl_pending(client->inBio) > 0) {
 | 
			
		||||
      uint8_t receiveBuffer[8092];
 | 
			
		||||
      int bytes = SSL_read(client->ssl, receiveBuffer, sizeof(receiveBuffer));
 | 
			
		||||
 | 
			
		||||
      if (bytes > 0) {
 | 
			
		||||
        uint8_t* buf = (uint8_t*)WuArenaAcquire(wu->arena, bytes);
 | 
			
		||||
        memcpy(buf, receiveBuffer, bytes);
 | 
			
		||||
        WuHandleSctp(wu, client, buf, bytes);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuHandleStun(Wu* wu, const StunPacket* packet,
 | 
			
		||||
                         const WuAddress* remote) {
 | 
			
		||||
  WuClient* client =
 | 
			
		||||
      WuFindClientByCreds(wu, &packet->serverUser, &packet->remoteUser);
 | 
			
		||||
 | 
			
		||||
  if (!client) {
 | 
			
		||||
    WuReportDebug(wu, "Stun: No client found");
 | 
			
		||||
    // TODO: Send unauthorized
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  StunPacket outPacket;
 | 
			
		||||
  outPacket.type = Stun_SuccessResponse;
 | 
			
		||||
  memcpy(outPacket.transactionId, packet->transactionId,
 | 
			
		||||
         kStunTransactionIdLength);
 | 
			
		||||
  outPacket.xorMappedAddress.family = Stun_IPV4;
 | 
			
		||||
  outPacket.xorMappedAddress.port = ByteSwap(remote->port ^ kStunXorMagic);
 | 
			
		||||
  outPacket.xorMappedAddress.address.ipv4 =
 | 
			
		||||
      ByteSwap(remote->host ^ kStunCookie);
 | 
			
		||||
 | 
			
		||||
  uint8_t stunResponse[512];
 | 
			
		||||
  size_t serializedSize =
 | 
			
		||||
      SerializeStunPacket(&outPacket, client->serverPassword.identifier,
 | 
			
		||||
                          client->serverPassword.length, stunResponse, 512);
 | 
			
		||||
 | 
			
		||||
  client->localSctpPort = remote->port;
 | 
			
		||||
  client->address = *remote;
 | 
			
		||||
 | 
			
		||||
  wu->writeUdpData(stunResponse, serializedSize, client, wu->userData);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuPurgeDeadClients(Wu* wu) {
 | 
			
		||||
  for (int32_t i = 0; i < wu->numClients; i++) {
 | 
			
		||||
    WuClient* client = wu->clients[i];
 | 
			
		||||
    if (client->ttl <= 0.0 || client->state == WuClient_WaitingRemoval) {
 | 
			
		||||
 | 
			
		||||
      if (client->ttl <= 0.0)
 | 
			
		||||
        WuReportDebug(wu, "Removing dead client due to no messages in 9s");
 | 
			
		||||
      else
 | 
			
		||||
        WuReportDebug(wu, "Removing client due to its own request");
 | 
			
		||||
 | 
			
		||||
      WuEvent evt;
 | 
			
		||||
      evt.type = WuEvent_ClientLeave;
 | 
			
		||||
      evt.client = client;
 | 
			
		||||
      WuPushEvent(wu, evt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int32_t WuCryptoInit(Wu* wu) {
 | 
			
		||||
 | 
			
		||||
  wu->sslCtx = SSL_CTX_new(DTLS_server_method());
 | 
			
		||||
  if (!wu->sslCtx) {
 | 
			
		||||
    ERR_print_errors_fp(stderr);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int sslStatus =
 | 
			
		||||
      SSL_CTX_set_cipher_list(wu->sslCtx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
 | 
			
		||||
  if (sslStatus != 1) {
 | 
			
		||||
    ERR_print_errors_fp(stderr);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SSL_CTX_set_verify(wu->sslCtx, SSL_VERIFY_NONE, NULL);
 | 
			
		||||
 | 
			
		||||
  WuCert cert;
 | 
			
		||||
 | 
			
		||||
  sslStatus = SSL_CTX_use_PrivateKey(wu->sslCtx, cert.key);
 | 
			
		||||
 | 
			
		||||
  if (sslStatus != 1) {
 | 
			
		||||
    ERR_print_errors_fp(stderr);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sslStatus = SSL_CTX_use_certificate(wu->sslCtx, cert.x509);
 | 
			
		||||
 | 
			
		||||
  if (sslStatus != 1) {
 | 
			
		||||
    ERR_print_errors_fp(stderr);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sslStatus = SSL_CTX_check_private_key(wu->sslCtx);
 | 
			
		||||
 | 
			
		||||
  if (sslStatus != 1) {
 | 
			
		||||
    ERR_print_errors_fp(stderr);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SSL_CTX_set_options(wu->sslCtx, SSL_OP_NO_QUERY_MTU);
 | 
			
		||||
 | 
			
		||||
  memcpy(wu->certFingerprint, cert.fingerprint, sizeof(cert.fingerprint));
 | 
			
		||||
 | 
			
		||||
  return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t WuCreate(const char* host, uint16_t port, int maxClients, Wu** wu) {
 | 
			
		||||
  *wu = NULL;
 | 
			
		||||
 | 
			
		||||
  Wu* ctx = (Wu*)calloc(1, sizeof(Wu));
 | 
			
		||||
 | 
			
		||||
  if (!ctx) {
 | 
			
		||||
    return WU_OUT_OF_MEMORY;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ctx->arena = (WuArena*)calloc(1, sizeof(WuArena));
 | 
			
		||||
 | 
			
		||||
  if (!ctx->arena) {
 | 
			
		||||
    WuDestroy(ctx);
 | 
			
		||||
    return WU_OUT_OF_MEMORY;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WuArenaInit(ctx->arena, 1 << 20);
 | 
			
		||||
 | 
			
		||||
  ctx->time = MsNow() * 0.001;
 | 
			
		||||
  ctx->port = port;
 | 
			
		||||
  ctx->pendingEvents = WuQueueCreate(sizeof(WuEvent), 1024);
 | 
			
		||||
  ctx->errorCallback = DefaultErrorCallback;
 | 
			
		||||
  ctx->debugCallback = DefaultErrorCallback;
 | 
			
		||||
  ctx->writeUdpData = WriteNothing;
 | 
			
		||||
 | 
			
		||||
  strncpy(ctx->host, host, sizeof(ctx->host));
 | 
			
		||||
 | 
			
		||||
  if (!WuCryptoInit(ctx)) {
 | 
			
		||||
    WuDestroy(ctx);
 | 
			
		||||
    return WU_ERROR;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ctx->maxClients = maxClients <= 0 ? 256 : maxClients;
 | 
			
		||||
  ctx->clientPool = WuPoolCreate(sizeof(WuClient), ctx->maxClients);
 | 
			
		||||
  ctx->clients = (WuClient**)calloc(ctx->maxClients, sizeof(WuClient*));
 | 
			
		||||
 | 
			
		||||
  *wu = ctx;
 | 
			
		||||
  return WU_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuSendHeartbeat(Wu* wu, WuClient* client) {
 | 
			
		||||
  SctpPacket packet;
 | 
			
		||||
  packet.sourcePort = wu->port;
 | 
			
		||||
  packet.destionationPort = client->remoteSctpPort;
 | 
			
		||||
  packet.verificationTag = client->sctpVerificationTag;
 | 
			
		||||
 | 
			
		||||
  SctpChunk rc;
 | 
			
		||||
  rc.type = Sctp_Heartbeat;
 | 
			
		||||
  rc.flags = kSctpFlagCompleteUnreliable;
 | 
			
		||||
  rc.length = SctpChunkLength(4 + 8);
 | 
			
		||||
  rc.as.heartbeat.heartbeatInfo = (const uint8_t*)&wu->time;
 | 
			
		||||
  rc.as.heartbeat.heartbeatInfoLen = sizeof(wu->time);
 | 
			
		||||
 | 
			
		||||
  WuSendSctp(wu, client, &packet, &rc, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuUpdateClients(Wu* wu) {
 | 
			
		||||
  double t = MsNow() * 0.001;
 | 
			
		||||
  wu->dt = t - wu->time;
 | 
			
		||||
  wu->time = t;
 | 
			
		||||
 | 
			
		||||
  for (int32_t i = 0; i < wu->numClients; i++) {
 | 
			
		||||
    WuClient* client = wu->clients[i];
 | 
			
		||||
    client->ttl -= wu->dt;
 | 
			
		||||
    client->nextHeartbeat -= wu->dt;
 | 
			
		||||
 | 
			
		||||
    if (client->nextHeartbeat <= 0.0) {
 | 
			
		||||
      client->nextHeartbeat = heartbeatInterval;
 | 
			
		||||
      WuSendHeartbeat(wu, client);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    WuClientSendPendingDTLS(wu, client);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t WuUpdate(Wu* wu, WuEvent* evt) {
 | 
			
		||||
  if (WuQueuePop(wu->pendingEvents, evt)) {
 | 
			
		||||
    return 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WuUpdateClients(wu);
 | 
			
		||||
  WuArenaReset(wu->arena);
 | 
			
		||||
 | 
			
		||||
  WuPurgeDeadClients(wu);
 | 
			
		||||
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int32_t WuSendData(Wu* wu, WuClient* client, const uint8_t* data,
 | 
			
		||||
                          int32_t length, DataChanProtoIdentifier proto) {
 | 
			
		||||
  if (client->state < WuClient_DataChannelOpen) {
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SctpPacket packet;
 | 
			
		||||
  packet.sourcePort = wu->port;
 | 
			
		||||
  packet.destionationPort = client->remoteSctpPort;
 | 
			
		||||
  packet.verificationTag = client->sctpVerificationTag;
 | 
			
		||||
 | 
			
		||||
  SctpChunk rc;
 | 
			
		||||
  rc.type = Sctp_Data;
 | 
			
		||||
  rc.flags = kSctpFlagCompleteUnreliable;
 | 
			
		||||
  rc.length = SctpDataChunkLength(length);
 | 
			
		||||
 | 
			
		||||
  auto* dc = &rc.as.data;
 | 
			
		||||
  dc->tsn = client->tsn++;
 | 
			
		||||
  dc->streamId = 0;  // TODO: Does it matter?
 | 
			
		||||
  dc->streamSeq = 0;
 | 
			
		||||
  dc->protoId = proto;
 | 
			
		||||
  dc->userData = data;
 | 
			
		||||
  dc->userDataLength = length;
 | 
			
		||||
 | 
			
		||||
  WuSendSctp(wu, client, &packet, &rc, 1);
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t WuSendText(Wu* wu, WuClient* client, const char* text, int32_t length) {
 | 
			
		||||
  return WuSendData(wu, client, (const uint8_t*)text, length, DCProto_String);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t WuSendBinary(Wu* wu, WuClient* client, const uint8_t* data,
 | 
			
		||||
                     int32_t length) {
 | 
			
		||||
  return WuSendData(wu, client, data, length, DCProto_Binary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SDPResult WuExchangeSDP(Wu* wu, const char* sdp, int32_t length) {
 | 
			
		||||
  ICESdpFields iceFields;
 | 
			
		||||
  if (!ParseSdp(sdp, length, &iceFields)) {
 | 
			
		||||
    return {WuSDPStatus_InvalidSDP, NULL, NULL, 0};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WuClient* client = WuNewClient(wu);
 | 
			
		||||
 | 
			
		||||
  if (!client) {
 | 
			
		||||
    return {WuSDPStatus_MaxClients, NULL, NULL, 0};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  client->serverUser.length = 4;
 | 
			
		||||
  WuRandomString((char*)client->serverUser.identifier,
 | 
			
		||||
                 client->serverUser.length);
 | 
			
		||||
  client->serverPassword.length = 24;
 | 
			
		||||
  WuRandomString((char*)client->serverPassword.identifier,
 | 
			
		||||
                 client->serverPassword.length);
 | 
			
		||||
  memcpy(client->remoteUser.identifier, iceFields.ufrag.value,
 | 
			
		||||
         Min(iceFields.ufrag.length, kMaxStunIdentifierLength));
 | 
			
		||||
  client->remoteUser.length = iceFields.ufrag.length;
 | 
			
		||||
  memcpy(client->remoteUserPassword.identifier, iceFields.password.value,
 | 
			
		||||
         Min(iceFields.password.length, kMaxStunIdentifierLength));
 | 
			
		||||
 | 
			
		||||
  int sdpLength = 0;
 | 
			
		||||
  const char* responseSdp = GenerateSDP(
 | 
			
		||||
      wu->arena, wu->certFingerprint, wu->host, wu->port,
 | 
			
		||||
      (char*)client->serverUser.identifier, client->serverUser.length,
 | 
			
		||||
      (char*)client->serverPassword.identifier, client->serverPassword.length,
 | 
			
		||||
      &iceFields, &sdpLength);
 | 
			
		||||
 | 
			
		||||
  if (!responseSdp) {
 | 
			
		||||
    return {WuSDPStatus_Error, NULL, NULL, 0};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {WuSDPStatus_Success, client, responseSdp, sdpLength};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuSetUserData(Wu* wu, void* userData) { wu->userData = userData; }
 | 
			
		||||
 | 
			
		||||
void WuHandleUDP(Wu* wu, const WuAddress* remote, const uint8_t* data,
 | 
			
		||||
                 int32_t length) {
 | 
			
		||||
  StunPacket stunPacket;
 | 
			
		||||
  if (ParseStun(data, length, &stunPacket)) {
 | 
			
		||||
    //WuReportDebug(wu, "Received stun packet");
 | 
			
		||||
    WuHandleStun(wu, &stunPacket, remote);
 | 
			
		||||
  } else {
 | 
			
		||||
    //WuReportDebug(wu, "Received DTLS packet");
 | 
			
		||||
    WuReceiveDTLSPacket(wu, data, length, remote);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuSetUDPWriteFunction(Wu* wu, WuWriteFn write) {
 | 
			
		||||
  wu->writeUdpData = write;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WuAddress WuClientGetAddress(const WuClient* client) { return client->address; }
 | 
			
		||||
 | 
			
		||||
void WuSetErrorCallback(Wu* wu, WuErrorFn callback) {
 | 
			
		||||
  if (callback) {
 | 
			
		||||
    wu->errorCallback = callback;
 | 
			
		||||
  } else {
 | 
			
		||||
    wu->errorCallback = DefaultErrorCallback;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuSetDebugCallback(Wu* wu, WuErrorFn callback) {
 | 
			
		||||
  if (callback) {
 | 
			
		||||
    wu->debugCallback = callback;
 | 
			
		||||
  } else {
 | 
			
		||||
    wu->debugCallback = DefaultErrorCallback;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuDestroy(Wu* wu) {
 | 
			
		||||
  if (!wu) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  free(wu);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WuClient* WuFindClient(const Wu* wu, WuAddress address) {
 | 
			
		||||
  for (int32_t i = 0; i < wu->numClients; i++) {
 | 
			
		||||
    WuClient* c = wu->clients[i];
 | 
			
		||||
 | 
			
		||||
    if (c->address.host == address.host && c->address.port == address.port) {
 | 
			
		||||
      return c;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return NULL;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,76 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define WU_OK 0
 | 
			
		||||
#define WU_ERROR 1
 | 
			
		||||
#define WU_OUT_OF_MEMORY 2
 | 
			
		||||
 | 
			
		||||
typedef struct WuClient WuClient;
 | 
			
		||||
typedef struct Wu Wu;
 | 
			
		||||
typedef void (*WuErrorFn)(const char* err, void* userData);
 | 
			
		||||
typedef void (*WuWriteFn)(const uint8_t* data, size_t length,
 | 
			
		||||
                          const WuClient* client, void* userData);
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
  WuEvent_BinaryData,
 | 
			
		||||
  WuEvent_ClientJoin,
 | 
			
		||||
  WuEvent_ClientLeave,
 | 
			
		||||
  WuEvent_TextData
 | 
			
		||||
} WuEventType;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
  WuSDPStatus_Success,
 | 
			
		||||
  WuSDPStatus_InvalidSDP,
 | 
			
		||||
  WuSDPStatus_MaxClients,
 | 
			
		||||
  WuSDPStatus_Error
 | 
			
		||||
} WuSDPStatus;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  WuEventType type;
 | 
			
		||||
  WuClient* client;
 | 
			
		||||
  const uint8_t* data;
 | 
			
		||||
  int32_t length;
 | 
			
		||||
} WuEvent;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  WuSDPStatus status;
 | 
			
		||||
  WuClient* client;
 | 
			
		||||
  const char* sdp;
 | 
			
		||||
  int32_t sdpLength;
 | 
			
		||||
} SDPResult;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
  uint32_t host;
 | 
			
		||||
  uint16_t port;
 | 
			
		||||
} WuAddress;
 | 
			
		||||
 | 
			
		||||
int32_t WuCreate(const char* host, uint16_t port, int maxClients, Wu** wu);
 | 
			
		||||
void WuDestroy(Wu* wu);
 | 
			
		||||
int32_t WuUpdate(Wu* wu, WuEvent* evt);
 | 
			
		||||
int32_t WuSendText(Wu* wu, WuClient* client, const char* text, int32_t length);
 | 
			
		||||
int32_t WuSendBinary(Wu* wu, WuClient* client, const uint8_t* data,
 | 
			
		||||
                     int32_t length);
 | 
			
		||||
void WuReportError(Wu* wu, const char* error);
 | 
			
		||||
void WuReportDebug(Wu* wu, const char* error);
 | 
			
		||||
void WuRemoveClient(Wu* wu, WuClient* client);
 | 
			
		||||
void WuClientSetUserData(WuClient* client, void* user);
 | 
			
		||||
void* WuClientGetUserData(const WuClient* client);
 | 
			
		||||
SDPResult WuExchangeSDP(Wu* wu, const char* sdp, int32_t length);
 | 
			
		||||
void WuHandleUDP(Wu* wu, const WuAddress* remote, const uint8_t* data,
 | 
			
		||||
                 int32_t length);
 | 
			
		||||
void WuSetUDPWriteFunction(Wu* wu, WuWriteFn write);
 | 
			
		||||
void WuSetUserData(Wu* wu, void* userData);
 | 
			
		||||
void WuSetErrorCallback(Wu* wu, WuErrorFn callback);
 | 
			
		||||
void WuSetDebugCallback(Wu* wu, WuErrorFn callback);
 | 
			
		||||
WuAddress WuClientGetAddress(const WuClient* client);
 | 
			
		||||
WuClient* WuFindClient(const Wu* wu, WuAddress address);
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@ -0,0 +1,26 @@
 | 
			
		||||
#include "WuArena.h"
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
void WuArenaInit(WuArena* arena, int32_t capacity) {
 | 
			
		||||
  arena->memory = (uint8_t*)calloc(capacity, 1);
 | 
			
		||||
  arena->length = 0;
 | 
			
		||||
  arena->capacity = capacity;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* WuArenaAcquire(WuArena* arena, int32_t blockSize) {
 | 
			
		||||
  assert(blockSize > 0);
 | 
			
		||||
  int32_t remain = arena->capacity - arena->length;
 | 
			
		||||
 | 
			
		||||
  if (remain >= blockSize) {
 | 
			
		||||
    uint8_t* m = arena->memory + arena->length;
 | 
			
		||||
    arena->length += blockSize;
 | 
			
		||||
    return m;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuArenaReset(WuArena* arena) { arena->length = 0; }
 | 
			
		||||
 | 
			
		||||
void WuArenaDestroy(WuArena* arena) { free(arena->memory); }
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
struct WuArena {
 | 
			
		||||
  uint8_t* memory;
 | 
			
		||||
  int32_t length;
 | 
			
		||||
  int32_t capacity;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void WuArenaInit(WuArena* arena, int32_t capacity);
 | 
			
		||||
void* WuArenaAcquire(WuArena* arena, int32_t blockSize);
 | 
			
		||||
void WuArenaReset(WuArena* arena);
 | 
			
		||||
void WuArenaDestroy(WuArena* arena);
 | 
			
		||||
@ -0,0 +1,48 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
T ByteSwap(T v) {
 | 
			
		||||
  if (sizeof(T) == 1) {
 | 
			
		||||
    return v;
 | 
			
		||||
  } else if (sizeof(T) == 2) {
 | 
			
		||||
    return __builtin_bswap16(uint16_t(v));
 | 
			
		||||
  } else if (sizeof(T) == 4) {
 | 
			
		||||
    return __builtin_bswap32(uint32_t(v));
 | 
			
		||||
  } else if (sizeof(T) == 8) {
 | 
			
		||||
    return __builtin_bswap64(uint64_t(v));
 | 
			
		||||
  } else {
 | 
			
		||||
    assert(0);
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
size_t WriteScalar(uint8_t* dest, T v) {
 | 
			
		||||
  *((T*)dest) = v;
 | 
			
		||||
  return sizeof(T);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
int32_t ReadScalar(const uint8_t* src, T* v) {
 | 
			
		||||
  *v = *(const T*)src;
 | 
			
		||||
  return sizeof(T);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
size_t WriteScalarSwapped(uint8_t* dest, T v) {
 | 
			
		||||
  *((T*)dest) = ByteSwap(v);
 | 
			
		||||
  return sizeof(T);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
int32_t ReadScalarSwapped(const uint8_t* src, T* v) {
 | 
			
		||||
  *v = ByteSwap(*(const T*)src);
 | 
			
		||||
  return sizeof(T);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline int32_t PadSize(int32_t numBytes, int32_t alignBytes) {
 | 
			
		||||
  return ((numBytes + alignBytes - 1) & ~(alignBytes - 1)) - numBytes;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
#include <windows.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <sys/time.h>
 | 
			
		||||
#endif
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
inline int64_t HpCounter() {
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
  LARGE_INTEGER li;
 | 
			
		||||
  QueryPerformanceCounter(&li);
 | 
			
		||||
  int64_t i64 = li.QuadPart;
 | 
			
		||||
#else
 | 
			
		||||
  struct timeval t;
 | 
			
		||||
  gettimeofday(&t, 0);
 | 
			
		||||
  int64_t i64 = t.tv_sec * int64_t(1000000) + t.tv_usec;
 | 
			
		||||
#endif
 | 
			
		||||
  return i64;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline int64_t HpFreq() {
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
  LARGE_INTEGER li;
 | 
			
		||||
  QueryPerformanceFrequency(&li);
 | 
			
		||||
  return li.QuadPart;
 | 
			
		||||
#else
 | 
			
		||||
  return int64_t(1000000);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline double MsNow() {
 | 
			
		||||
  return double(HpCounter()) * 1000.0 / double(HpFreq());
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,65 @@
 | 
			
		||||
#include "WuCrypto.h"
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <openssl/hmac.h>
 | 
			
		||||
#include <openssl/rand.h>
 | 
			
		||||
#include "WuRng.h"
 | 
			
		||||
 | 
			
		||||
WuSHA1Digest WuSHA1(const uint8_t* src, size_t len, const void* key,
 | 
			
		||||
                    size_t keyLen) {
 | 
			
		||||
  WuSHA1Digest digest;
 | 
			
		||||
  HMAC(EVP_sha1(), key, keyLen, src, len, digest.bytes, NULL);
 | 
			
		||||
 | 
			
		||||
  return digest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WuCert::WuCert() : key(EVP_PKEY_new()), x509(X509_new()) {
 | 
			
		||||
  RSA* rsa = RSA_new();
 | 
			
		||||
  BIGNUM* n = BN_new();
 | 
			
		||||
  BN_set_word(n, RSA_F4);
 | 
			
		||||
 | 
			
		||||
  if (!RAND_status()) {
 | 
			
		||||
    uint64_t seed = WuRandomU64();
 | 
			
		||||
    RAND_seed(&seed, sizeof(seed));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  RSA_generate_key_ex(rsa, 2048, n, NULL);
 | 
			
		||||
  EVP_PKEY_assign_RSA(key, rsa);
 | 
			
		||||
 | 
			
		||||
  BIGNUM* serial = BN_new();
 | 
			
		||||
  X509_NAME* name = X509_NAME_new();
 | 
			
		||||
  X509_set_pubkey(x509, key);
 | 
			
		||||
  BN_pseudo_rand(serial, 64, 0, 0);
 | 
			
		||||
 | 
			
		||||
  X509_set_version(x509, 0L);
 | 
			
		||||
  X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8,
 | 
			
		||||
                             (unsigned char*)"wusocket", -1, -1, 0);
 | 
			
		||||
  X509_set_subject_name(x509, name);
 | 
			
		||||
  X509_set_issuer_name(x509, name);
 | 
			
		||||
  X509_gmtime_adj(X509_get_notBefore(x509), 0);
 | 
			
		||||
  X509_gmtime_adj(X509_get_notAfter(x509), 365 * 24 * 3600);
 | 
			
		||||
  X509_sign(x509, key, EVP_sha1());
 | 
			
		||||
 | 
			
		||||
  unsigned int len = 32;
 | 
			
		||||
  uint8_t buf[32] = {0};
 | 
			
		||||
  X509_digest(x509, EVP_sha256(), buf, &len);
 | 
			
		||||
 | 
			
		||||
  assert(len == 32);
 | 
			
		||||
  for (unsigned int i = 0; i < len; i++) {
 | 
			
		||||
    if (i < 31) {
 | 
			
		||||
      snprintf(fingerprint + i * 3, 4, "%02X:", buf[i]);
 | 
			
		||||
    } else {
 | 
			
		||||
      snprintf(fingerprint + i * 3, 3, "%02X", buf[i]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fingerprint[95] = '\0';
 | 
			
		||||
 | 
			
		||||
  BN_free(n);
 | 
			
		||||
  BN_free(serial);
 | 
			
		||||
  X509_NAME_free(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WuCert::~WuCert() {
 | 
			
		||||
  EVP_PKEY_free(key);
 | 
			
		||||
  X509_free(x509);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <openssl/x509.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
const size_t kSHA1Length = 20;
 | 
			
		||||
 | 
			
		||||
struct WuSHA1Digest {
 | 
			
		||||
  uint8_t bytes[kSHA1Length];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct WuCert {
 | 
			
		||||
  WuCert();
 | 
			
		||||
  ~WuCert();
 | 
			
		||||
 | 
			
		||||
  EVP_PKEY* key;
 | 
			
		||||
  X509* x509;
 | 
			
		||||
  char fingerprint[96];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
WuSHA1Digest WuSHA1(const uint8_t* src, size_t len, const void* key,
 | 
			
		||||
                    size_t keyLen);
 | 
			
		||||
@ -0,0 +1,36 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include "Wu.h"
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
typedef struct WuHost WuHost;
 | 
			
		||||
 | 
			
		||||
int32_t WuHostCreate(const char* hostAddr, uint16_t port, int32_t maxClients,
 | 
			
		||||
                     WuHost** host);
 | 
			
		||||
void WuHostDestroy(WuHost* host);
 | 
			
		||||
/*
 | 
			
		||||
 * Timeout:
 | 
			
		||||
 *  -1 = Block until an event
 | 
			
		||||
 *   0 = Return immediately
 | 
			
		||||
 *  >0 = Block for N milliseconds
 | 
			
		||||
 * Returns 1 if an event was received, 0 otherwise.
 | 
			
		||||
 */
 | 
			
		||||
int32_t WuHostServe(WuHost* host, WuEvent* evt, int timeout);
 | 
			
		||||
void WuHostRemoveClient(WuHost* wu, WuClient* client);
 | 
			
		||||
int32_t WuHostSendText(WuHost* host, WuClient* client, const char* text,
 | 
			
		||||
                       int32_t length);
 | 
			
		||||
int32_t WuHostSendBinary(WuHost* host, WuClient* client, const uint8_t* data,
 | 
			
		||||
                         int32_t length);
 | 
			
		||||
void WuHostSetErrorCallback(WuHost* host, WuErrorFn callback);
 | 
			
		||||
void WuHostSetDebugCallback(WuHost* host, WuErrorFn callback);
 | 
			
		||||
WuClient* WuHostFindClient(const WuHost* host, WuAddress address);
 | 
			
		||||
 | 
			
		||||
void WuGotHttp(WuHost *host, const char msg[], const uint32_t msglen,
 | 
			
		||||
               char resp[]);
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@ -0,0 +1,268 @@
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <netinet/tcp.h>
 | 
			
		||||
#include <pthread.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <sys/epoll.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include "WuHost.h"
 | 
			
		||||
#include "WuHttp.h"
 | 
			
		||||
#include "WuMath.h"
 | 
			
		||||
#include "WuNetwork.h"
 | 
			
		||||
#include "WuPool.h"
 | 
			
		||||
#include "WuRng.h"
 | 
			
		||||
#include "WuString.h"
 | 
			
		||||
 | 
			
		||||
static pthread_mutex_t wumutex = PTHREAD_MUTEX_INITIALIZER;
 | 
			
		||||
 | 
			
		||||
struct WuConnectionBuffer {
 | 
			
		||||
  size_t size = 0;
 | 
			
		||||
  int fd = -1;
 | 
			
		||||
  uint8_t requestBuffer[kMaxHttpRequestLength];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct WuHost {
 | 
			
		||||
  Wu* wu;
 | 
			
		||||
  int udpfd;
 | 
			
		||||
  int epfd;
 | 
			
		||||
  int pollTimeout;
 | 
			
		||||
  WuPool* bufferPool;
 | 
			
		||||
  struct epoll_event* events;
 | 
			
		||||
  int32_t maxEvents;
 | 
			
		||||
  uint16_t port;
 | 
			
		||||
  char errBuf[512];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void HostReclaimBuffer(WuHost* host, WuConnectionBuffer* buffer) {
 | 
			
		||||
  buffer->fd = -1;
 | 
			
		||||
  buffer->size = 0;
 | 
			
		||||
  WuPoolRelease(host->bufferPool, buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static WuConnectionBuffer* HostGetBuffer(WuHost* host) {
 | 
			
		||||
  WuConnectionBuffer* buffer = (WuConnectionBuffer*)WuPoolAcquire(host->bufferPool);
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void HandleErrno(WuHost* host, const char* description) {
 | 
			
		||||
  snprintf(host->errBuf, sizeof(host->errBuf), "%s: %s", description,
 | 
			
		||||
           strerror(errno));
 | 
			
		||||
  WuReportError(host->wu, host->errBuf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WriteUDPData(const uint8_t* data, size_t length,
 | 
			
		||||
                         const WuClient* client, void* userData) {
 | 
			
		||||
  WuHost* host = (WuHost*)userData;
 | 
			
		||||
 | 
			
		||||
  WuAddress address = WuClientGetAddress(client);
 | 
			
		||||
  struct sockaddr_in netaddr;
 | 
			
		||||
  netaddr.sin_family = AF_INET;
 | 
			
		||||
  netaddr.sin_port = htons(address.port);
 | 
			
		||||
  netaddr.sin_addr.s_addr = htonl(address.host);
 | 
			
		||||
 | 
			
		||||
  sendto(host->udpfd, data, length, 0, (struct sockaddr*)&netaddr,
 | 
			
		||||
         sizeof(netaddr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t WuHostServe(WuHost* host, WuEvent* evt, int timeout) {
 | 
			
		||||
  if (pthread_mutex_lock(&wumutex))
 | 
			
		||||
    abort();
 | 
			
		||||
  int32_t hres = WuUpdate(host->wu, evt);
 | 
			
		||||
  pthread_mutex_unlock(&wumutex);
 | 
			
		||||
 | 
			
		||||
  if (hres) {
 | 
			
		||||
    return hres;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int n =
 | 
			
		||||
      epoll_wait(host->epfd, host->events, host->maxEvents, timeout);
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i < n; i++) {
 | 
			
		||||
    struct epoll_event* e = &host->events[i];
 | 
			
		||||
    WuConnectionBuffer* c = (WuConnectionBuffer*)e->data.ptr;
 | 
			
		||||
 | 
			
		||||
    if ((e->events & EPOLLERR) || (e->events & EPOLLHUP) ||
 | 
			
		||||
        (!(e->events & EPOLLIN))) {
 | 
			
		||||
      close(c->fd);
 | 
			
		||||
      HostReclaimBuffer(host, c);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (host->udpfd == c->fd) {
 | 
			
		||||
      struct sockaddr_in remote;
 | 
			
		||||
      socklen_t remoteLen = sizeof(remote);
 | 
			
		||||
      uint8_t buf[4096];
 | 
			
		||||
 | 
			
		||||
      ssize_t r = 0;
 | 
			
		||||
      while ((r = recvfrom(host->udpfd, buf, sizeof(buf), 0,
 | 
			
		||||
                           (struct sockaddr*)&remote, &remoteLen)) > 0) {
 | 
			
		||||
        WuAddress address;
 | 
			
		||||
        address.host = ntohl(remote.sin_addr.s_addr);
 | 
			
		||||
        address.port = ntohs(remote.sin_port);
 | 
			
		||||
 | 
			
		||||
        if (pthread_mutex_lock(&wumutex))
 | 
			
		||||
          abort();
 | 
			
		||||
        WuHandleUDP(host->wu, &address, buf, r);
 | 
			
		||||
        pthread_mutex_unlock(&wumutex);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    } else {
 | 
			
		||||
      WuReportError(host->wu, "Unknown epoll source");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t WuHostCreate(const char* hostAddr, uint16_t port, int32_t maxClients, WuHost** host) {
 | 
			
		||||
  *host = NULL;
 | 
			
		||||
 | 
			
		||||
  WuHost* ctx = (WuHost*)calloc(1, sizeof(WuHost));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  if (!ctx) {
 | 
			
		||||
    return WU_OUT_OF_MEMORY;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int32_t status = WuCreate(hostAddr, port, maxClients, &ctx->wu);
 | 
			
		||||
 | 
			
		||||
  if (status != WU_OK) {
 | 
			
		||||
    free(ctx);
 | 
			
		||||
    return status;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ctx->udpfd = CreateSocket(port);
 | 
			
		||||
 | 
			
		||||
  if (ctx->udpfd == -1) {
 | 
			
		||||
    WuHostDestroy(ctx);
 | 
			
		||||
    return WU_ERROR;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  status = MakeNonBlocking(ctx->udpfd);
 | 
			
		||||
  if (status == -1) {
 | 
			
		||||
    WuHostDestroy(ctx);
 | 
			
		||||
    return WU_ERROR;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ctx->epfd = epoll_create(1024);
 | 
			
		||||
  if (ctx->epfd == -1) {
 | 
			
		||||
    WuHostDestroy(ctx);
 | 
			
		||||
    return WU_ERROR;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const int32_t maxEvents = 128;
 | 
			
		||||
  ctx->bufferPool = WuPoolCreate(sizeof(WuConnectionBuffer), maxEvents + 2);
 | 
			
		||||
 | 
			
		||||
  if (!ctx->bufferPool) {
 | 
			
		||||
    WuHostDestroy(ctx);
 | 
			
		||||
    return WU_OUT_OF_MEMORY;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WuConnectionBuffer* udpBuf = HostGetBuffer(ctx);
 | 
			
		||||
  udpBuf->fd = ctx->udpfd;
 | 
			
		||||
 | 
			
		||||
  struct epoll_event event;
 | 
			
		||||
  event.events = EPOLLIN | EPOLLET;
 | 
			
		||||
  event.data.ptr = udpBuf;
 | 
			
		||||
  status = epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, ctx->udpfd, &event);
 | 
			
		||||
  if (status == -1) {
 | 
			
		||||
    WuHostDestroy(ctx);
 | 
			
		||||
    return WU_ERROR;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ctx->maxEvents = maxEvents;
 | 
			
		||||
  ctx->events = (struct epoll_event*)calloc(ctx->maxEvents, sizeof(event));
 | 
			
		||||
 | 
			
		||||
  if (!ctx->events) {
 | 
			
		||||
    WuHostDestroy(ctx);
 | 
			
		||||
    return WU_OUT_OF_MEMORY;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WuSetUserData(ctx->wu, ctx);
 | 
			
		||||
  WuSetUDPWriteFunction(ctx->wu, WriteUDPData);
 | 
			
		||||
 | 
			
		||||
  *host = ctx;
 | 
			
		||||
 | 
			
		||||
  return WU_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuHostRemoveClient(WuHost* host, WuClient* client) {
 | 
			
		||||
  WuRemoveClient(host->wu, client);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t WuHostSendText(WuHost* host, WuClient* client, const char* text,
 | 
			
		||||
                       int32_t length) {
 | 
			
		||||
  return WuSendText(host->wu, client, text, length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t WuHostSendBinary(WuHost* host, WuClient* client, const uint8_t* data,
 | 
			
		||||
                         int32_t length) {
 | 
			
		||||
  if (pthread_mutex_lock(&wumutex))
 | 
			
		||||
    abort();
 | 
			
		||||
  int32_t ret = WuSendBinary(host->wu, client, data, length);
 | 
			
		||||
  pthread_mutex_unlock(&wumutex);
 | 
			
		||||
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuHostSetErrorCallback(WuHost* host, WuErrorFn callback) {
 | 
			
		||||
  WuSetErrorCallback(host->wu, callback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuHostSetDebugCallback(WuHost* host, WuErrorFn callback) {
 | 
			
		||||
  WuSetDebugCallback(host->wu, callback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuHostDestroy(WuHost* host) {
 | 
			
		||||
  if (!host) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WuDestroy(host->wu);
 | 
			
		||||
 | 
			
		||||
  if (host->udpfd != -1) {
 | 
			
		||||
    close(host->udpfd);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (host->epfd != -1) {
 | 
			
		||||
    close(host->epfd);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (host->bufferPool) {
 | 
			
		||||
    free(host->bufferPool);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (host->events) {
 | 
			
		||||
    free(host->events);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WuClient* WuHostFindClient(const WuHost* host, WuAddress address) {
 | 
			
		||||
  return WuFindClient(host->wu, address);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuGotHttp(WuHost *host, const char msg[], const uint32_t msglen,
 | 
			
		||||
               char resp[]) {
 | 
			
		||||
 | 
			
		||||
  const SDPResult sdp = WuExchangeSDP(
 | 
			
		||||
      host->wu, msg, msglen);
 | 
			
		||||
 | 
			
		||||
  if (sdp.status == WuSDPStatus_Success) {
 | 
			
		||||
    snprintf(resp, 4096,
 | 
			
		||||
             "%.*s",
 | 
			
		||||
             sdp.sdpLength, sdp.sdp);
 | 
			
		||||
  } else if (sdp.status == WuSDPStatus_MaxClients) {
 | 
			
		||||
    WuReportError(host->wu, "Too many connections");
 | 
			
		||||
    strcpy(resp, HTTP_UNAVAILABLE);
 | 
			
		||||
  } else if (sdp.status == WuSDPStatus_InvalidSDP) {
 | 
			
		||||
    WuReportError(host->wu, "Invalid SDP");
 | 
			
		||||
    strcpy(resp, HTTP_BAD_REQUEST);
 | 
			
		||||
  } else {
 | 
			
		||||
    WuReportError(host->wu, "Other error");
 | 
			
		||||
    strcpy(resp, HTTP_SERVER_ERROR);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,11 @@
 | 
			
		||||
#include "WuHost.h"
 | 
			
		||||
 | 
			
		||||
int32_t WuHostCreate(const char*, const char*, int32_t, WuHost** host) {
 | 
			
		||||
  *host = NULL;
 | 
			
		||||
  return WU_OK;
 | 
			
		||||
}
 | 
			
		||||
int32_t WuHostServe(WuHost*, WuEvent*, int) { return 0; }
 | 
			
		||||
void WuHostRemoveClient(WuHost*, WuClient*) {}
 | 
			
		||||
int32_t WuHostSendText(WuHost*, WuClient*, const char*, int32_t) { return 0; }
 | 
			
		||||
int32_t WuHostSendBinary(WuHost*, WuClient*, const uint8_t*, int32_t) { return 0; }
 | 
			
		||||
void WuHostSetErrorCallback(WuHost*, WuErrorFn) {}
 | 
			
		||||
@ -0,0 +1,9 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
#define HTTP_BAD_REQUEST "HTTP/1.1 400 Bad request\r\n\r\n"
 | 
			
		||||
#define HTTP_UNAVAILABLE "HTTP/1.1 503 Service Unavailable\r\n\r\n"
 | 
			
		||||
#define HTTP_SERVER_ERROR "HTTP/1.1 500 Internal Server Error\r\n\r\n"
 | 
			
		||||
 | 
			
		||||
const size_t kMaxHttpRequestLength = 4096;
 | 
			
		||||
@ -0,0 +1,15 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
const T& Min(const T& a, const T& b) {
 | 
			
		||||
  if (a < b) return a;
 | 
			
		||||
 | 
			
		||||
  return b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
const T& Max(const T& a, const T& b) {
 | 
			
		||||
  if (a > b) return a;
 | 
			
		||||
 | 
			
		||||
  return b;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,58 @@
 | 
			
		||||
#include "WuNetwork.h"
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
 | 
			
		||||
void HexDump(const uint8_t* src, size_t len) {
 | 
			
		||||
  for (size_t i = 0; i < len; i++) {
 | 
			
		||||
    if (i % 8 == 0) printf("%04x ", uint32_t(i));
 | 
			
		||||
 | 
			
		||||
    printf("%02x ", src[i]);
 | 
			
		||||
 | 
			
		||||
    if ((i + 1) % 8 == 0) printf("\n");
 | 
			
		||||
  }
 | 
			
		||||
  printf("\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int MakeNonBlocking(int sfd) {
 | 
			
		||||
  int flags = fcntl(sfd, F_GETFL, 0);
 | 
			
		||||
  if (flags == -1) {
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  flags |= O_NONBLOCK;
 | 
			
		||||
 | 
			
		||||
  int s = fcntl(sfd, F_SETFL, flags);
 | 
			
		||||
  if (s == -1) {
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int CreateSocket(uint16_t port) {
 | 
			
		||||
 | 
			
		||||
  int sfd = socket(AF_INET, SOCK_DGRAM, 0);
 | 
			
		||||
  if (sfd == -1) {
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int enable = 1;
 | 
			
		||||
  setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
 | 
			
		||||
 | 
			
		||||
  struct sockaddr_in addr;
 | 
			
		||||
  addr.sin_family = AF_INET;
 | 
			
		||||
  addr.sin_port = htons(port);
 | 
			
		||||
  addr.sin_addr.s_addr = INADDR_ANY;
 | 
			
		||||
 | 
			
		||||
  if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == 0)
 | 
			
		||||
    return sfd;
 | 
			
		||||
 | 
			
		||||
  close(sfd);
 | 
			
		||||
 | 
			
		||||
  return -1;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,9 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
 | 
			
		||||
void HexDump(const uint8_t* src, size_t len);
 | 
			
		||||
int MakeNonBlocking(int sfd);
 | 
			
		||||
int CreateSocket(uint16_t port);
 | 
			
		||||
@ -0,0 +1,60 @@
 | 
			
		||||
#include "WuPool.h"
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
struct BlockHeader {
 | 
			
		||||
  int32_t index;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct WuPool {
 | 
			
		||||
  int32_t slotSize;
 | 
			
		||||
  int32_t numBytes;
 | 
			
		||||
  int32_t numBlocks;
 | 
			
		||||
  uint8_t* memory;
 | 
			
		||||
  int32_t freeIndicesCount;
 | 
			
		||||
  int32_t* freeIndices;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
WuPool* WuPoolCreate(int32_t blockSize, int32_t numBlocks) {
 | 
			
		||||
  WuPool* pool = (WuPool*)calloc(1, sizeof(WuPool));
 | 
			
		||||
 | 
			
		||||
  pool->slotSize = blockSize + sizeof(BlockHeader);
 | 
			
		||||
  pool->numBytes = pool->slotSize * numBlocks;
 | 
			
		||||
  pool->numBlocks = numBlocks;
 | 
			
		||||
  pool->memory = (uint8_t*)calloc(pool->numBytes, 1);
 | 
			
		||||
  pool->freeIndicesCount = numBlocks;
 | 
			
		||||
  pool->freeIndices = (int32_t*)calloc(numBlocks, sizeof(int32_t));
 | 
			
		||||
 | 
			
		||||
  for (int32_t i = 0; i < numBlocks; i++) {
 | 
			
		||||
    pool->freeIndices[i] = numBlocks - i - 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return pool;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuPoolDestroy(WuPool* pool) {
 | 
			
		||||
  free(pool->memory);
 | 
			
		||||
  free(pool->freeIndices);
 | 
			
		||||
  free(pool);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* WuPoolAcquire(WuPool* pool) {
 | 
			
		||||
  if (pool->freeIndicesCount == 0) return NULL;
 | 
			
		||||
 | 
			
		||||
  const int32_t index = pool->freeIndices[pool->freeIndicesCount - 1];
 | 
			
		||||
  pool->freeIndicesCount--;
 | 
			
		||||
  const int32_t offset = index * pool->slotSize;
 | 
			
		||||
 | 
			
		||||
  uint8_t* block = pool->memory + offset;
 | 
			
		||||
  BlockHeader* header = (BlockHeader*)block;
 | 
			
		||||
  header->index = index;
 | 
			
		||||
 | 
			
		||||
  uint8_t* userMem = block + sizeof(BlockHeader);
 | 
			
		||||
  return userMem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuPoolRelease(WuPool* pool, void* ptr) {
 | 
			
		||||
  uint8_t* mem = (uint8_t*)ptr - sizeof(BlockHeader);
 | 
			
		||||
  BlockHeader* header = (BlockHeader*)mem;
 | 
			
		||||
  pool->freeIndices[pool->freeIndicesCount++] = header->index;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,10 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
struct WuPool;
 | 
			
		||||
 | 
			
		||||
WuPool* WuPoolCreate(int32_t blockSize, int32_t numBlocks);
 | 
			
		||||
void WuPoolDestroy(WuPool* pool);
 | 
			
		||||
void* WuPoolAcquire(WuPool* pool);
 | 
			
		||||
void WuPoolRelease(WuPool* pool, void* ptr);
 | 
			
		||||
@ -0,0 +1,58 @@
 | 
			
		||||
#include "WuQueue.h"
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
static int32_t WuQueueFull(const WuQueue* q) {
 | 
			
		||||
  if (q->length == q->capacity) {
 | 
			
		||||
    return 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WuQueue* WuQueueCreate(int32_t itemSize, int32_t capacity) {
 | 
			
		||||
  WuQueue* q = (WuQueue*)calloc(1, sizeof(WuQueue));
 | 
			
		||||
  WuQueueInit(q, itemSize, capacity);
 | 
			
		||||
  return q;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuQueueInit(WuQueue* q, int32_t itemSize, int32_t capacity) {
 | 
			
		||||
  memset(q, 0, sizeof(WuQueue));
 | 
			
		||||
  q->itemSize = itemSize;
 | 
			
		||||
  q->capacity = capacity;
 | 
			
		||||
  q->items = (uint8_t*)calloc(q->capacity, itemSize);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuQueuePush(WuQueue* q, const void* item) {
 | 
			
		||||
  if (WuQueueFull(q)) {
 | 
			
		||||
    int32_t newCap = q->capacity * 1.5;
 | 
			
		||||
    uint8_t* newItems = (uint8_t*)calloc(newCap, q->itemSize);
 | 
			
		||||
 | 
			
		||||
    int32_t nUpper = q->length - q->start;
 | 
			
		||||
    int32_t nLower = q->length - nUpper;
 | 
			
		||||
    memcpy(newItems, q->items + q->start * q->itemSize, q->itemSize * nUpper);
 | 
			
		||||
    memcpy(newItems + q->itemSize * nUpper, q->items, q->itemSize * nLower);
 | 
			
		||||
 | 
			
		||||
    free(q->items);
 | 
			
		||||
 | 
			
		||||
    q->start = 0;
 | 
			
		||||
    q->capacity = newCap;
 | 
			
		||||
    q->items = newItems;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const int32_t insertIdx =
 | 
			
		||||
      ((q->start + q->length) % q->capacity) * q->itemSize;
 | 
			
		||||
  memcpy(q->items + insertIdx, item, q->itemSize);
 | 
			
		||||
  q->length++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t WuQueuePop(WuQueue* q, void* item) {
 | 
			
		||||
  if (q->length > 0) {
 | 
			
		||||
    memcpy(item, q->items + q->start * q->itemSize, q->itemSize);
 | 
			
		||||
    q->start = (q->start + 1) % q->capacity;
 | 
			
		||||
    q->length--;
 | 
			
		||||
    return 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,16 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
struct WuQueue {
 | 
			
		||||
  int32_t itemSize;
 | 
			
		||||
  int32_t start;
 | 
			
		||||
  int32_t length;
 | 
			
		||||
  int32_t capacity;
 | 
			
		||||
  uint8_t* items;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
WuQueue* WuQueueCreate(int32_t itemSize, int32_t capacity);
 | 
			
		||||
void WuQueueInit(WuQueue* q, int32_t itemSize, int32_t capacity);
 | 
			
		||||
void WuQueuePush(WuQueue* q, const void* item);
 | 
			
		||||
int32_t WuQueuePop(WuQueue* q, void* item);
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include "WuRng.h"
 | 
			
		||||
 | 
			
		||||
static const char kCharacterTable[] =
 | 
			
		||||
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 | 
			
		||||
 | 
			
		||||
static inline uint64_t rotl(const uint64_t x, int k) {
 | 
			
		||||
  return (x << k) | (x >> (64 - k));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint64_t WuGetRngSeed() {
 | 
			
		||||
  uint64_t x = rand();
 | 
			
		||||
  uint64_t z = (x += UINT64_C(0x9E3779B97F4A7C15));
 | 
			
		||||
  z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9);
 | 
			
		||||
  z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB);
 | 
			
		||||
  return z ^ (z >> 31);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void WuRngInit(WuRngState* state, uint64_t seed) {
 | 
			
		||||
  state->s[0] = seed;
 | 
			
		||||
  state->s[1] = seed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static uint64_t WuRngNext(WuRngState* state) {
 | 
			
		||||
  const uint64_t s0 = state->s[0];
 | 
			
		||||
  uint64_t s1 = state->s[1];
 | 
			
		||||
  const uint64_t result = s0 + s1;
 | 
			
		||||
 | 
			
		||||
  s1 ^= s0;
 | 
			
		||||
  state->s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14);
 | 
			
		||||
  state->s[1] = rotl(s1, 36);
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WuRandomString(char* out, size_t length) {
 | 
			
		||||
  WuRngState state;
 | 
			
		||||
  WuRngInit(&state, WuGetRngSeed());
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < length; i++) {
 | 
			
		||||
    out[i] = kCharacterTable[WuRngNext(&state) % (sizeof(kCharacterTable) - 1)];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t WuRandomU64() {
 | 
			
		||||
  WuRngState state;
 | 
			
		||||
  WuRngInit(&state, WuGetRngSeed());
 | 
			
		||||
  return WuRngNext(&state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t WuRandomU32() { return (uint32_t)WuRandomU64(); }
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
// http://xoroshiro.di.unimi.it/xoroshiro128plus.c
 | 
			
		||||
struct WuRngState {
 | 
			
		||||
  uint64_t s[2];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
uint64_t WuRandomU64();
 | 
			
		||||
uint32_t WuRandomU32();
 | 
			
		||||
 | 
			
		||||
void WuRandomString(char* out, size_t length);
 | 
			
		||||
@ -0,0 +1,169 @@
 | 
			
		||||
#include "WuSctp.h"
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include "CRC32.h"
 | 
			
		||||
#include "WuBufferOp.h"
 | 
			
		||||
#include "WuMath.h"
 | 
			
		||||
#include "WuNetwork.h"
 | 
			
		||||
 | 
			
		||||
int32_t ParseSctpPacket(const uint8_t* buf, size_t len, SctpPacket* packet,
 | 
			
		||||
                        SctpChunk* chunks, size_t maxChunks, size_t* nChunk) {
 | 
			
		||||
  if (len < 16) {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int32_t offset = ReadScalarSwapped(buf, &packet->sourcePort);
 | 
			
		||||
  offset += ReadScalarSwapped(buf + offset, &packet->destionationPort);
 | 
			
		||||
  offset += ReadScalarSwapped(buf + offset, &packet->verificationTag);
 | 
			
		||||
  offset += ReadScalarSwapped(buf + offset, &packet->checkSum);
 | 
			
		||||
 | 
			
		||||
  int32_t left = len - offset;
 | 
			
		||||
 | 
			
		||||
  size_t chunkNum = 0;
 | 
			
		||||
  while (left >= 4 && chunkNum < maxChunks) {
 | 
			
		||||
    SctpChunk* chunk = &chunks[chunkNum++];
 | 
			
		||||
 | 
			
		||||
    offset += ReadScalarSwapped(buf + offset, &chunk->type);
 | 
			
		||||
    offset += ReadScalarSwapped(buf + offset, &chunk->flags);
 | 
			
		||||
    offset += ReadScalarSwapped(buf + offset, &chunk->length);
 | 
			
		||||
 | 
			
		||||
    *nChunk += 1;
 | 
			
		||||
 | 
			
		||||
    if (chunk->type == Sctp_Data) {
 | 
			
		||||
      auto* p = &chunk->as.data;
 | 
			
		||||
      size_t chunkOffset = ReadScalarSwapped(buf + offset, &p->tsn);
 | 
			
		||||
      chunkOffset +=
 | 
			
		||||
          ReadScalarSwapped(buf + offset + chunkOffset, &p->streamId);
 | 
			
		||||
      chunkOffset +=
 | 
			
		||||
          ReadScalarSwapped(buf + offset + chunkOffset, &p->streamSeq);
 | 
			
		||||
      chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset, &p->protoId);
 | 
			
		||||
      p->userDataLength = Max(int32_t(chunk->length) - 16, 0);
 | 
			
		||||
      p->userData = buf + offset + chunkOffset;
 | 
			
		||||
    } else if (chunk->type == Sctp_Sack) {
 | 
			
		||||
      auto* sack = &chunk->as.sack;
 | 
			
		||||
      size_t chunkOffset =
 | 
			
		||||
          ReadScalarSwapped(buf + offset, &sack->cumulativeTsnAck);
 | 
			
		||||
      chunkOffset +=
 | 
			
		||||
          ReadScalarSwapped(buf + offset + chunkOffset, &sack->advRecvWindow);
 | 
			
		||||
      chunkOffset +=
 | 
			
		||||
          ReadScalarSwapped(buf + offset + chunkOffset, &sack->numGapAckBlocks);
 | 
			
		||||
      ReadScalarSwapped(buf + offset + chunkOffset, &sack->numDupTsn);
 | 
			
		||||
    } else if (chunk->type == Sctp_Heartbeat) {
 | 
			
		||||
      auto* p = &chunk->as.heartbeat;
 | 
			
		||||
      size_t chunkOffset = 2;  // skip type
 | 
			
		||||
      uint16_t heartbeatLen;
 | 
			
		||||
      chunkOffset +=
 | 
			
		||||
          ReadScalarSwapped(buf + offset + chunkOffset, &heartbeatLen);
 | 
			
		||||
      p->heartbeatInfoLen = int32_t(heartbeatLen) - 4;
 | 
			
		||||
      p->heartbeatInfo = buf + offset + chunkOffset;
 | 
			
		||||
    } else if (chunk->type == Sctp_Init) {
 | 
			
		||||
      size_t chunkOffset =
 | 
			
		||||
          ReadScalarSwapped(buf + offset, &chunk->as.init.initiateTag);
 | 
			
		||||
      chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset,
 | 
			
		||||
                                       &chunk->as.init.windowCredit);
 | 
			
		||||
      chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset,
 | 
			
		||||
                                       &chunk->as.init.numOutboundStreams);
 | 
			
		||||
      chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset,
 | 
			
		||||
                                       &chunk->as.init.numInboundStreams);
 | 
			
		||||
      ReadScalarSwapped(buf + offset + chunkOffset, &chunk->as.init.initialTsn);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int32_t valueLength = chunk->length - 4;
 | 
			
		||||
    int32_t pad = PadSize(valueLength, 4);
 | 
			
		||||
    offset += valueLength + pad;
 | 
			
		||||
    left = len - offset;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t SerializeSctpPacket(const SctpPacket* packet, const SctpChunk* chunks,
 | 
			
		||||
                           size_t numChunks, uint8_t* dst, size_t dstLen) {
 | 
			
		||||
  size_t offset = WriteScalar(dst, htons(packet->sourcePort));
 | 
			
		||||
  offset += WriteScalar(dst + offset, htons(packet->destionationPort));
 | 
			
		||||
  offset += WriteScalar(dst + offset, htonl(packet->verificationTag));
 | 
			
		||||
 | 
			
		||||
  size_t crcOffset = offset;
 | 
			
		||||
  offset += WriteScalar(dst + offset, uint32_t(0));
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < numChunks; i++) {
 | 
			
		||||
    const SctpChunk* chunk = &chunks[i];
 | 
			
		||||
 | 
			
		||||
    offset += WriteScalar(dst + offset, chunk->type);
 | 
			
		||||
    offset += WriteScalar(dst + offset, chunk->flags);
 | 
			
		||||
    offset += WriteScalar(dst + offset, htons(chunk->length));
 | 
			
		||||
 | 
			
		||||
    switch (chunk->type) {
 | 
			
		||||
      case Sctp_Data: {
 | 
			
		||||
        auto* dc = &chunk->as.data;
 | 
			
		||||
        offset += WriteScalar(dst + offset, htonl(dc->tsn));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(dc->streamId));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(dc->streamSeq));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htonl(dc->protoId));
 | 
			
		||||
        memcpy(dst + offset, dc->userData, dc->userDataLength);
 | 
			
		||||
        int32_t pad = PadSize(dc->userDataLength, 4);
 | 
			
		||||
        offset += dc->userDataLength + pad;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case Sctp_InitAck: {
 | 
			
		||||
        offset += WriteScalar(dst + offset, htonl(chunk->as.init.initiateTag));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htonl(chunk->as.init.windowCredit));
 | 
			
		||||
        offset +=
 | 
			
		||||
            WriteScalar(dst + offset, htons(chunk->as.init.numOutboundStreams));
 | 
			
		||||
        offset +=
 | 
			
		||||
            WriteScalar(dst + offset, htons(chunk->as.init.numInboundStreams));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htonl(chunk->as.init.initialTsn));
 | 
			
		||||
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(Sctp_StateCookie));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(8));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htonl(0xB00B1E5));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(Sctp_ForwardTsn));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(4));
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case Sctp_Sack: {
 | 
			
		||||
        auto* sack = &chunk->as.sack;
 | 
			
		||||
        offset += WriteScalar(dst + offset, htonl(sack->cumulativeTsnAck));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htonl(sack->advRecvWindow));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(sack->numGapAckBlocks));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(sack->numDupTsn));
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case Sctp_Heartbeat:
 | 
			
		||||
      case Sctp_HeartbeatAck: {
 | 
			
		||||
        auto* hb = &chunk->as.heartbeat;
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(1));
 | 
			
		||||
        offset += WriteScalar(dst + offset, htons(hb->heartbeatInfoLen + 4));
 | 
			
		||||
        memcpy(dst + offset, hb->heartbeatInfo, hb->heartbeatInfoLen);
 | 
			
		||||
        offset += hb->heartbeatInfoLen + PadSize(hb->heartbeatInfoLen, 4);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case Sctp_Shutdown: {
 | 
			
		||||
        auto* shutdown = &chunk->as.shutdown;
 | 
			
		||||
        offset += WriteScalar(dst + offset, htonl(shutdown->cumulativeTsnAck));
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case SctpChunk_ForwardTsn: {
 | 
			
		||||
        auto* forwardTsn = &chunk->as.forwardTsn;
 | 
			
		||||
        offset +=
 | 
			
		||||
            WriteScalar(dst + offset, htonl(forwardTsn->newCumulativeTsn));
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t crc = SctpCRC32(dst, offset);
 | 
			
		||||
  WriteScalar(dst + crcOffset, htonl(crc));
 | 
			
		||||
 | 
			
		||||
  return offset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t SctpDataChunkLength(int32_t userDataLength) {
 | 
			
		||||
  return 16 + userDataLength;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t SctpChunkLength(int32_t contentLength) { return 4 + contentLength; }
 | 
			
		||||
@ -0,0 +1,100 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
const uint32_t kSctpDefaultBufferSpace = 1 << 18;
 | 
			
		||||
const uint32_t kSctpMinInitAckLength = 32;
 | 
			
		||||
 | 
			
		||||
enum SctpFlag {
 | 
			
		||||
  SctpFlagEndFragment = 0x01,
 | 
			
		||||
  SctpFlagBeginFragment = 0x02,
 | 
			
		||||
  SctpFlagUnreliable = 0x04
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const uint8_t kSctpFlagCompleteUnreliable =
 | 
			
		||||
    SctpFlagEndFragment | SctpFlagBeginFragment | SctpFlagUnreliable;
 | 
			
		||||
 | 
			
		||||
enum SctpChunkType {
 | 
			
		||||
  Sctp_Data = 0x00,
 | 
			
		||||
  Sctp_Init = 0x01,
 | 
			
		||||
  Sctp_InitAck = 0x02,
 | 
			
		||||
  Sctp_Sack = 0x03,
 | 
			
		||||
  Sctp_Heartbeat = 0x04,
 | 
			
		||||
  Sctp_HeartbeatAck = 0x05,
 | 
			
		||||
  Sctp_Abort = 0x06,
 | 
			
		||||
  Sctp_Shutdown = 0x07,
 | 
			
		||||
  Sctp_CookieEcho = 0x0A,
 | 
			
		||||
  Sctp_CookieAck = 0x0B,
 | 
			
		||||
  SctpChunk_ForwardTsn = 0xC0
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum SctpParamType {
 | 
			
		||||
  Sctp_StateCookie = 0x07,
 | 
			
		||||
  Sctp_ForwardTsn = 0xC000,
 | 
			
		||||
  Sctp_Random = 0x8002,
 | 
			
		||||
  Sctp_AuthChunkList = 0x8003,
 | 
			
		||||
  Sctp_HMACAlgo = 0x8004,
 | 
			
		||||
  Sctp_SupportedExts = 0x8008
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct SctpChunk {
 | 
			
		||||
  uint8_t type;
 | 
			
		||||
  uint8_t flags;
 | 
			
		||||
  uint16_t length;
 | 
			
		||||
 | 
			
		||||
  union {
 | 
			
		||||
    struct {
 | 
			
		||||
      uint32_t tsn;
 | 
			
		||||
      uint16_t streamId;
 | 
			
		||||
      uint16_t streamSeq;
 | 
			
		||||
      uint32_t protoId;
 | 
			
		||||
      int32_t userDataLength;
 | 
			
		||||
      const uint8_t* userData;
 | 
			
		||||
    } data;
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
      uint32_t initiateTag;
 | 
			
		||||
      uint32_t windowCredit;
 | 
			
		||||
      uint16_t numOutboundStreams;
 | 
			
		||||
      uint16_t numInboundStreams;
 | 
			
		||||
      uint32_t initialTsn;
 | 
			
		||||
    } init;
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
      int32_t heartbeatInfoLen;
 | 
			
		||||
      const uint8_t* heartbeatInfo;
 | 
			
		||||
    } heartbeat;
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
      uint32_t cumulativeTsnAck;
 | 
			
		||||
      uint32_t advRecvWindow;
 | 
			
		||||
      uint16_t numGapAckBlocks;
 | 
			
		||||
      uint16_t numDupTsn;
 | 
			
		||||
    } sack;
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
      uint32_t cumulativeTsnAck;
 | 
			
		||||
    } shutdown;
 | 
			
		||||
 | 
			
		||||
    struct {
 | 
			
		||||
      uint32_t newCumulativeTsn;
 | 
			
		||||
    } forwardTsn;
 | 
			
		||||
  } as;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct SctpPacket {
 | 
			
		||||
  uint16_t sourcePort;
 | 
			
		||||
  uint16_t destionationPort;
 | 
			
		||||
  uint32_t verificationTag;
 | 
			
		||||
  uint32_t checkSum;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int32_t ParseSctpPacket(const uint8_t* buf, size_t len, SctpPacket* packet,
 | 
			
		||||
                        SctpChunk* chunks, size_t maxChunks, size_t* nChunk);
 | 
			
		||||
 | 
			
		||||
size_t SerializeSctpPacket(const SctpPacket* packet, const SctpChunk* chunks,
 | 
			
		||||
                           size_t numChunks, uint8_t* dst, size_t dstLen);
 | 
			
		||||
 | 
			
		||||
int32_t SctpDataChunkLength(int32_t userDataLength);
 | 
			
		||||
int32_t SctpChunkLength(int32_t contentLength);
 | 
			
		||||
@ -0,0 +1,152 @@
 | 
			
		||||
#include "WuSdp.h"
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include "WuArena.h"
 | 
			
		||||
#include "WuRng.h"
 | 
			
		||||
 | 
			
		||||
enum SdpParseState { kParseIgnore, kParseType, kParseEq, kParseField };
 | 
			
		||||
 | 
			
		||||
static bool ValidField(const IceField* field) { return field->length > 0; }
 | 
			
		||||
 | 
			
		||||
static bool BeginsWith(const char* s, size_t len, const char* prefix,
 | 
			
		||||
                       size_t plen) {
 | 
			
		||||
  if (plen > len) return false;
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < plen; i++) {
 | 
			
		||||
    char a = s[i];
 | 
			
		||||
    char b = prefix[i];
 | 
			
		||||
 | 
			
		||||
    if (a != b) return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool GetIceValue(const char* field, size_t len, const char* name,
 | 
			
		||||
                        IceField* o) {
 | 
			
		||||
  if (BeginsWith(field, len, name, strlen(name))) {
 | 
			
		||||
    for (size_t i = 0; i < len; i++) {
 | 
			
		||||
      char c = field[i];
 | 
			
		||||
      if (c == ':') {
 | 
			
		||||
        size_t valueBegin = i + 1;
 | 
			
		||||
        if (valueBegin < len) {
 | 
			
		||||
          size_t valueLength = len - valueBegin;
 | 
			
		||||
          o->value = field + valueBegin;
 | 
			
		||||
          o->length = int32_t(valueLength);
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void ParseSdpField(const char* field, size_t len, ICESdpFields* fields) {
 | 
			
		||||
  GetIceValue(field, len, "ice-ufrag", &fields->ufrag);
 | 
			
		||||
  GetIceValue(field, len, "ice-pwd", &fields->password);
 | 
			
		||||
  GetIceValue(field, len, "mid", &fields->mid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ParseSdp(const char* sdp, size_t len, ICESdpFields* fields) {
 | 
			
		||||
  memset(fields, 0, sizeof(ICESdpFields));
 | 
			
		||||
 | 
			
		||||
  SdpParseState state = kParseType;
 | 
			
		||||
  size_t begin = 0;
 | 
			
		||||
  size_t length = 0;
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < len; i++) {
 | 
			
		||||
    char c = sdp[i];
 | 
			
		||||
    switch (state) {
 | 
			
		||||
      case kParseType: {
 | 
			
		||||
        if (c == 'a') {
 | 
			
		||||
          state = kParseEq;
 | 
			
		||||
        } else {
 | 
			
		||||
          state = kParseIgnore;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case kParseEq: {
 | 
			
		||||
        if (c == '=') {
 | 
			
		||||
          state = kParseField;
 | 
			
		||||
          begin = i + 1;
 | 
			
		||||
          length = 0;
 | 
			
		||||
          break;
 | 
			
		||||
        } else {
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      case kParseField: {
 | 
			
		||||
        switch (c) {
 | 
			
		||||
          case '\n': {
 | 
			
		||||
            ParseSdpField(sdp + begin, length, fields);
 | 
			
		||||
            length = 0;
 | 
			
		||||
            state = kParseType;
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case '\r': {
 | 
			
		||||
            state = kParseIgnore;
 | 
			
		||||
            ParseSdpField(sdp + begin, length, fields);
 | 
			
		||||
            length = 0;
 | 
			
		||||
            break;
 | 
			
		||||
          };
 | 
			
		||||
          default: { length++; }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      default: {
 | 
			
		||||
        if (c == '\n') state = kParseType;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return ValidField(&fields->ufrag) && ValidField(&fields->password) &&
 | 
			
		||||
         ValidField(&fields->mid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char* GenerateSDP(WuArena* arena, const char* certFingerprint,
 | 
			
		||||
                        const char* serverIp, uint16_t serverPort,
 | 
			
		||||
                        const char* ufrag, int32_t ufragLen, const char* pass,
 | 
			
		||||
                        int32_t passLen, const ICESdpFields* remote,
 | 
			
		||||
                        int* outLength) {
 | 
			
		||||
  const uint32_t port = uint32_t(serverPort);
 | 
			
		||||
  char buf[4096];
 | 
			
		||||
 | 
			
		||||
  int32_t length = snprintf(
 | 
			
		||||
      buf, sizeof(buf),
 | 
			
		||||
      "{\"answer\":{\"sdp\":\"v=0\\r\\n"
 | 
			
		||||
      "o=- %u 1 IN IP4 %u\\r\\n"
 | 
			
		||||
      "s=-\\r\\n"
 | 
			
		||||
      "t=0 0\\r\\n"
 | 
			
		||||
      "m=application %u UDP/DTLS/SCTP webrtc-datachannel\\r\\n"
 | 
			
		||||
      "c=IN IP4 %s\\r\\n"
 | 
			
		||||
      "a=ice-lite\\r\\n"
 | 
			
		||||
      "a=ice-ufrag:%.*s\\r\\n"
 | 
			
		||||
      "a=ice-pwd:%.*s\\r\\n"
 | 
			
		||||
      "a=fingerprint:sha-256 %s\\r\\n"
 | 
			
		||||
      "a=ice-options:trickle\\r\\n"
 | 
			
		||||
      "a=setup:passive\\r\\n"
 | 
			
		||||
      "a=mid:%.*s\\r\\n"
 | 
			
		||||
      "a=sctp-port:%u\\r\\n\","
 | 
			
		||||
      "\"type\":\"answer\"},\"candidate\":{\"sdpMLineIndex\":0,"
 | 
			
		||||
      "\"sdpMid\":\"%.*s\",\"candidate\":\"candidate:1 1 UDP %u %s %u typ "
 | 
			
		||||
      "host\"}}",
 | 
			
		||||
      WuRandomU32(), port, port, serverIp, ufragLen, ufrag, passLen, pass,
 | 
			
		||||
      certFingerprint, remote->mid.length, remote->mid.value, port,
 | 
			
		||||
      remote->mid.length, remote->mid.value, WuRandomU32(), serverIp, port);
 | 
			
		||||
 | 
			
		||||
  if (length <= 0 || length >= int32_t(sizeof(buf))) {
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  char* sdp = (char*)WuArenaAcquire(arena, length);
 | 
			
		||||
 | 
			
		||||
  if (!sdp) {
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  memcpy(sdp, buf, length);
 | 
			
		||||
  *outLength = length;
 | 
			
		||||
 | 
			
		||||
  return sdp;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
struct WuArena;
 | 
			
		||||
 | 
			
		||||
struct IceField {
 | 
			
		||||
  const char* value;
 | 
			
		||||
  int32_t length;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ICESdpFields {
 | 
			
		||||
  IceField ufrag;
 | 
			
		||||
  IceField password;
 | 
			
		||||
  IceField mid;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool ParseSdp(const char* sdp, size_t len, ICESdpFields* fields);
 | 
			
		||||
 | 
			
		||||
const char* GenerateSDP(WuArena* arena, const char* certFingerprint,
 | 
			
		||||
                        const char* serverIp, uint16_t serverPort,
 | 
			
		||||
                        const char* ufrag, int32_t ufragLen, const char* pass,
 | 
			
		||||
                        int32_t passLen, const ICESdpFields* remote,
 | 
			
		||||
                        int* outLength);
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
#include "WuString.h"
 | 
			
		||||
#include <ctype.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
int32_t FindTokenIndex(const char* s, size_t len, char token) {
 | 
			
		||||
  for (size_t i = 0; i < len; i++) {
 | 
			
		||||
    if (s[i] == token) return i;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MemEqual(const void* first, size_t firstLen, const void* second,
 | 
			
		||||
              size_t secondLen) {
 | 
			
		||||
  if (firstLen != secondLen) return false;
 | 
			
		||||
 | 
			
		||||
  return memcmp(first, second, firstLen) == 0;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
int32_t FindTokenIndex(const char* s, size_t len, char token);
 | 
			
		||||
bool MemEqual(const void* first, size_t firstLen, const void* second,
 | 
			
		||||
              size_t secondLen);
 | 
			
		||||
@ -0,0 +1,134 @@
 | 
			
		||||
#include "WuStun.h"
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include "CRC32.h"
 | 
			
		||||
#include "WuCrypto.h"
 | 
			
		||||
 | 
			
		||||
const int32_t kStunHeaderLength = 20;
 | 
			
		||||
const int32_t kStunAlignment = 4;
 | 
			
		||||
 | 
			
		||||
bool ParseStun(const uint8_t* src, int32_t len, StunPacket* packet) {
 | 
			
		||||
  if (len < kStunHeaderLength || src[0] != 0 || src[1] != 1) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  src += ReadScalarSwapped(src, &packet->type);
 | 
			
		||||
 | 
			
		||||
  if (packet->type != Stun_BindingRequest) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  src += ReadScalarSwapped(src, &packet->length);
 | 
			
		||||
 | 
			
		||||
  if (packet->length < 4 || packet->length > len - kStunHeaderLength) {
 | 
			
		||||
    // Need at least 1 attribute
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  src += ReadScalarSwapped(src, &packet->cookie);
 | 
			
		||||
 | 
			
		||||
  for (int32_t i = 0; i < kStunTransactionIdLength; i++) {
 | 
			
		||||
    packet->transactionId[i] = src[i];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  src += kStunTransactionIdLength;
 | 
			
		||||
 | 
			
		||||
  int32_t maxOffset = int32_t(packet->length) - 1;
 | 
			
		||||
  int32_t payloadOffset = 0;
 | 
			
		||||
  while (payloadOffset < maxOffset) {
 | 
			
		||||
    int32_t remain = len - kStunHeaderLength - payloadOffset;
 | 
			
		||||
    if (remain >= 4) {
 | 
			
		||||
      uint16_t payloadType = 0;
 | 
			
		||||
      uint16_t payloadLength = 0;
 | 
			
		||||
 | 
			
		||||
      payloadOffset += ReadScalarSwapped(src + payloadOffset, &payloadType);
 | 
			
		||||
      payloadOffset += ReadScalarSwapped(src + payloadOffset, &payloadLength);
 | 
			
		||||
      remain -= 4;
 | 
			
		||||
 | 
			
		||||
      int32_t paddedLength =
 | 
			
		||||
          payloadLength + PadSize(payloadLength, kStunAlignment);
 | 
			
		||||
 | 
			
		||||
      if (payloadType == StunAttrib_User) {
 | 
			
		||||
        // fragment = min 4 chars
 | 
			
		||||
        // username = fragment:fragment (at least 9 characters)
 | 
			
		||||
        if (paddedLength <= remain && payloadLength >= 9) {
 | 
			
		||||
          const char* uname = (const char*)src + payloadOffset;
 | 
			
		||||
          int32_t colonIndex = FindTokenIndex(uname, payloadLength, ':');
 | 
			
		||||
          if (colonIndex >= 4) {
 | 
			
		||||
            int32_t serverUserLength = colonIndex;
 | 
			
		||||
            int32_t remoteUserLength = payloadLength - colonIndex - 1;
 | 
			
		||||
            if (serverUserLength > kMaxStunIdentifierLength ||
 | 
			
		||||
                remoteUserLength > kMaxStunIdentifierLength) {
 | 
			
		||||
              return false;
 | 
			
		||||
            } else {
 | 
			
		||||
              packet->serverUser.length = serverUserLength;
 | 
			
		||||
              packet->remoteUser.length = remoteUserLength;
 | 
			
		||||
              memcpy(packet->serverUser.identifier, uname, serverUserLength);
 | 
			
		||||
              memcpy(packet->remoteUser.identifier, uname + colonIndex + 1,
 | 
			
		||||
                     remoteUserLength);
 | 
			
		||||
              return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
          } else {
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          // Actual length > reported length
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      payloadOffset += paddedLength;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t SerializeStunPacket(const StunPacket* packet, const uint8_t* password,
 | 
			
		||||
                            int32_t passwordLen, uint8_t* dest, int32_t len) {
 | 
			
		||||
  memset(dest, 0, len);
 | 
			
		||||
  int32_t offset = WriteScalar(dest, htons(Stun_SuccessResponse));
 | 
			
		||||
  // X-MAPPED-ADDRESS (ip4) + MESSAGE-INTEGRITY SHA1
 | 
			
		||||
  int32_t contentLength = 12 + 24;
 | 
			
		||||
  int32_t contentLengthIntegrity = contentLength + 8;
 | 
			
		||||
  const int32_t contentLengthOffset = offset;
 | 
			
		||||
  offset += WriteScalar(dest + offset, htons(contentLength));
 | 
			
		||||
  offset += WriteScalar(dest + offset, htonl(kStunCookie));
 | 
			
		||||
 | 
			
		||||
  for (int32_t i = 0; i < 12; i++) {
 | 
			
		||||
    dest[i + offset] = packet->transactionId[i];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  offset += 12;
 | 
			
		||||
 | 
			
		||||
  // xor mapped address attribute ipv4
 | 
			
		||||
  offset += WriteScalar(dest + offset, htons(StunAttrib_XorMappedAddress));
 | 
			
		||||
  offset += WriteScalar(dest + offset, htons(8));
 | 
			
		||||
  offset += WriteScalar(dest + offset, uint8_t(0));  // reserved
 | 
			
		||||
  offset += WriteScalar(dest + offset, packet->xorMappedAddress.family);
 | 
			
		||||
  offset += WriteScalar(dest + offset, packet->xorMappedAddress.port);
 | 
			
		||||
  offset += WriteScalar(dest + offset, packet->xorMappedAddress.address.ipv4);
 | 
			
		||||
 | 
			
		||||
  WuSHA1Digest digest = WuSHA1(dest, offset, password, passwordLen);
 | 
			
		||||
 | 
			
		||||
  offset += WriteScalar(dest + offset, htons(StunAttrib_MessageIntegrity));
 | 
			
		||||
  offset += WriteScalar(dest + offset, htons(20));
 | 
			
		||||
 | 
			
		||||
  for (int32_t i = 0; i < 20; i++) {
 | 
			
		||||
    dest[i + offset] = digest.bytes[i];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  offset += 20;
 | 
			
		||||
 | 
			
		||||
  WriteScalar(dest + contentLengthOffset, htons(contentLengthIntegrity));
 | 
			
		||||
  uint32_t crc = StunCRC32(dest, offset) ^ 0x5354554e;
 | 
			
		||||
 | 
			
		||||
  offset += WriteScalar(dest + offset, htons(StunAttrib_Fingerprint));
 | 
			
		||||
  offset += WriteScalar(dest + offset, htons(4));
 | 
			
		||||
  offset += WriteScalar(dest + offset, htonl(crc));
 | 
			
		||||
 | 
			
		||||
  return offset;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,57 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "WuBufferOp.h"
 | 
			
		||||
#include "WuString.h"
 | 
			
		||||
 | 
			
		||||
const int32_t kMaxStunIdentifierLength = 128;
 | 
			
		||||
const int32_t kStunTransactionIdLength = 12;
 | 
			
		||||
const uint32_t kStunCookie = 0x2112a442;
 | 
			
		||||
const uint16_t kStunXorMagic = 0x2112;
 | 
			
		||||
 | 
			
		||||
struct StunUserIdentifier {
 | 
			
		||||
  uint8_t identifier[kMaxStunIdentifierLength];
 | 
			
		||||
  int32_t length;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum StunAddressFamily { Stun_IPV4 = 0x01, Stun_IPV6 = 0x02 };
 | 
			
		||||
 | 
			
		||||
enum StunType { Stun_BindingRequest = 0x0001, Stun_SuccessResponse = 0x0101 };
 | 
			
		||||
 | 
			
		||||
enum StunAttributeType {
 | 
			
		||||
  StunAttrib_User = 0x06,
 | 
			
		||||
  StunAttrib_MessageIntegrity = 0x08,
 | 
			
		||||
  StunAttrib_XorMappedAddress = 0x20,
 | 
			
		||||
  StunAttrib_Fingerprint = 0x8028
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct StunAddress {
 | 
			
		||||
  uint8_t family;
 | 
			
		||||
  uint16_t port;
 | 
			
		||||
 | 
			
		||||
  union {
 | 
			
		||||
    uint32_t ipv4;
 | 
			
		||||
    uint8_t ipv6[16];
 | 
			
		||||
  } address;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
inline bool StunUserIdentifierEqual(const StunUserIdentifier* a,
 | 
			
		||||
                                    const StunUserIdentifier* b) {
 | 
			
		||||
  return MemEqual(a->identifier, a->length, b->identifier, b->length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct StunPacket {
 | 
			
		||||
  uint16_t type;
 | 
			
		||||
  uint16_t length;
 | 
			
		||||
  uint32_t cookie;
 | 
			
		||||
  uint8_t transactionId[kStunTransactionIdLength];
 | 
			
		||||
 | 
			
		||||
  StunUserIdentifier remoteUser;
 | 
			
		||||
  StunUserIdentifier serverUser;
 | 
			
		||||
 | 
			
		||||
  StunAddress xorMappedAddress;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool ParseStun(const uint8_t* src, int32_t len, StunPacket* packet);
 | 
			
		||||
 | 
			
		||||
int32_t SerializeStunPacket(const StunPacket* packet, const uint8_t* password,
 | 
			
		||||
                            int32_t passwordLen, uint8_t* dest, int32_t len);
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "webudp",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "webudp.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "configure": "node-gyp configure",
 | 
			
		||||
    "build": "node-gyp build"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "express": "4.16.3",
 | 
			
		||||
    "cors": "2.8.4",
 | 
			
		||||
    "body-parser": "1.18.3"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "node-gyp": "6.1.0",
 | 
			
		||||
    "nan": "2.14.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue