Udp
This commit is contained in:
committed by
Matthew McClaskey
parent
ba902f8194
commit
3b40a92548
771
common/network/webudp/Wu.cpp
Normal file
771
common/network/webudp/Wu.cpp
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user