Use sourcecode for libjuice

This commit is contained in:
dijunkun
2023-07-13 16:58:20 +08:00
parent ef6a04dc97
commit 8cd87a2646
76 changed files with 14877 additions and 179 deletions

310
thirdparty/libjuice/src/addr.c vendored Normal file
View File

@@ -0,0 +1,310 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "addr.h"
#include "log.h"
#include <stdio.h>
#include <string.h>
socklen_t addr_get_len(const struct sockaddr *sa) {
switch (sa->sa_family) {
case AF_INET:
return sizeof(struct sockaddr_in);
case AF_INET6:
return sizeof(struct sockaddr_in6);
default:
JLOG_WARN("Unknown address family %hu", sa->sa_family);
return 0;
}
}
uint16_t addr_get_port(const struct sockaddr *sa) {
switch (sa->sa_family) {
case AF_INET:
return ntohs(((struct sockaddr_in *)sa)->sin_port);
case AF_INET6:
return ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
default:
JLOG_WARN("Unknown address family %hu", sa->sa_family);
return 0;
}
}
int addr_set_port(struct sockaddr *sa, uint16_t port) {
switch (sa->sa_family) {
case AF_INET:
((struct sockaddr_in *)sa)->sin_port = htons(port);
return 0;
case AF_INET6:
((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
return 0;
default:
JLOG_WARN("Unknown address family %hu", sa->sa_family);
return -1;
}
}
bool addr_is_any(const struct sockaddr *sa) {
switch (sa->sa_family) {
case AF_INET: {
const struct sockaddr_in *sin = (const struct sockaddr_in *)sa;
const uint8_t *b = (const uint8_t *)&sin->sin_addr;
for (int i = 0; i < 4; ++i)
if (b[i] != 0)
return false;
return true;
}
case AF_INET6: {
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa;
if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
const uint8_t *b = (const uint8_t *)&sin6->sin6_addr + 12;
for (int i = 0; i < 4; ++i)
if (b[i] != 0)
return false;
} else {
const uint8_t *b = (const uint8_t *)&sin6->sin6_addr;
for (int i = 0; i < 16; ++i)
if (b[i] != 0)
return false;
}
return true;
}
default:
return false;
}
}
bool addr_is_local(const struct sockaddr *sa) {
switch (sa->sa_family) {
case AF_INET: {
const struct sockaddr_in *sin = (const struct sockaddr_in *)sa;
const uint8_t *b = (const uint8_t *)&sin->sin_addr;
if (b[0] == 127) // loopback
return true;
if (b[0] == 169 && b[1] == 254) // link-local
return true;
return false;
}
case AF_INET6: {
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa;
if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) {
return true;
}
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
return true;
}
if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
const uint8_t *b = (const uint8_t *)&sin6->sin6_addr + 12;
if (b[0] == 127) // loopback
return true;
if (b[0] == 169 && b[1] == 254) // link-local
return true;
return false;
}
return false;
}
default:
return false;
}
}
bool addr_unmap_inet6_v4mapped(struct sockaddr *sa, socklen_t *len) {
if (sa->sa_family != AF_INET6)
return false;
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa;
if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
return false;
struct sockaddr_in6 copy = *sin6;
sin6 = &copy;
struct sockaddr_in *sin = (struct sockaddr_in *)sa;
memset(sin, 0, sizeof(*sin));
sin->sin_family = AF_INET;
sin->sin_port = sin6->sin6_port;
memcpy(&sin->sin_addr, ((const uint8_t *)&sin6->sin6_addr) + 12, 4);
*len = sizeof(*sin);
return true;
}
bool addr_map_inet6_v4mapped(struct sockaddr_storage *ss, socklen_t *len) {
if (ss->ss_family != AF_INET)
return false;
const struct sockaddr_in *sin = (const struct sockaddr_in *)ss;
struct sockaddr_in copy = *sin;
sin = &copy;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss;
memset(sin6, 0, sizeof(*sin6));
sin6->sin6_family = AF_INET6;
sin6->sin6_port = sin->sin_port;
uint8_t *b = (uint8_t *)&sin6->sin6_addr;
memset(b, 0, 10);
memset(b + 10, 0xFF, 2);
memcpy(b + 12, (const uint8_t *)&sin->sin_addr, 4);
*len = sizeof(*sin6);
return true;
}
bool addr_is_equal(const struct sockaddr *a, const struct sockaddr *b, bool compare_ports) {
if (a->sa_family != b->sa_family)
return false;
switch (a->sa_family) {
case AF_INET: {
const struct sockaddr_in *ain = (const struct sockaddr_in *)a;
const struct sockaddr_in *bin = (const struct sockaddr_in *)b;
if (memcmp(&ain->sin_addr, &bin->sin_addr, 4) != 0)
return false;
if (compare_ports && ain->sin_port != bin->sin_port)
return false;
break;
}
case AF_INET6: {
const struct sockaddr_in6 *ain6 = (const struct sockaddr_in6 *)a;
const struct sockaddr_in6 *bin6 = (const struct sockaddr_in6 *)b;
if (memcmp(&ain6->sin6_addr, &bin6->sin6_addr, 16) != 0)
return false;
if (compare_ports && ain6->sin6_port != bin6->sin6_port)
return false;
break;
}
default:
return false;
}
return true;
}
int addr_to_string(const struct sockaddr *sa, char *buffer, size_t size) {
socklen_t salen = addr_get_len(sa);
if (salen == 0)
goto error;
char host[ADDR_MAX_NUMERICHOST_LEN];
char service[ADDR_MAX_NUMERICSERV_LEN];
if (getnameinfo(sa, salen, host, ADDR_MAX_NUMERICHOST_LEN, service, ADDR_MAX_NUMERICSERV_LEN,
NI_NUMERICHOST | NI_NUMERICSERV | NI_DGRAM)) {
JLOG_ERROR("getnameinfo failed, errno=%d", sockerrno);
goto error;
}
int len = snprintf(buffer, size, "%s:%s", host, service);
if (len < 0 || (size_t)len >= size)
goto error;
return len;
error:
// Make sure we still write a valid null-terminated string
snprintf(buffer, size, "?");
return -1;
}
// djb2 hash function
#define DJB2_INIT 5381
static void djb2(unsigned long *hash, int i) {
*hash = ((*hash << 5) + *hash) + i; // hash * 33 + i
}
unsigned long addr_hash(const struct sockaddr *sa, bool with_port) {
unsigned long hash = DJB2_INIT;
djb2(&hash, sa->sa_family);
switch (sa->sa_family) {
case AF_INET: {
const struct sockaddr_in *sin = (const struct sockaddr_in *)sa;
const uint8_t *b = (const uint8_t *)&sin->sin_addr;
for (int i = 0; i < 4; ++i)
djb2(&hash, b[i]);
if (with_port) {
djb2(&hash, sin->sin_port >> 8);
djb2(&hash, sin->sin_port & 0xFF);
}
break;
}
case AF_INET6: {
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa;
const uint8_t *b = (const uint8_t *)&sin6->sin6_addr;
for (int i = 0; i < 16; ++i)
djb2(&hash, b[i]);
if (with_port) {
djb2(&hash, sin6->sin6_port >> 8);
djb2(&hash, sin6->sin6_port & 0xFF);
}
break;
}
default:
break;
}
return hash;
}
int addr_resolve(const char *hostname, const char *service, addr_record_t *records, size_t count) {
addr_record_t *end = records + count;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_ADDRCONFIG;
struct addrinfo *ai_list = NULL;
if (getaddrinfo(hostname, service, &hints, &ai_list)) {
JLOG_WARN("Address resolution failed for %s:%s", hostname, service);
return -1;
}
int ret = 0;
for (struct addrinfo *ai = ai_list; ai; ai = ai->ai_next) {
if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
++ret;
if (records != end) {
memcpy(&records->addr, ai->ai_addr, ai->ai_addrlen);
records->len = (socklen_t)ai->ai_addrlen;
++records;
}
}
}
freeaddrinfo(ai_list);
return ret;
}
bool addr_is_numeric_hostname(const char *hostname) {
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV;
struct addrinfo *ai_list = NULL;
if (getaddrinfo(hostname, "9", &hints, &ai_list))
return false;
freeaddrinfo(ai_list);
return true;
}
bool addr_record_is_equal(const addr_record_t *a, const addr_record_t *b, bool compare_ports) {
return addr_is_equal((const struct sockaddr *)&a->addr, (const struct sockaddr *)&b->addr,
compare_ports);
}
int addr_record_to_string(const addr_record_t *record, char *buffer, size_t size) {
return addr_to_string((const struct sockaddr *)&record->addr, buffer, size);
}
unsigned long addr_record_hash(const addr_record_t *record, bool with_port) {
return addr_hash((const struct sockaddr *)&record->addr, with_port);
}

45
thirdparty/libjuice/src/addr.h vendored Normal file
View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_ADDR_H
#define JUICE_ADDR_H
#include "socket.h"
#include <stdbool.h>
#include <stdint.h>
// IPv6 max representation length is 45 plus 4 for potential zone index
#define ADDR_MAX_NUMERICHOST_LEN 56 // 45 + 4 + 1 rounded up
#define ADDR_MAX_NUMERICSERV_LEN 8 // 5 + 1 rounded up
#define ADDR_MAX_STRING_LEN 64
socklen_t addr_get_len(const struct sockaddr *sa);
uint16_t addr_get_port(const struct sockaddr *sa);
int addr_set_port(struct sockaddr *sa, uint16_t port);
bool addr_is_any(const struct sockaddr *sa);
bool addr_is_local(const struct sockaddr *sa);
bool addr_unmap_inet6_v4mapped(struct sockaddr *sa, socklen_t *len);
bool addr_map_inet6_v4mapped(struct sockaddr_storage *ss, socklen_t *len);
bool addr_is_equal(const struct sockaddr *a, const struct sockaddr *b, bool compare_ports);
int addr_to_string(const struct sockaddr *sa, char *buffer, size_t size);
unsigned long addr_hash(const struct sockaddr *sa, bool with_port);
typedef struct addr_record {
struct sockaddr_storage addr;
socklen_t len;
} addr_record_t;
int addr_resolve(const char *hostname, const char *service, addr_record_t *records, size_t count);
bool addr_is_numeric_hostname(const char *hostname);
bool addr_record_is_equal(const addr_record_t *a, const addr_record_t *b, bool compare_ports);
int addr_record_to_string(const addr_record_t *record, char *buffer, size_t size);
unsigned long addr_record_hash(const addr_record_t *record, bool with_port);
#endif // JUICE_ADDR_H

2514
thirdparty/libjuice/src/agent.c vendored Normal file

File diff suppressed because it is too large Load Diff

227
thirdparty/libjuice/src/agent.h vendored Normal file
View File

@@ -0,0 +1,227 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_AGENT_H
#define JUICE_AGENT_H
#include "addr.h"
#include "conn.h"
#include "ice.h"
#include "juice.h"
#include "stun.h"
#include "thread.h"
#include "timestamp.h"
#include "turn.h"
#include <stdbool.h>
#include <stdint.h>
// RFC 8445: Agents MUST NOT use an RTO value smaller than 500 ms.
#define MIN_STUN_RETRANSMISSION_TIMEOUT 500 // msecs
#define MAX_STUN_RETRANSMISSION_COUNT 5 // count (exponential backoff, will give ~30s)
// RFC 8445: ICE agents SHOULD use a default Ta value, 50 ms, but MAY use
// another value based on the characteristics of the associated data.
#define STUN_PACING_TIME 50 // msecs
// RFC 8445: Agents SHOULD use a Tr value of 15 seconds. Agents MAY use a bigger value but MUST NOT
// use a value smaller than 15 seconds.
#define STUN_KEEPALIVE_PERIOD 15000 // msecs
// Consent freshness
// RFC 7675: Consent expires after 30 seconds.
#define CONSENT_TIMEOUT 30000 // msecs
// RFC 7675: To prevent synchronization of consent checks, each interval MUST be randomized from
// between 0.8 and 1.2 times the basic period. Implementations SHOULD set a default interval of 5
// seconds, resulting in a period between checks of 4 to 6 seconds. Implementations MUST NOT set the
// period between checks to less than 4 seconds.
#define MIN_CONSENT_CHECK_PERIOD 4000 // msecs
#define MAX_CONSENT_CHECK_PERIOD 6000 // msecs
// TURN refresh period
#define TURN_LIFETIME 600000 // msecs, 10 min
#define TURN_REFRESH_PERIOD (TURN_LIFETIME - 60000) // msecs, lifetime - 1 min
// ICE trickling timeout
#define ICE_FAIL_TIMEOUT 30000 // msecs
// Max STUN and TURN server entries
#define MAX_SERVER_ENTRIES_COUNT 2 // max STUN server entries
#define MAX_RELAY_ENTRIES_COUNT 2 // max TURN server entries
// Max TURN redirections for ALTERNATE-SERVER mechanism
#define MAX_TURN_REDIRECTIONS 1
// Compute max candidates and entries count
#define MAX_STUN_SERVER_RECORDS_COUNT MAX_SERVER_ENTRIES_COUNT
#define MAX_HOST_CANDIDATES_COUNT ((ICE_MAX_CANDIDATES_COUNT - MAX_STUN_SERVER_RECORDS_COUNT) / 2)
#define MAX_PEER_REFLEXIVE_CANDIDATES_COUNT MAX_HOST_CANDIDATES_COUNT
#define MAX_CANDIDATE_PAIRS_COUNT (ICE_MAX_CANDIDATES_COUNT * (1 + MAX_RELAY_ENTRIES_COUNT))
#define MAX_STUN_ENTRIES_COUNT (MAX_CANDIDATE_PAIRS_COUNT + MAX_STUN_SERVER_RECORDS_COUNT)
#define AGENT_TURN_MAP_SIZE ICE_MAX_CANDIDATES_COUNT
typedef enum agent_mode {
AGENT_MODE_UNKNOWN,
AGENT_MODE_CONTROLLED,
AGENT_MODE_CONTROLLING
} agent_mode_t;
typedef enum agent_stun_entry_type {
AGENT_STUN_ENTRY_TYPE_EMPTY,
AGENT_STUN_ENTRY_TYPE_SERVER,
AGENT_STUN_ENTRY_TYPE_RELAY,
AGENT_STUN_ENTRY_TYPE_CHECK
} agent_stun_entry_type_t;
typedef enum agent_stun_entry_state {
AGENT_STUN_ENTRY_STATE_PENDING,
AGENT_STUN_ENTRY_STATE_CANCELLED,
AGENT_STUN_ENTRY_STATE_FAILED,
AGENT_STUN_ENTRY_STATE_SUCCEEDED,
AGENT_STUN_ENTRY_STATE_SUCCEEDED_KEEPALIVE,
AGENT_STUN_ENTRY_STATE_IDLE
} agent_stun_entry_state_t;
typedef struct agent_turn_state {
turn_map_t map;
stun_credentials_t credentials;
const char *password;
} agent_turn_state_t;
typedef struct agent_stun_entry {
agent_stun_entry_type_t type;
agent_stun_entry_state_t state;
agent_mode_t mode;
ice_candidate_pair_t *pair;
addr_record_t record;
addr_record_t relayed;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
timestamp_t next_transmission;
timediff_t retransmission_timeout;
int retransmissions;
// TURN
agent_turn_state_t *turn;
unsigned int turn_redirections;
struct agent_stun_entry *relay_entry;
} agent_stun_entry_t;
struct juice_agent {
juice_config_t config;
juice_state_t state;
agent_mode_t mode;
ice_description_t local;
ice_description_t remote;
ice_candidate_pair_t candidate_pairs[MAX_CANDIDATE_PAIRS_COUNT];
ice_candidate_pair_t *ordered_pairs[MAX_CANDIDATE_PAIRS_COUNT];
ice_candidate_pair_t *selected_pair;
int candidate_pairs_count;
agent_stun_entry_t entries[MAX_STUN_ENTRIES_COUNT];
int entries_count;
atomic_ptr(agent_stun_entry_t) selected_entry;
uint64_t ice_tiebreaker;
timestamp_t fail_timestamp;
bool gathering_done;
int conn_index;
void *conn_impl;
thread_t resolver_thread;
bool resolver_thread_started;
};
juice_agent_t *agent_create(const juice_config_t *config);
void agent_destroy(juice_agent_t *agent);
int agent_gather_candidates(juice_agent_t *agent);
int agent_resolve_servers(juice_agent_t *agent);
int agent_get_local_description(juice_agent_t *agent, char *buffer, size_t size);
int agent_set_remote_description(juice_agent_t *agent, const char *sdp);
int agent_add_remote_candidate(juice_agent_t *agent, const char *sdp);
int agent_set_remote_gathering_done(juice_agent_t *agent);
int agent_send(juice_agent_t *agent, const char *data, size_t size, int ds);
int agent_direct_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int agent_relay_send(juice_agent_t *agent, agent_stun_entry_t *entry, const addr_record_t *dst,
const char *data, size_t size, int ds);
int agent_channel_send(juice_agent_t *agent, agent_stun_entry_t *entry, const addr_record_t *dst,
const char *data, size_t size, int ds);
juice_state_t agent_get_state(juice_agent_t *agent);
int agent_get_selected_candidate_pair(juice_agent_t *agent, ice_candidate_t *local,
ice_candidate_t *remote);
int agent_conn_recv(juice_agent_t *agent, char *buf, size_t len, const addr_record_t *src);
int agent_conn_update(juice_agent_t *agent, timestamp_t *next_timestamp);
int agent_conn_fail(juice_agent_t *agent);
int agent_input(juice_agent_t *agent, char *buf, size_t len, const addr_record_t *src,
const addr_record_t *relayed); // relayed may be NULL
int agent_bookkeeping(juice_agent_t *agent, timestamp_t *next_timestamp);
void agent_change_state(juice_agent_t *agent, juice_state_t state);
int agent_verify_stun_binding(juice_agent_t *agent, void *buf, size_t size,
const stun_message_t *msg);
int agent_verify_credentials(juice_agent_t *agent, const agent_stun_entry_t *entry, void *buf,
size_t size, stun_message_t *msg);
int agent_dispatch_stun(juice_agent_t *agent, void *buf, size_t size, stun_message_t *msg,
const addr_record_t *src,
const addr_record_t *relayed); // relayed may be NULL
int agent_process_stun_binding(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry, const addr_record_t *src,
const addr_record_t *relayed); // relayed may be NULL
int agent_send_stun_binding(juice_agent_t *agent, agent_stun_entry_t *entry, stun_class_t msg_class,
unsigned int error_code, const uint8_t *transaction_id,
const addr_record_t *mapped);
int agent_process_turn_allocate(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry);
int agent_send_turn_allocate_request(juice_agent_t *agent, const agent_stun_entry_t *entry,
stun_method_t method);
int agent_process_turn_create_permission(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry);
int agent_send_turn_create_permission_request(juice_agent_t *agent, agent_stun_entry_t *entry,
const addr_record_t *record, int ds);
int agent_process_turn_channel_bind(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry);
int agent_send_turn_channel_bind_request(juice_agent_t *agent, agent_stun_entry_t *entry,
const addr_record_t *record, int ds,
uint16_t *out_channel); // out_channel may be NULL
int agent_process_turn_data(juice_agent_t *agent, const stun_message_t *msg,
agent_stun_entry_t *entry);
int agent_process_channel_data(juice_agent_t *agent, agent_stun_entry_t *entry, char *buf,
size_t len);
int agent_add_local_relayed_candidate(juice_agent_t *agent, const addr_record_t *record);
int agent_add_local_reflexive_candidate(juice_agent_t *agent, ice_candidate_type_t type,
const addr_record_t *record);
int agent_add_remote_reflexive_candidate(juice_agent_t *agent, ice_candidate_type_t type,
uint32_t priority, const addr_record_t *record);
int agent_add_candidate_pair(juice_agent_t *agent, ice_candidate_t *local,
ice_candidate_t *remote); // local may be NULL
int agent_add_candidate_pairs_for_remote(juice_agent_t *agent, ice_candidate_t *remote);
int agent_unfreeze_candidate_pair(juice_agent_t *agent, ice_candidate_pair_t *pair);
void agent_arm_keepalive(juice_agent_t *agent, agent_stun_entry_t *entry);
void agent_arm_transmission(juice_agent_t *agent, agent_stun_entry_t *entry, timediff_t delay);
void agent_update_gathering_done(juice_agent_t *agent);
void agent_update_candidate_pairs(juice_agent_t *agent);
void agent_update_ordered_pairs(juice_agent_t *agent);
agent_stun_entry_t *agent_find_entry_from_transaction_id(juice_agent_t *agent,
const uint8_t *transaction_id);
agent_stun_entry_t *
agent_find_entry_from_record(juice_agent_t *agent, const addr_record_t *record,
const addr_record_t *relayed); // relayed may be NULL
void agent_translate_host_candidate_entry(juice_agent_t *agent, agent_stun_entry_t *entry);
#endif

90
thirdparty/libjuice/src/base64.c vendored Normal file
View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2021 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "base64.h"
#include <string.h>
#include <ctype.h>
int juice_base64_encode(const void *data, size_t size, char *out, size_t out_size) {
static const char tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (out_size < 4 * ((size + 2) / 3) + 1)
return -1;
const uint8_t *in = (const uint8_t *)data;
char *w = out;
while (size >= 3) {
*w++ = tab[*in >> 2];
*w++ = tab[((*in & 0x03) << 4) | (*(in + 1) >> 4)];
*w++ = tab[((*(in + 1) & 0x0F) << 2) | (*(in + 2) >> 6)];
*w++ = tab[*(in + 2) & 0x3F];
in += 3;
size -= 3;
}
if (size) {
*w++ = tab[*in >> 2];
if (size == 1) {
*w++ = tab[(*in & 0x03) << 4];
*w++ = '=';
} else { // size == 2
*w++ = tab[((*in & 0x03) << 4) | (*(in + 1) >> 4)];
*w++ = tab[(*(in + 1) & 0x0F) << 2];
}
*w++ = '=';
}
*w = '\0';
return (int)(w - out);
}
int juice_base64_decode(const char *str, void *out, size_t out_size) {
const uint8_t *in = (const uint8_t *)str;
uint8_t *w = (uint8_t *)out;
while (*in && *in != '=') {
uint8_t tab[4] = {0, 0, 0, 0};
size_t size = 0;
while (*in && size < 4) {
uint8_t c = *in++;
if (isspace(c))
continue;
if (c == '=')
break;
if ('A' <= c && c <= 'Z')
tab[size++] = c - 'A';
else if ('a' <= c && c <= 'z')
tab[size++] = c + 26 - 'a';
else if ('0' <= c && c <= '9')
tab[size++] = c + 52 - '0';
else if (c == '+' || c == '-')
tab[size++] = 62;
else if (c == '/' || c == '_')
tab[size++] = 63;
else
return -1; // Invalid character
}
if (size > 0) {
if (out_size < size - 1)
return -1;
out_size -= size - 1;
*w++ = (tab[0] << 2) | (tab[1] >> 4);
if (size > 1) {
*w++ = (tab[1] << 4) | (tab[2] >> 2);
if (size > 2)
*w++ = (tab[2] << 6) | tab[3];
}
}
}
return (int)(w - (uint8_t *)out);
}

24
thirdparty/libjuice/src/base64.h vendored Normal file
View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_BASE64_H
#define JUICE_BASE64_H
#include "juice.h"
#include <stdint.h>
#include <stdlib.h>
// RFC4648-compliant base64 encoder and decoder
JUICE_EXPORT int juice_base64_encode(const void *data, size_t size, char *out, size_t out_size);
JUICE_EXPORT int juice_base64_decode(const char *str, void *out, size_t out_size);
#define BASE64_ENCODE(data, size, out, out_size) juice_base64_encode(data, size, out, out_size)
#define BASE64_DECODE(str, out, out_size) juice_base64_decode(str, out, out_size)
#endif // JUICE_BASE64_H

249
thirdparty/libjuice/src/conn.c vendored Normal file
View File

@@ -0,0 +1,249 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "conn.h"
#include "agent.h"
#include "conn_mux.h"
#include "conn_poll.h"
#include "conn_thread.h"
#include "log.h"
#include <assert.h>
#include <string.h>
#define INITIAL_REGISTRY_SIZE 16
typedef struct conn_mode_entry {
int (*registry_init_func)(conn_registry_t *registry, udp_socket_config_t *config);
void (*registry_cleanup_func)(conn_registry_t *registry);
int (*init_func)(juice_agent_t *agent, struct conn_registry *registry,
udp_socket_config_t *config);
void (*cleanup_func)(juice_agent_t *agent);
void (*lock_func)(juice_agent_t *agent);
void (*unlock_func)(juice_agent_t *agent);
int (*interrupt_func)(juice_agent_t *agent);
int (*send_func)(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int (*get_addrs_func)(juice_agent_t *agent, addr_record_t *records, size_t size);
mutex_t mutex;
conn_registry_t *registry;
} conn_mode_entry_t;
#define MODE_ENTRIES_SIZE 3
static conn_mode_entry_t mode_entries[MODE_ENTRIES_SIZE] = {
{conn_poll_registry_init, conn_poll_registry_cleanup, conn_poll_init, conn_poll_cleanup,
conn_poll_lock, conn_poll_unlock, conn_poll_interrupt, conn_poll_send, conn_poll_get_addrs,
MUTEX_INITIALIZER, NULL},
{conn_mux_registry_init, conn_mux_registry_cleanup, conn_mux_init, conn_mux_cleanup,
conn_mux_lock, conn_mux_unlock, conn_mux_interrupt, conn_mux_send, conn_mux_get_addrs,
MUTEX_INITIALIZER, NULL},
{NULL, NULL, conn_thread_init, conn_thread_cleanup, conn_thread_lock, conn_thread_unlock,
conn_thread_interrupt, conn_thread_send, conn_thread_get_addrs, MUTEX_INITIALIZER, NULL}};
static conn_mode_entry_t *get_mode_entry(juice_agent_t *agent) {
juice_concurrency_mode_t mode = agent->config.concurrency_mode;
assert(mode >= 0 && mode < MODE_ENTRIES_SIZE);
return mode_entries + (int)mode;
}
static conn_registry_t *acquire_registry(conn_mode_entry_t *entry, udp_socket_config_t *config) {
// entry must be locked
conn_registry_t *registry = entry->registry;
if (!registry) {
if (!entry->registry_init_func)
return NULL;
JLOG_DEBUG("Creating connections registry");
registry = calloc(1, sizeof(conn_registry_t));
if (!registry) {
JLOG_FATAL("Memory allocation failed for connections registry");
return NULL;
}
registry->agents = malloc(INITIAL_REGISTRY_SIZE * sizeof(juice_agent_t *));
if (!registry->agents) {
JLOG_FATAL("Memory allocation failed for connections array");
free(registry);
return NULL;
}
registry->agents_size = INITIAL_REGISTRY_SIZE;
registry->agents_count = 0;
memset(registry->agents, 0, INITIAL_REGISTRY_SIZE * sizeof(juice_agent_t *));
mutex_init(&registry->mutex, MUTEX_RECURSIVE);
mutex_lock(&registry->mutex);
if (entry->registry_init_func(registry, config)) {
mutex_unlock(&registry->mutex);
free(registry->agents);
free(registry);
return NULL;
}
entry->registry = registry;
} else {
mutex_lock(&registry->mutex);
}
// registry is locked
return registry;
}
static void release_registry(conn_mode_entry_t *entry) {
// entry must be locked
conn_registry_t *registry = entry->registry;
if (!registry)
return;
// registry must be locked
if (registry->agents_count == 0) {
JLOG_DEBUG("No connection left, destroying connections registry");
mutex_unlock(&registry->mutex);
if (entry->registry_cleanup_func)
entry->registry_cleanup_func(registry);
free(registry->agents);
free(registry);
entry->registry = NULL;
return;
}
JLOG_VERBOSE("%d connection%s left", registry->agents_count,
registry->agents_count >= 2 ? "s" : "");
mutex_unlock(&registry->mutex);
}
int conn_create(juice_agent_t *agent, udp_socket_config_t *config) {
conn_mode_entry_t *entry = get_mode_entry(agent);
mutex_lock(&entry->mutex);
conn_registry_t *registry = acquire_registry(entry, config); // locks the registry if created
mutex_unlock(&entry->mutex);
JLOG_DEBUG("Creating connection");
if (registry) {
int i = 0;
while (i < registry->agents_size && registry->agents[i])
++i;
if (i == registry->agents_size) {
int new_size = registry->agents_size * 2;
JLOG_DEBUG("Reallocating connections array, new_size=%d", new_size);
assert(new_size > 0);
juice_agent_t **new_agents =
realloc(registry->agents, new_size * sizeof(juice_agent_t *));
if (!new_agents) {
JLOG_FATAL("Memory reallocation failed for connections array");
mutex_unlock(&registry->mutex);
return -1;
}
registry->agents = new_agents;
registry->agents_size = new_size;
memset(registry->agents + i, 0, (new_size - i) * sizeof(juice_agent_t *));
}
if (get_mode_entry(agent)->init_func(agent, registry, config)) {
mutex_unlock(&registry->mutex);
return -1;
}
registry->agents[i] = agent;
agent->conn_index = i;
++registry->agents_count;
mutex_unlock(&registry->mutex);
} else {
if (get_mode_entry(agent)->init_func(agent, NULL, config)) {
mutex_unlock(&registry->mutex);
return -1;
}
agent->conn_index = -1;
}
conn_interrupt(agent);
return 0;
}
void conn_destroy(juice_agent_t *agent) {
conn_mode_entry_t *entry = get_mode_entry(agent);
mutex_lock(&entry->mutex);
JLOG_DEBUG("Destroying connection");
conn_registry_t *registry = entry->registry;
if (registry) {
mutex_lock(&registry->mutex);
entry->cleanup_func(agent);
if (agent->conn_index >= 0) {
int i = agent->conn_index;
assert(registry->agents[i] == agent);
registry->agents[i] = NULL;
agent->conn_index = -1;
}
assert(registry->agents_count > 0);
--registry->agents_count;
release_registry(entry); // unlocks the registry
} else {
entry->cleanup_func(agent);
assert(agent->conn_index < 0);
}
mutex_unlock(&entry->mutex);
}
void conn_lock(juice_agent_t *agent) {
if (!agent->conn_impl)
return;
get_mode_entry(agent)->lock_func(agent);
}
void conn_unlock(juice_agent_t *agent) {
if (!agent->conn_impl)
return;
get_mode_entry(agent)->unlock_func(agent);
}
int conn_interrupt(juice_agent_t *agent) {
if (!agent->conn_impl)
return -1;
return get_mode_entry(agent)->interrupt_func(agent);
}
int conn_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds) {
if (!agent->conn_impl)
return -1;
return get_mode_entry(agent)->send_func(agent, dst, data, size, ds);
}
int conn_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size) {
if (!agent->conn_impl)
return -1;
return get_mode_entry(agent)->get_addrs_func(agent, records, size);
}

44
thirdparty/libjuice/src/conn.h vendored Normal file
View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONN_H
#define JUICE_CONN_H
#include "addr.h"
#include "juice.h"
#include "thread.h"
#include "timestamp.h"
#include "udp.h"
#include <stdbool.h>
#include <stdint.h>
typedef struct juice_agent juice_agent_t;
// Generic connection interface for agents
// This interface abstracts sockets and polling to allow for different concurrency modes.
// See include/juice/juice.h for implemented concurrency modes
typedef struct conn_registry {
void *impl;
mutex_t mutex;
juice_agent_t **agents;
int agents_size;
int agents_count;
} conn_registry_t;
int conn_create(juice_agent_t *agent, udp_socket_config_t *config);
void conn_destroy(juice_agent_t *agent);
void conn_lock(juice_agent_t *agent);
void conn_unlock(juice_agent_t *agent);
int conn_interrupt(juice_agent_t *agent);
int conn_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int conn_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size);
#endif

540
thirdparty/libjuice/src/conn_mux.c vendored Normal file
View File

@@ -0,0 +1,540 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "conn_mux.h"
#include "agent.h"
#include "log.h"
#include "socket.h"
#include "stun.h"
#include "thread.h"
#include "udp.h"
#include <assert.h>
#include <string.h>
#define BUFFER_SIZE 4096
#define INITIAL_MAP_SIZE 16
typedef enum map_entry_type {
MAP_ENTRY_TYPE_EMPTY = 0,
MAP_ENTRY_TYPE_DELETED,
MAP_ENTRY_TYPE_FULL
} map_entry_type_t;
typedef struct map_entry {
map_entry_type_t type;
juice_agent_t *agent;
addr_record_t record;
} map_entry_t;
typedef struct registry_impl {
thread_t thread;
socket_t sock;
mutex_t send_mutex;
int send_ds;
map_entry_t *map;
int map_size;
int map_count;
} registry_impl_t;
typedef struct conn_impl {
conn_registry_t *registry;
timestamp_t next_timestamp;
bool finished;
} conn_impl_t;
static bool is_ready(const juice_agent_t *agent) {
if (!agent)
return false;
conn_impl_t *conn_impl = agent->conn_impl;
if (!conn_impl || conn_impl->finished)
return false;
return true;
}
static map_entry_t *find_map_entry(registry_impl_t *impl, const addr_record_t *record,
bool allow_deleted);
static int insert_map_entry(registry_impl_t *impl, const addr_record_t *record,
juice_agent_t *agent);
static int remove_map_entries(registry_impl_t *impl, juice_agent_t *agent);
static int grow_map(registry_impl_t *impl, int new_size);
static map_entry_t *find_map_entry(registry_impl_t *impl, const addr_record_t *record,
bool allow_deleted) {
unsigned long key = addr_record_hash(record, false) % impl->map_size;
unsigned long pos = key;
while (true) {
map_entry_t *entry = impl->map + pos;
if (entry->type == MAP_ENTRY_TYPE_EMPTY ||
addr_record_is_equal(&entry->record, record, true)) // compare ports
break;
if (entry->type == MAP_ENTRY_TYPE_DELETED && allow_deleted)
break;
pos = (pos + 1) % impl->map_size;
if (pos == key)
return NULL;
}
return impl->map + pos;
}
static int insert_map_entry(registry_impl_t *impl, const addr_record_t *record,
juice_agent_t *agent) {
map_entry_t *entry = find_map_entry(impl, record, true); // allow deleted
if (!entry || (entry->type != MAP_ENTRY_TYPE_FULL && impl->map_count * 2 >= impl->map_size)) {
grow_map(impl, impl->map_size * 2);
return insert_map_entry(impl, record, agent);
}
if (entry->type != MAP_ENTRY_TYPE_FULL)
++impl->map_count;
entry->type = MAP_ENTRY_TYPE_FULL;
entry->agent = agent;
entry->record = *record;
JLOG_VERBOSE("Added map entry, count=%d", impl->map_count);
return 0;
}
static int remove_map_entries(registry_impl_t *impl, juice_agent_t *agent) {
int count = 0;
for (int i = 0; i < impl->map_size; ++i) {
map_entry_t *entry = impl->map + i;
if (entry->type == MAP_ENTRY_TYPE_FULL && entry->agent == agent) {
entry->type = MAP_ENTRY_TYPE_DELETED;
entry->agent = NULL;
++count;
}
}
assert(impl->map_count >= count);
impl->map_count -= count;
JLOG_VERBOSE("Removed %d map entries, count=%d", count, impl->map_count);
return 0;
}
static int grow_map(registry_impl_t *impl, int new_size) {
if (new_size <= impl->map_size)
return 0;
JLOG_DEBUG("Growing map, new_size=%d", new_size);
map_entry_t *new_map = calloc(1, new_size * sizeof(map_entry_t));
if (!new_map) {
JLOG_FATAL("Memory allocation failed for map");
return -1;
}
map_entry_t *old_map = impl->map;
int old_size = impl->map_size;
impl->map = new_map;
impl->map_size = new_size;
impl->map_count = 0;
for (int i = 0; i < old_size; ++i) {
map_entry_t *old_entry = old_map + i;
if (old_entry->type == MAP_ENTRY_TYPE_FULL)
insert_map_entry(impl, &old_entry->record, old_entry->agent);
}
free(old_map);
return 0;
}
int conn_mux_prepare(conn_registry_t *registry, struct pollfd *pfd, timestamp_t *next_timestamp);
int conn_mux_process(conn_registry_t *registry, struct pollfd *pfd);
int conn_mux_recv(conn_registry_t *registry, char *buffer, size_t size, addr_record_t *src);
void conn_mux_fail(conn_registry_t *registry);
int conn_mux_run(conn_registry_t *registry);
static thread_return_t THREAD_CALL conn_mux_entry(void *arg) {
conn_registry_t *registry = (conn_registry_t *)arg;
conn_mux_run(registry);
return (thread_return_t)0;
}
int conn_mux_registry_init(conn_registry_t *registry, udp_socket_config_t *config) {
(void)config;
registry_impl_t *registry_impl = calloc(1, sizeof(registry_impl_t));
if (!registry_impl) {
JLOG_FATAL("Memory allocation failed for connections registry impl");
return -1;
}
registry_impl->map = calloc(INITIAL_MAP_SIZE, sizeof(map_entry_t));
if (!registry_impl->map) {
JLOG_FATAL("Memory allocation failed for map");
free(registry_impl);
return -1;
}
registry_impl->map_size = INITIAL_MAP_SIZE;
registry_impl->map_count = 0;
registry_impl->sock = udp_create_socket(config);
if (registry_impl->sock == INVALID_SOCKET) {
JLOG_FATAL("UDP socket creation failed");
free(registry_impl->map);
free(registry_impl);
return -1;
}
mutex_init(&registry_impl->send_mutex, 0);
registry->impl = registry_impl;
JLOG_DEBUG("Starting connections thread");
int ret = thread_init(&registry_impl->thread, conn_mux_entry, registry);
if (ret) {
JLOG_FATAL("Thread creation failed, error=%d", ret);
goto error;
}
return 0;
error:
mutex_destroy(&registry_impl->send_mutex);
closesocket(registry_impl->sock);
free(registry_impl->map);
free(registry_impl);
registry->impl = NULL;
return -1;
}
void conn_mux_registry_cleanup(conn_registry_t *registry) {
registry_impl_t *registry_impl = registry->impl;
JLOG_VERBOSE("Waiting for connections thread");
thread_join(registry_impl->thread, NULL);
mutex_destroy(&registry_impl->send_mutex);
closesocket(registry_impl->sock);
free(registry_impl->map);
free(registry->impl);
registry->impl = NULL;
}
int conn_mux_prepare(conn_registry_t *registry, struct pollfd *pfd, timestamp_t *next_timestamp) {
timestamp_t now = current_timestamp();
*next_timestamp = now + 60000;
mutex_lock(&registry->mutex);
registry_impl_t *registry_impl = registry->impl;
pfd->fd = registry_impl->sock;
pfd->events = POLLIN;
for (int i = 0; i < registry->agents_size; ++i) {
juice_agent_t *agent = registry->agents[i];
if (is_ready(agent)) {
conn_impl_t *conn_impl = agent->conn_impl;
if (*next_timestamp > conn_impl->next_timestamp)
*next_timestamp = conn_impl->next_timestamp;
}
}
int count = registry->agents_count;
mutex_unlock(&registry->mutex);
return count;
}
static juice_agent_t *lookup_agent(conn_registry_t *registry, char *buf, size_t len,
const addr_record_t *src) {
JLOG_VERBOSE("Looking up agent from address");
registry_impl_t *registry_impl = registry->impl;
map_entry_t *entry = find_map_entry(registry_impl, src, false);
juice_agent_t *agent = entry && entry->type == MAP_ENTRY_TYPE_FULL ? entry->agent : NULL;
if (agent) {
JLOG_DEBUG("Found agent from address");
return agent;
}
if (!is_stun_datagram(buf, len)) {
JLOG_INFO("Got non-STUN message from unknown source address");
return NULL;
}
JLOG_VERBOSE("Looking up agent from STUN message content");
stun_message_t msg;
if (stun_read(buf, len, &msg) < 0) {
JLOG_ERROR("STUN message reading failed");
return NULL;
}
if (msg.msg_class == STUN_CLASS_REQUEST && msg.msg_method == STUN_METHOD_BINDING &&
msg.has_integrity) {
// Binding request from peer
char username[STUN_MAX_USERNAME_LEN];
strcpy(username, msg.credentials.username);
char *separator = strchr(username, ':');
if (!separator) {
JLOG_WARN("STUN username invalid, username=\"%s\"", username);
return NULL;
}
*separator = '\0';
const char *local_ufrag = username;
for (int i = 0; i < registry->agents_size; ++i) {
agent = registry->agents[i];
if (is_ready(agent)) {
if (strcmp(local_ufrag, agent->local.ice_ufrag) == 0) {
JLOG_DEBUG("Found agent from ICE ufrag");
insert_map_entry(registry_impl, src, agent);
return agent;
}
}
}
} else {
if (!STUN_IS_RESPONSE(msg.msg_class)) {
JLOG_INFO("Got unexpected STUN message from unknown source address");
return NULL;
}
for (int i = 0; i < registry->agents_size; ++i) {
agent = registry->agents[i];
if (is_ready(agent)) {
if (agent_find_entry_from_transaction_id(agent, msg.transaction_id)) {
JLOG_DEBUG("Found agent from transaction ID");
return agent;
}
}
}
}
return NULL;
}
int conn_mux_process(conn_registry_t *registry, struct pollfd *pfd) {
mutex_lock(&registry->mutex);
if (pfd->revents & POLLNVAL || pfd->revents & POLLERR) {
JLOG_ERROR("Error when polling socket");
conn_mux_fail(registry);
mutex_unlock(&registry->mutex);
return -1;
}
if (pfd->revents & POLLIN) {
char buffer[BUFFER_SIZE];
addr_record_t src;
int ret;
while ((ret = conn_mux_recv(registry, buffer, BUFFER_SIZE, &src)) > 0) {
if (JLOG_DEBUG_ENABLED) {
char src_str[ADDR_MAX_STRING_LEN];
addr_record_to_string(&src, src_str, ADDR_MAX_STRING_LEN);
JLOG_DEBUG("Demultiplexing incoming datagram from %s", src_str);
}
juice_agent_t *agent = lookup_agent(registry, buffer, (size_t)ret, &src);
if (!agent || !is_ready(agent)) {
JLOG_DEBUG("Agent not found for incoming datagram, dropping");
continue;
}
conn_impl_t *conn_impl = agent->conn_impl;
if (agent_conn_recv(agent, buffer, (size_t)ret, &src) != 0) {
JLOG_WARN("Agent receive failed");
conn_impl->finished = true;
continue;
}
conn_impl->next_timestamp = current_timestamp();
}
if (ret < 0) {
conn_mux_fail(registry);
mutex_unlock(&registry->mutex);
return -1;
}
}
for (int i = 0; i < registry->agents_size; ++i) {
juice_agent_t *agent = registry->agents[i];
if (is_ready(agent)) {
conn_impl_t *conn_impl = agent->conn_impl;
if (conn_impl->next_timestamp <= current_timestamp()) {
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
conn_impl->finished = true;
continue;
}
}
}
}
mutex_unlock(&registry->mutex);
return 0;
}
int conn_mux_recv(conn_registry_t *registry, char *buffer, size_t size, addr_record_t *src) {
JLOG_VERBOSE("Receiving datagram");
registry_impl_t *registry_impl = registry->impl;
int len;
while ((len = udp_recvfrom(registry_impl->sock, buffer, size, src)) == 0) {
// Empty datagram (used to interrupt)
}
if (len < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
JLOG_VERBOSE("No more datagrams to receive");
return 0;
}
JLOG_ERROR("recvfrom failed, errno=%d", sockerrno);
return -1;
}
addr_unmap_inet6_v4mapped((struct sockaddr *)&src->addr, &src->len);
return len; // len > 0
}
void conn_mux_fail(conn_registry_t *registry) {
for (int i = 0; i < registry->agents_size; ++i) {
juice_agent_t *agent = registry->agents[i];
if (is_ready(agent)) {
conn_impl_t *conn_impl = agent->conn_impl;
agent_conn_fail(agent);
conn_impl->finished = true;
}
}
}
int conn_mux_run(conn_registry_t *registry) {
struct pollfd pfd[1];
timestamp_t next_timestamp;
while (conn_mux_prepare(registry, pfd, &next_timestamp) > 0) {
timediff_t timediff = next_timestamp - current_timestamp();
if (timediff < 0)
timediff = 0;
JLOG_VERBOSE("Entering poll for %d ms", (int)timediff);
int ret = poll(pfd, 1, (int)timediff);
JLOG_VERBOSE("Leaving poll");
if (ret < 0) {
if (sockerrno == SEINTR || sockerrno == SEAGAIN) {
JLOG_VERBOSE("poll interrupted");
continue;
} else {
JLOG_FATAL("poll failed, errno=%d", sockerrno);
break;
}
}
if (conn_mux_process(registry, pfd) < 0)
break;
}
JLOG_DEBUG("Leaving connections thread");
return 0;
}
int conn_mux_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config) {
(void)config; // ignored, only the config from the first connection is used
conn_impl_t *conn_impl = calloc(1, sizeof(conn_impl_t));
if (!conn_impl) {
JLOG_FATAL("Memory allocation failed for connection impl");
return -1;
}
conn_impl->registry = registry;
agent->conn_impl = conn_impl;
return 0;
}
void conn_mux_cleanup(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_lock(&registry->mutex);
registry_impl_t *registry_impl = registry->impl;
remove_map_entries(registry_impl, agent);
mutex_unlock(&registry->mutex);
conn_mux_interrupt(agent);
free(agent->conn_impl);
agent->conn_impl = NULL;
}
void conn_mux_lock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_lock(&registry->mutex);
}
void conn_mux_unlock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_unlock(&registry->mutex);
}
int conn_mux_interrupt(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_lock(&registry->mutex);
conn_impl->next_timestamp = current_timestamp();
mutex_unlock(&registry->mutex);
JLOG_VERBOSE("Interrupting connections thread");
registry_impl_t *registry_impl = registry->impl;
mutex_lock(&registry_impl->send_mutex);
if (udp_sendto_self(registry_impl->sock, NULL, 0) < 0) {
if (sockerrno != SEAGAIN && sockerrno != SEWOULDBLOCK) {
JLOG_WARN("Failed to interrupt poll by triggering socket, errno=%d", sockerrno);
}
mutex_unlock(&registry_impl->send_mutex);
return -1;
}
mutex_unlock(&registry_impl->send_mutex);
return 0;
}
int conn_mux_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds) {
conn_impl_t *conn_impl = agent->conn_impl;
registry_impl_t *registry_impl = conn_impl->registry->impl;
mutex_lock(&registry_impl->send_mutex);
if (registry_impl->send_ds >= 0 && registry_impl->send_ds != ds) {
JLOG_VERBOSE("Setting Differentiated Services field to 0x%X", ds);
if (udp_set_diffserv(registry_impl->sock, ds) == 0)
registry_impl->send_ds = ds;
else
registry_impl->send_ds = -1; // disable for next time
}
JLOG_VERBOSE("Sending datagram, size=%d", size);
int ret = udp_sendto(registry_impl->sock, data, size, dst);
if (ret < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK)
JLOG_INFO("Send failed, buffer is full");
else if (sockerrno == SEMSGSIZE)
JLOG_WARN("Send failed, datagram is too large");
else
JLOG_WARN("Send failed, errno=%d", sockerrno);
}
mutex_unlock(&registry_impl->send_mutex);
return ret;
}
int conn_mux_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size) {
conn_impl_t *conn_impl = agent->conn_impl;
registry_impl_t *registry_impl = conn_impl->registry->impl;
return udp_get_addrs(registry_impl->sock, records, size);
}

32
thirdparty/libjuice/src/conn_mux.h vendored Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONN_MUX_H
#define JUICE_CONN_MUX_H
#include "addr.h"
#include "conn.h"
#include "thread.h"
#include "timestamp.h"
#include <stdbool.h>
#include <stdint.h>
int conn_mux_registry_init(conn_registry_t *registry, udp_socket_config_t *config);
void conn_mux_registry_cleanup(conn_registry_t *registry);
int conn_mux_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config);
void conn_mux_cleanup(juice_agent_t *agent);
void conn_mux_lock(juice_agent_t *agent);
void conn_mux_unlock(juice_agent_t *agent);
int conn_mux_interrupt(juice_agent_t *agent);
int conn_mux_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int conn_mux_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size);
#endif

433
thirdparty/libjuice/src/conn_poll.c vendored Normal file
View File

@@ -0,0 +1,433 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "conn_poll.h"
#include "agent.h"
#include "log.h"
#include "socket.h"
#include "thread.h"
#include "udp.h"
#include <assert.h>
#include <string.h>
#define BUFFER_SIZE 4096
typedef struct registry_impl {
thread_t thread;
#ifdef _WIN32
socket_t interrupt_sock;
#else
int interrupt_pipe_out;
int interrupt_pipe_in;
#endif
} registry_impl_t;
typedef enum conn_state { CONN_STATE_NEW = 0, CONN_STATE_READY, CONN_STATE_FINISHED } conn_state_t;
typedef struct conn_impl {
conn_registry_t *registry;
conn_state_t state;
socket_t sock;
mutex_t send_mutex;
int send_ds;
timestamp_t next_timestamp;
} conn_impl_t;
typedef struct pfds_record {
struct pollfd *pfds;
nfds_t size;
} pfds_record_t;
int conn_poll_prepare(conn_registry_t *registry, pfds_record_t *pfds, timestamp_t *next_timestamp);
int conn_poll_process(conn_registry_t *registry, pfds_record_t *pfds);
int conn_poll_recv(socket_t sock, char *buffer, size_t size, addr_record_t *src);
int conn_poll_run(conn_registry_t *registry);
static thread_return_t THREAD_CALL conn_thread_entry(void *arg) {
conn_registry_t *registry = (conn_registry_t *)arg;
conn_poll_run(registry);
return (thread_return_t)0;
}
int conn_poll_registry_init(conn_registry_t *registry, udp_socket_config_t *config) {
(void)config;
registry_impl_t *registry_impl = calloc(1, sizeof(registry_impl_t));
if (!registry_impl) {
JLOG_FATAL("Memory allocation failed for connections registry impl");
return -1;
}
#ifdef _WIN32
udp_socket_config_t interrupt_config;
memset(&interrupt_config, 0, sizeof(interrupt_config));
interrupt_config.bind_address = "localhost";
registry_impl->interrupt_sock = udp_create_socket(&interrupt_config);
if (registry_impl->interrupt_sock == INVALID_SOCKET) {
JLOG_FATAL("Dummy socket creation failed");
free(registry_impl);
return -1;
}
#else
int pipefds[2];
if (pipe(pipefds)) {
JLOG_FATAL("Pipe creation failed");
free(registry_impl);
return -1;
}
fcntl(pipefds[0], F_SETFL, O_NONBLOCK);
fcntl(pipefds[1], F_SETFL, O_NONBLOCK);
registry_impl->interrupt_pipe_out = pipefds[1]; // read
registry_impl->interrupt_pipe_in = pipefds[0]; // write
#endif
registry->impl = registry_impl;
JLOG_DEBUG("Starting connections thread");
int ret = thread_init(&registry_impl->thread, conn_thread_entry, registry);
if (ret) {
JLOG_FATAL("Thread creation failed, error=%d", ret);
goto error;
}
return 0;
error:
#ifndef _WIN32
close(registry_impl->interrupt_pipe_out);
close(registry_impl->interrupt_pipe_in);
#endif
free(registry_impl);
registry->impl = NULL;
return -1;
}
void conn_poll_registry_cleanup(conn_registry_t *registry) {
registry_impl_t *registry_impl = registry->impl;
JLOG_VERBOSE("Waiting for connections thread");
thread_join(registry_impl->thread, NULL);
#ifdef _WIN32
closesocket(registry_impl->interrupt_sock);
#else
close(registry_impl->interrupt_pipe_out);
close(registry_impl->interrupt_pipe_in);
#endif
free(registry->impl);
registry->impl = NULL;
}
int conn_poll_prepare(conn_registry_t *registry, pfds_record_t *pfds, timestamp_t *next_timestamp) {
timestamp_t now = current_timestamp();
*next_timestamp = now + 60000;
mutex_lock(&registry->mutex);
nfds_t size = (nfds_t)(1 + registry->agents_size);
if (pfds->size != size) {
struct pollfd *new_pfds = realloc(pfds->pfds, sizeof(struct pollfd) * size);
if (!new_pfds) {
JLOG_FATAL("Memory allocation for poll file descriptors failed");
goto error;
}
pfds->pfds = new_pfds;
pfds->size = size;
}
registry_impl_t *registry_impl = registry->impl;
struct pollfd *interrupt_pfd = pfds->pfds;
assert(interrupt_pfd);
#ifdef _WIN32
interrupt_pfd->fd = registry_impl->interrupt_sock;
#else
interrupt_pfd->fd = registry_impl->interrupt_pipe_in;
#endif
interrupt_pfd->events = POLLIN;
for (nfds_t i = 1; i < pfds->size; ++i) {
struct pollfd *pfd = pfds->pfds + i;
juice_agent_t *agent = registry->agents[i - 1];
if (!agent) {
pfd->fd = INVALID_SOCKET;
pfd->events = 0;
continue;
}
conn_impl_t *conn_impl = agent->conn_impl;
if (!conn_impl ||
(conn_impl->state != CONN_STATE_NEW && conn_impl->state != CONN_STATE_READY)) {
pfd->fd = INVALID_SOCKET;
pfd->events = 0;
continue;
}
if (conn_impl->state == CONN_STATE_NEW)
conn_impl->state = CONN_STATE_READY;
if (*next_timestamp > conn_impl->next_timestamp)
*next_timestamp = conn_impl->next_timestamp;
pfd->fd = conn_impl->sock;
pfd->events = POLLIN;
}
int count = registry->agents_count;
mutex_unlock(&registry->mutex);
return count;
error:
mutex_unlock(&registry->mutex);
return -1;
}
int conn_poll_recv(socket_t sock, char *buffer, size_t size, addr_record_t *src) {
JLOG_VERBOSE("Receiving datagram");
int len;
while ((len = udp_recvfrom(sock, buffer, size, src)) == 0) {
// Empty datagram, ignore
}
if (len < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
JLOG_VERBOSE("No more datagrams to receive");
return 0;
}
JLOG_ERROR("recvfrom failed, errno=%d", sockerrno);
return -1;
}
addr_unmap_inet6_v4mapped((struct sockaddr *)&src->addr, &src->len);
return len; // len > 0
}
int conn_poll_process(conn_registry_t *registry, pfds_record_t *pfds) {
struct pollfd *interrupt_pfd = pfds->pfds;
if (interrupt_pfd->revents & POLLIN) {
#ifdef _WIN32
char dummy;
addr_record_t src;
while (udp_recvfrom(interrupt_pfd->fd, &dummy, 1, &src) >= 0) {
// Ignore
}
#else
char dummy;
while (read(interrupt_pfd->fd, &dummy, 1) > 0) {
// Ignore
}
#endif
}
for (nfds_t i = 1; i < pfds->size; ++i) {
struct pollfd *pfd = pfds->pfds + i;
if (pfd->fd == INVALID_SOCKET)
continue;
mutex_lock(&registry->mutex);
juice_agent_t *agent = registry->agents[i - 1];
if (!agent)
goto end;
conn_impl_t *conn_impl = agent->conn_impl;
if (!conn_impl || conn_impl->sock != pfd->fd || conn_impl->state != CONN_STATE_READY)
goto end;
if (pfd->revents & POLLNVAL || pfd->revents & POLLERR) {
JLOG_WARN("Error when polling socket");
agent_conn_fail(agent);
conn_impl->state = CONN_STATE_FINISHED;
goto end;
}
if (pfd->revents & POLLIN) {
char buffer[BUFFER_SIZE];
addr_record_t src;
int ret = 0;
int left = 1000; // limit for fairness between sockets
while (left-- &&
(ret = conn_poll_recv(conn_impl->sock, buffer, BUFFER_SIZE, &src)) > 0) {
if (agent_conn_recv(agent, buffer, (size_t)ret, &src) != 0) {
JLOG_WARN("Agent receive failed");
conn_impl->state = CONN_STATE_FINISHED;
break;
}
}
if (conn_impl->state == CONN_STATE_FINISHED)
goto end;
if (ret < 0) {
agent_conn_fail(agent);
conn_impl->state = CONN_STATE_FINISHED;
goto end;
}
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
conn_impl->state = CONN_STATE_FINISHED;
goto end;
}
} else if (conn_impl->next_timestamp <= current_timestamp()) {
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
conn_impl->state = CONN_STATE_FINISHED;
goto end;
}
}
end:
mutex_unlock(&registry->mutex);
}
return 0;
}
int conn_poll_run(conn_registry_t *registry) {
pfds_record_t pfds;
pfds.pfds = NULL;
pfds.size = 0;
timestamp_t next_timestamp = 0;
int count;
while ((count = conn_poll_prepare(registry, &pfds, &next_timestamp)) > 0) {
timediff_t timediff = next_timestamp - current_timestamp();
if (timediff < 0)
timediff = 0;
JLOG_VERBOSE("Entering poll on %d sockets for %d ms", count, (int)timediff);
int ret = poll(pfds.pfds, pfds.size, (int)timediff);
JLOG_VERBOSE("Leaving poll");
if (ret < 0) {
#ifdef _WIN32
if (ret == WSAENOTSOCK)
continue; // prepare again as the fd has been removed
#endif
if (sockerrno == SEINTR || sockerrno == SEAGAIN) {
JLOG_VERBOSE("poll interrupted");
continue;
} else {
JLOG_FATAL("poll failed, errno=%d", sockerrno);
break;
}
}
if (conn_poll_process(registry, &pfds) < 0)
break;
}
JLOG_DEBUG("Leaving connections thread");
free(pfds.pfds);
return 0;
}
int conn_poll_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config) {
conn_impl_t *conn_impl = calloc(1, sizeof(conn_impl_t));
if (!conn_impl) {
JLOG_FATAL("Memory allocation failed for connection impl");
return -1;
}
conn_impl->sock = udp_create_socket(config);
if (conn_impl->sock == INVALID_SOCKET) {
JLOG_ERROR("UDP socket creation failed");
free(conn_impl);
return -1;
}
mutex_init(&conn_impl->send_mutex, 0);
conn_impl->registry = registry;
agent->conn_impl = conn_impl;
return 0;
}
void conn_poll_cleanup(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_poll_interrupt(agent);
mutex_destroy(&conn_impl->send_mutex);
closesocket(conn_impl->sock);
free(agent->conn_impl);
agent->conn_impl = NULL;
}
void conn_poll_lock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_lock(&registry->mutex);
}
void conn_poll_unlock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
mutex_unlock(&registry->mutex);
}
int conn_poll_interrupt(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
conn_registry_t *registry = conn_impl->registry;
registry_impl_t *registry_impl = registry->impl;
mutex_lock(&registry->mutex);
conn_impl->next_timestamp = current_timestamp();
mutex_unlock(&registry->mutex);
JLOG_VERBOSE("Interrupting connections thread");
#ifdef _WIN32
if (udp_sendto_self(registry_impl->interrupt_sock, NULL, 0) < 0) {
if (sockerrno != SEAGAIN && sockerrno != SEWOULDBLOCK) {
JLOG_WARN("Failed to interrupt poll by triggering socket, errno=%d", sockerrno);
}
return -1;
}
#else
char dummy = 0;
if (write(registry_impl->interrupt_pipe_out, &dummy, 1) < 0 && errno != EAGAIN &&
errno != EWOULDBLOCK) {
JLOG_WARN("Failed to interrupt poll by writing to pipe, errno=%d", errno);
}
#endif
return 0;
}
int conn_poll_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->send_mutex);
if (conn_impl->send_ds >= 0 && conn_impl->send_ds != ds) {
JLOG_VERBOSE("Setting Differentiated Services field to 0x%X", ds);
if (udp_set_diffserv(conn_impl->sock, ds) == 0)
conn_impl->send_ds = ds;
else
conn_impl->send_ds = -1; // disable for next time
}
JLOG_VERBOSE("Sending datagram, size=%d", size);
int ret = udp_sendto(conn_impl->sock, data, size, dst);
if (ret < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK)
JLOG_INFO("Send failed, buffer is full");
else if (sockerrno == SEMSGSIZE)
JLOG_WARN("Send failed, datagram is too large");
else
JLOG_WARN("Send failed, errno=%d", sockerrno);
}
mutex_unlock(&conn_impl->send_mutex);
return ret;
}
int conn_poll_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size) {
conn_impl_t *conn_impl = agent->conn_impl;
return udp_get_addrs(conn_impl->sock, records, size);
}

32
thirdparty/libjuice/src/conn_poll.h vendored Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONN_POLL_H
#define JUICE_CONN_POLL_H
#include "addr.h"
#include "conn.h"
#include "thread.h"
#include "timestamp.h"
#include <stdbool.h>
#include <stdint.h>
int conn_poll_registry_init(conn_registry_t *registry, udp_socket_config_t *config);
void conn_poll_registry_cleanup(conn_registry_t *registry);
int conn_poll_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config);
void conn_poll_cleanup(juice_agent_t *agent);
void conn_poll_lock(juice_agent_t *agent);
void conn_poll_unlock(juice_agent_t *agent);
int conn_poll_interrupt(juice_agent_t *agent);
int conn_poll_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int conn_poll_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size);
#endif

278
thirdparty/libjuice/src/conn_thread.c vendored Normal file
View File

@@ -0,0 +1,278 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "conn_thread.h"
#include "agent.h"
#include "log.h"
#include "socket.h"
#include "thread.h"
#include "udp.h"
#include <assert.h>
#include <string.h>
#define BUFFER_SIZE 4096
typedef struct conn_impl {
thread_t thread;
socket_t sock;
mutex_t mutex;
mutex_t send_mutex;
int send_ds;
timestamp_t next_timestamp;
bool stopped;
} conn_impl_t;
int conn_thread_run(juice_agent_t *agent);
int conn_thread_prepare(juice_agent_t *agent, struct pollfd *pfd, timestamp_t *next_timestamp);
int conn_thread_process(juice_agent_t *agent, struct pollfd *pfd);
int conn_thread_recv(socket_t sock, char *buffer, size_t size, addr_record_t *src);
static thread_return_t THREAD_CALL conn_thread_entry(void *arg) {
juice_agent_t *agent = (juice_agent_t *)arg;
conn_thread_run(agent);
return (thread_return_t)0;
}
int conn_thread_prepare(juice_agent_t *agent, struct pollfd *pfd, timestamp_t *next_timestamp) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
if (conn_impl->stopped) {
mutex_unlock(&conn_impl->mutex);
return 0;
}
pfd->fd = conn_impl->sock;
pfd->events = POLLIN;
*next_timestamp = conn_impl->next_timestamp;
mutex_unlock(&conn_impl->mutex);
return 1;
}
int conn_thread_process(juice_agent_t *agent, struct pollfd *pfd) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
if (conn_impl->stopped) {
mutex_unlock(&conn_impl->mutex);
return -1;
}
if (pfd->revents & POLLNVAL || pfd->revents & POLLERR) {
JLOG_ERROR("Error when polling socket");
agent_conn_fail(agent);
mutex_unlock(&conn_impl->mutex);
return -1;
}
if (pfd->revents & POLLIN) {
char buffer[BUFFER_SIZE];
addr_record_t src;
int ret;
while ((ret = conn_thread_recv(conn_impl->sock, buffer, BUFFER_SIZE, &src)) > 0) {
if (agent_conn_recv(agent, buffer, (size_t)ret, &src) != 0) {
JLOG_WARN("Agent receive failed");
mutex_unlock(&conn_impl->mutex);
return -1;
}
}
if (ret < 0) {
agent_conn_fail(agent);
mutex_unlock(&conn_impl->mutex);
return -1;
}
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
mutex_unlock(&conn_impl->mutex);
return -1;
}
} else if (conn_impl->next_timestamp <= current_timestamp()) {
if (agent_conn_update(agent, &conn_impl->next_timestamp) != 0) {
JLOG_WARN("Agent update failed");
mutex_unlock(&conn_impl->mutex);
return -1;
}
}
mutex_unlock(&conn_impl->mutex);
return 0;
}
int conn_thread_recv(socket_t sock, char *buffer, size_t size, addr_record_t *src) {
JLOG_VERBOSE("Receiving datagram");
int len;
while ((len = udp_recvfrom(sock, buffer, size, src)) == 0) {
// Empty datagram (used to interrupt)
}
if (len < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK) {
JLOG_VERBOSE("No more datagrams to receive");
return 0;
}
JLOG_ERROR("recvfrom failed, errno=%d", sockerrno);
return -1;
}
addr_unmap_inet6_v4mapped((struct sockaddr *)&src->addr, &src->len);
return len; // len > 0
}
int conn_thread_run(juice_agent_t *agent) {
struct pollfd pfd[1];
timestamp_t next_timestamp;
while (conn_thread_prepare(agent, pfd, &next_timestamp) > 0) {
timediff_t timediff = next_timestamp - current_timestamp();
if (timediff < 0)
timediff = 0;
JLOG_VERBOSE("Entering poll for %d ms", (int)timediff);
int ret = poll(pfd, 1, (int)timediff);
JLOG_VERBOSE("Leaving poll");
if (ret < 0) {
if (sockerrno == SEINTR || sockerrno == SEAGAIN) {
JLOG_VERBOSE("poll interrupted");
continue;
} else {
JLOG_FATAL("poll failed, errno=%d", sockerrno);
break;
}
}
if (conn_thread_process(agent, pfd) < 0)
break;
}
JLOG_DEBUG("Leaving connection thread");
return 0;
}
int conn_thread_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config) {
(void)registry;
conn_impl_t *conn_impl = calloc(1, sizeof(conn_impl_t));
if (!conn_impl) {
JLOG_FATAL("Memory allocation failed for connection impl");
return -1;
}
conn_impl->sock = udp_create_socket(config);
if (conn_impl->sock == INVALID_SOCKET) {
JLOG_ERROR("UDP socket creation failed");
free(conn_impl);
return -1;
}
mutex_init(&conn_impl->mutex, 0);
mutex_init(&conn_impl->send_mutex, 0);
agent->conn_impl = conn_impl;
JLOG_DEBUG("Starting connection thread");
int ret = thread_init(&conn_impl->thread, conn_thread_entry, agent);
if (ret) {
JLOG_FATAL("Thread creation failed, error=%d", ret);
free(conn_impl);
agent->conn_impl = NULL;
return -1;
}
return 0;
}
void conn_thread_cleanup(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
conn_impl->stopped = true;
mutex_unlock(&conn_impl->mutex);
conn_thread_interrupt(agent);
JLOG_VERBOSE("Waiting for connection thread");
thread_join(conn_impl->thread, NULL);
closesocket(conn_impl->sock);
mutex_destroy(&conn_impl->mutex);
mutex_destroy(&conn_impl->send_mutex);
free(agent->conn_impl);
agent->conn_impl = NULL;
}
void conn_thread_lock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
}
void conn_thread_unlock(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_unlock(&conn_impl->mutex);
}
int conn_thread_interrupt(juice_agent_t *agent) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->mutex);
conn_impl->next_timestamp = current_timestamp();
mutex_unlock(&conn_impl->mutex);
JLOG_VERBOSE("Interrupting connection thread");
mutex_lock(&conn_impl->send_mutex);
if (udp_sendto_self(conn_impl->sock, NULL, 0) < 0) {
if (sockerrno != SEAGAIN && sockerrno != SEWOULDBLOCK) {
JLOG_WARN("Failed to interrupt poll by triggering socket, errno=%d", sockerrno);
}
mutex_unlock(&conn_impl->send_mutex);
return -1;
}
mutex_unlock(&conn_impl->send_mutex);
return 0;
}
int conn_thread_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds) {
conn_impl_t *conn_impl = agent->conn_impl;
mutex_lock(&conn_impl->send_mutex);
if (conn_impl->send_ds >= 0 && conn_impl->send_ds != ds) {
JLOG_VERBOSE("Setting Differentiated Services field to 0x%X", ds);
if (udp_set_diffserv(conn_impl->sock, ds) == 0)
conn_impl->send_ds = ds;
else
conn_impl->send_ds = -1; // disable for next time
}
JLOG_VERBOSE("Sending datagram, size=%d", size);
int ret = udp_sendto(conn_impl->sock, data, size, dst);
if (ret < 0) {
if (sockerrno == SEAGAIN || sockerrno == SEWOULDBLOCK)
JLOG_INFO("Send failed, buffer is full");
else if (sockerrno == SEMSGSIZE)
JLOG_WARN("Send failed, datagram is too large");
else
JLOG_WARN("Send failed, errno=%d", sockerrno);
}
mutex_unlock(&conn_impl->send_mutex);
return ret;
}
int conn_thread_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size) {
conn_impl_t *conn_impl = agent->conn_impl;
return udp_get_addrs(conn_impl->sock, records, size);
}

32
thirdparty/libjuice/src/conn_thread.h vendored Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2022 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONN_THREAD_H
#define JUICE_CONN_THREAD_H
#include "addr.h"
#include "conn.h"
#include "thread.h"
#include "timestamp.h"
#include <stdbool.h>
#include <stdint.h>
int conn_thread_registry_init(conn_registry_t *registry, udp_socket_config_t *config);
void conn_thread_registry_cleanup(conn_registry_t *registry);
int conn_thread_init(juice_agent_t *agent, conn_registry_t *registry, udp_socket_config_t *config);
void conn_thread_cleanup(juice_agent_t *agent);
void conn_thread_lock(juice_agent_t *agent);
void conn_thread_unlock(juice_agent_t *agent);
int conn_thread_interrupt(juice_agent_t *agent);
int conn_thread_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
int ds);
int conn_thread_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size);
#endif

34
thirdparty/libjuice/src/const_time.c vendored Normal file
View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2021 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "const_time.h"
int const_time_memcmp(const void *a, const void *b, size_t len) {
const unsigned char *ca = a;
const unsigned char *cb = b;
unsigned char x = 0;
for (size_t i = 0; i < len; i++)
x |= ca[i] ^ cb[i];
return x;
}
int const_time_strcmp(const void *a, const void *b) {
const unsigned char *ca = a;
const unsigned char *cb = b;
unsigned char x = 0;
size_t i = 0;
for(;;) {
x |= ca[i] ^ cb[i];
if (!ca[i] || !cb[i])
break;
++i;
}
return x;
}

18
thirdparty/libjuice/src/const_time.h vendored Normal file
View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2021 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CONST_TIME_H
#define JUICE_CONST_TIME_H
#include <stdint.h>
#include <stdlib.h>
int const_time_memcmp(const void *a, const void *b, size_t len);
int const_time_strcmp(const void *a, const void *b);
#endif

38
thirdparty/libjuice/src/crc32.c vendored Normal file
View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "crc32.h"
#define CRC32_REVERSED_POLY 0xEDB88320
#define CRC32_INIT 0xFFFFFFFF
#define CRC32_XOR 0xFFFFFFFF
static uint32_t crc32_byte(uint32_t crc) {
for (int i = 0; i < 8; ++i)
if (crc & 1)
crc = (crc >> 1) ^ CRC32_REVERSED_POLY;
else
crc = (crc >> 1);
return crc;
}
static uint32_t crc32_table(const uint8_t *p, size_t size, uint32_t *table) {
uint32_t crc = CRC32_INIT;
while (size--)
crc = table[(uint8_t)(crc & 0xFF) ^ *p++] ^ (crc >> 8);
return crc ^ CRC32_XOR;
}
JUICE_EXPORT uint32_t juice_crc32(const void *data, size_t size) {
static uint32_t table[256] = {0};
if (table[0] == 0)
for (uint32_t i = 0; i < 256; ++i)
table[i] = crc32_byte(i);
return crc32_table(data, size, table);
}

21
thirdparty/libjuice/src/crc32.h vendored Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_CRC32_H
#define JUICE_CRC32_H
#include "juice.h"
#include <stdint.h>
#include <stdlib.h>
JUICE_EXPORT uint32_t juice_crc32(const void *data, size_t size);
#define CRC32(data, size) juice_crc32(data, size)
#endif // JUICE_CRC32_H

59
thirdparty/libjuice/src/hash.c vendored Normal file
View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "hash.h"
#if USE_NETTLE
#include <nettle/md5.h>
#include <nettle/sha1.h>
#include <nettle/sha2.h>
#else
#include "picohash.h"
#endif
void hash_md5(const void *message, size_t size, void *digest) {
#if USE_NETTLE
struct md5_ctx ctx;
md5_init(&ctx);
md5_update(&ctx, size, message);
md5_digest(&ctx, HASH_MD5_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_md5(&ctx);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}
void hash_sha1(const void *message, size_t size, void *digest) {
#if USE_NETTLE
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, size, message);
sha1_digest(&ctx, HASH_SHA1_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_sha1(&ctx);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}
void hash_sha256(const void *message, size_t size, void *digest) {
#if USE_NETTLE
struct sha256_ctx ctx;
sha256_init(&ctx);
sha256_update(&ctx, size, message);
sha256_digest(&ctx, HASH_SHA256_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_sha256(&ctx);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}

23
thirdparty/libjuice/src/hash.h vendored Normal file
View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_HASH_H
#define JUICE_HASH_H
#include <stdint.h>
#include <stdlib.h>
#define HASH_MD5_SIZE 16
#define HASH_SHA1_SIZE 24
#define HASH_SHA256_SIZE 32
void hash_md5(const void *message, size_t size, void *digest);
void hash_sha1(const void *message, size_t size, void *digest);
void hash_sha256(const void *message, size_t size, void *digest);
#endif

43
thirdparty/libjuice/src/hmac.c vendored Normal file
View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "hmac.h"
#if USE_NETTLE
#include <nettle/hmac.h>
#else
#include "picohash.h"
#endif
void hmac_sha1(const void *message, size_t size, const void *key, size_t key_size, void *digest) {
#if USE_NETTLE
struct hmac_sha1_ctx ctx;
hmac_sha1_set_key(&ctx, key_size, key);
hmac_sha1_update(&ctx, size, message);
hmac_sha1_digest(&ctx, HMAC_SHA1_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_hmac(&ctx, picohash_init_sha1, key, key_size);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}
void hmac_sha256(const void *message, size_t size, const void *key, size_t key_size, void *digest) {
#if USE_NETTLE
struct hmac_sha256_ctx ctx;
hmac_sha256_set_key(&ctx, key_size, key);
hmac_sha256_update(&ctx, size, message);
hmac_sha256_digest(&ctx, HMAC_SHA256_SIZE, digest);
#else
picohash_ctx_t ctx;
picohash_init_hmac(&ctx, picohash_init_sha256, key, key_size);
picohash_update(&ctx, message, size);
picohash_final(&ctx, digest);
#endif
}

21
thirdparty/libjuice/src/hmac.h vendored Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_HMAC_H
#define JUICE_HMAC_H
#include <stdint.h>
#include <stdlib.h>
#define HMAC_SHA1_SIZE 20
#define HMAC_SHA256_SIZE 32
void hmac_sha1(const void *message, size_t size, const void *key, size_t key_size, void *digest);
void hmac_sha256(const void *message, size_t size, const void *key, size_t key_size, void *digest);
#endif

408
thirdparty/libjuice/src/ice.c vendored Normal file
View File

@@ -0,0 +1,408 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "ice.h"
#include "log.h"
#include "random.h"
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 1024
#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
// See RFC4566 for SDP format: https://www.rfc-editor.org/rfc/rfc4566.html
static const char *skip_prefix(const char *str, const char *prefix) {
size_t len = strlen(prefix);
return strncmp(str, prefix, len) == 0 ? str + len : str;
}
static bool match_prefix(const char *str, const char *prefix, const char **end) {
*end = skip_prefix(str, prefix);
return *end != str || !*prefix;
}
static int parse_sdp_line(const char *line, ice_description_t *description) {
const char *arg;
if (match_prefix(line, "a=ice-ufrag:", &arg)) {
sscanf(arg, "%256s", description->ice_ufrag);
return 0;
}
if (match_prefix(line, "a=ice-pwd:", &arg)) {
sscanf(arg, "%256s", description->ice_pwd);
return 0;
}
if (match_prefix(line, "a=end-of-candidates:", &arg)) {
description->finished = true;
return 0;
}
ice_candidate_t candidate;
if (ice_parse_candidate_sdp(line, &candidate) == 0) {
ice_add_candidate(&candidate, description);
return 0;
}
return ICE_PARSE_IGNORED;
}
static int parse_sdp_candidate(const char *line, ice_candidate_t *candidate) {
memset(candidate, 0, sizeof(*candidate));
line = skip_prefix(line, "a=");
line = skip_prefix(line, "candidate:");
char transport[32 + 1];
char type[32 + 1];
if (sscanf(line, "%32s %d %32s %u %256s %32s typ %32s", candidate->foundation,
&candidate->component, transport, &candidate->priority, candidate->hostname,
candidate->service, type) != 7) {
JLOG_WARN("Failed to parse candidate: %s", line);
return ICE_PARSE_ERROR;
}
for (int i = 0; transport[i]; ++i)
transport[i] = toupper((unsigned char)transport[i]);
for (int i = 0; type[i]; ++i)
type[i] = tolower((unsigned char)type[i]);
if (strcmp(type, "host") == 0)
candidate->type = ICE_CANDIDATE_TYPE_HOST;
else if (strcmp(type, "srflx") == 0)
candidate->type = ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
else if (strcmp(type, "relay") == 0)
candidate->type = ICE_CANDIDATE_TYPE_RELAYED;
else {
JLOG_WARN("Ignoring candidate with unknown type \"%s\"", type);
return ICE_PARSE_IGNORED;
}
if (strcmp(transport, "UDP") != 0) {
JLOG_WARN("Ignoring candidate with transport %s", transport);
return ICE_PARSE_IGNORED;
}
return 0;
}
int ice_parse_sdp(const char *sdp, ice_description_t *description) {
memset(description, 0, sizeof(*description));
description->candidates_count = 0;
description->finished = false;
char buffer[BUFFER_SIZE];
size_t size = 0;
while (*sdp) {
if (*sdp == '\n') {
if (size) {
buffer[size++] = '\0';
if(parse_sdp_line(buffer, description) == ICE_PARSE_ERROR)
return ICE_PARSE_ERROR;
size = 0;
}
} else if (*sdp != '\r' && size + 1 < BUFFER_SIZE) {
buffer[size++] = *sdp;
}
++sdp;
}
ice_sort_candidates(description);
JLOG_DEBUG("Parsed remote description: ufrag=\"%s\", pwd=\"%s\", candidates=%d",
description->ice_ufrag, description->ice_pwd, description->candidates_count);
if (*description->ice_ufrag == '\0')
return ICE_PARSE_MISSING_UFRAG;
if (*description->ice_pwd == '\0')
return ICE_PARSE_MISSING_PWD;
return 0;
}
int ice_parse_candidate_sdp(const char *line, ice_candidate_t *candidate) {
const char *arg;
if (match_prefix(line, "a=candidate:", &arg)) {
int ret = parse_sdp_candidate(line, candidate);
if (ret < 0)
return ret;
ice_resolve_candidate(candidate, ICE_RESOLVE_MODE_SIMPLE);
return 0;
}
return ICE_PARSE_ERROR;
}
int ice_create_local_description(ice_description_t *description) {
memset(description, 0, sizeof(*description));
juice_random_str64(description->ice_ufrag, 4 + 1);
juice_random_str64(description->ice_pwd, 22 + 1);
description->candidates_count = 0;
description->finished = false;
JLOG_DEBUG("Created local description: ufrag=\"%s\", pwd=\"%s\"", description->ice_ufrag,
description->ice_pwd);
return 0;
}
int ice_create_local_candidate(ice_candidate_type_t type, int component, int index,
const addr_record_t *record, ice_candidate_t *candidate) {
memset(candidate, 0, sizeof(*candidate));
candidate->type = type;
candidate->component = component;
candidate->resolved = *record;
strcpy(candidate->foundation, "-");
candidate->priority = ice_compute_priority(candidate->type, candidate->resolved.addr.ss_family,
candidate->component, index);
if (getnameinfo((struct sockaddr *)&record->addr, record->len, candidate->hostname, 256,
candidate->service, 32, NI_NUMERICHOST | NI_NUMERICSERV | NI_DGRAM)) {
JLOG_ERROR("getnameinfo failed, errno=%d", sockerrno);
return -1;
}
return 0;
}
int ice_resolve_candidate(ice_candidate_t *candidate, ice_resolve_mode_t mode) {
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_ADDRCONFIG;
if (mode != ICE_RESOLVE_MODE_LOOKUP)
hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV;
struct addrinfo *ai_list = NULL;
if (getaddrinfo(candidate->hostname, candidate->service, &hints, &ai_list)) {
JLOG_INFO("Failed to resolve address: %s:%s", candidate->hostname, candidate->service);
candidate->resolved.len = 0;
return -1;
}
for (struct addrinfo *ai = ai_list; ai; ai = ai->ai_next) {
if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
candidate->resolved.len = (socklen_t)ai->ai_addrlen;
memcpy(&candidate->resolved.addr, ai->ai_addr, ai->ai_addrlen);
break;
}
}
freeaddrinfo(ai_list);
return 0;
}
int ice_add_candidate(ice_candidate_t *candidate, ice_description_t *description) {
if (candidate->type == ICE_CANDIDATE_TYPE_UNKNOWN)
return -1;
if (description->candidates_count >= ICE_MAX_CANDIDATES_COUNT) {
JLOG_WARN("Description already has the maximum number of candidates");
return -1;
}
if (strcmp(candidate->foundation, "-") == 0)
snprintf(candidate->foundation, 32, "%u",
(unsigned int)(description->candidates_count + 1));
ice_candidate_t *pos = description->candidates + description->candidates_count;
*pos = *candidate;
++description->candidates_count;
return 0;
}
void ice_sort_candidates(ice_description_t *description) {
// In-place insertion sort
ice_candidate_t *begin = description->candidates;
ice_candidate_t *end = begin + description->candidates_count;
ice_candidate_t *cur = begin;
while (++cur < end) {
uint32_t priority = cur->priority;
ice_candidate_t *prev = cur;
ice_candidate_t tmp = *prev;
while (--prev >= begin && prev->priority < priority) {
*(prev + 1) = *prev;
}
if (prev + 1 != cur)
*(prev + 1) = tmp;
}
}
ice_candidate_t *ice_find_candidate_from_addr(ice_description_t *description,
const addr_record_t *record,
ice_candidate_type_t type) {
ice_candidate_t *cur = description->candidates;
ice_candidate_t *end = cur + description->candidates_count;
while (cur != end) {
if ((type == ICE_CANDIDATE_TYPE_UNKNOWN || cur->type == type) &&
addr_is_equal((struct sockaddr *)&record->addr, (struct sockaddr *)&cur->resolved.addr,
true))
return cur;
++cur;
}
return NULL;
}
int ice_generate_sdp(const ice_description_t *description, char *buffer, size_t size) {
if (!*description->ice_ufrag || !*description->ice_pwd)
return -1;
int len = 0;
char *begin = buffer;
char *end = begin + size;
// Round 0: description
// Round i with i>0 and i<count+1: candidate i-1
// Round count + 1: end-of-candidates and ice-options lines
for (int i = 0; i < description->candidates_count + 2; ++i) {
int ret;
if (i == 0) {
ret = snprintf(begin, end - begin, "a=ice-ufrag:%s\r\na=ice-pwd:%s\r\n",
description->ice_ufrag, description->ice_pwd);
} else if (i < description->candidates_count + 1) {
const ice_candidate_t *candidate = description->candidates + i - 1;
if (candidate->type == ICE_CANDIDATE_TYPE_UNKNOWN ||
candidate->type == ICE_CANDIDATE_TYPE_PEER_REFLEXIVE)
continue;
char tmp[BUFFER_SIZE];
if (ice_generate_candidate_sdp(candidate, tmp, BUFFER_SIZE) < 0)
continue;
ret = snprintf(begin, end - begin, "%s\r\n", tmp);
} else { // i == description->candidates_count + 1
// RFC 8445 10. ICE Option: An agent compliant to this specification MUST inform the
// peer about the compliance using the 'ice2' option.
if (description->finished)
ret = snprintf(begin, end - begin, "a=end-of-candidates\r\na=ice-options:ice2\r\n");
else
ret = snprintf(begin, end - begin, "a=ice-options:ice2,trickle\r\n");
}
if (ret < 0)
return -1;
len += ret;
if (begin < end)
begin += ret >= end - begin ? end - begin - 1 : ret;
}
return len;
}
int ice_generate_candidate_sdp(const ice_candidate_t *candidate, char *buffer, size_t size) {
const char *type = NULL;
const char *suffix = NULL;
switch (candidate->type) {
case ICE_CANDIDATE_TYPE_HOST:
type = "host";
break;
case ICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
type = "prflx";
break;
case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
type = "srflx";
suffix = "raddr 0.0.0.0 rport 0"; // This is needed for compatibility with Firefox
break;
case ICE_CANDIDATE_TYPE_RELAYED:
type = "relay";
suffix = "raddr 0.0.0.0 rport 0"; // This is needed for compatibility with Firefox
break;
default:
JLOG_ERROR("Unknown candidate type");
return -1;
}
return snprintf(buffer, size, "a=candidate:%s %u UDP %u %s %s typ %s%s%s",
candidate->foundation, candidate->component, candidate->priority,
candidate->hostname, candidate->service, type, suffix ? " " : "",
suffix ? suffix : "");
}
int ice_create_candidate_pair(ice_candidate_t *local, ice_candidate_t *remote, bool is_controlling,
ice_candidate_pair_t *pair) { // local or remote might be NULL
if (local && remote && local->resolved.addr.ss_family != remote->resolved.addr.ss_family) {
JLOG_ERROR("Mismatching candidates address families");
return -1;
}
memset(pair, 0, sizeof(*pair));
pair->local = local;
pair->remote = remote;
pair->state = ICE_CANDIDATE_PAIR_STATE_FROZEN;
return ice_update_candidate_pair(pair, is_controlling);
}
int ice_update_candidate_pair(ice_candidate_pair_t *pair, bool is_controlling) {
// Compute pair priority according to RFC 8445, extended to support generic pairs missing local
// or remote See https://www.rfc-editor.org/rfc/rfc8445.html#section-6.1.2.3
if (!pair->local && !pair->remote)
return 0;
uint64_t local_priority =
pair->local
? pair->local->priority
: ice_compute_priority(ICE_CANDIDATE_TYPE_HOST, pair->remote->resolved.addr.ss_family,
pair->remote->component, 0);
uint64_t remote_priority =
pair->remote
? pair->remote->priority
: ice_compute_priority(ICE_CANDIDATE_TYPE_HOST, pair->local->resolved.addr.ss_family,
pair->local->component, 0);
uint64_t g = is_controlling ? local_priority : remote_priority;
uint64_t d = is_controlling ? remote_priority : local_priority;
uint64_t min = g < d ? g : d;
uint64_t max = g > d ? g : d;
pair->priority = (min << 32) + (max << 1) + (g > d ? 1 : 0);
return 0;
}
int ice_candidates_count(const ice_description_t *description, ice_candidate_type_t type) {
int count = 0;
for (int i = 0; i < description->candidates_count; ++i) {
const ice_candidate_t *candidate = description->candidates + i;
if (candidate->type == type)
++count;
}
return count;
}
uint32_t ice_compute_priority(ice_candidate_type_t type, int family, int component, int index) {
// Compute candidate priority according to RFC 8445
// See https://www.rfc-editor.org/rfc/rfc8445.html#section-5.1.2.1
uint32_t p = 0;
switch (type) {
case ICE_CANDIDATE_TYPE_HOST:
p += ICE_CANDIDATE_PREF_HOST;
break;
case ICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
p += ICE_CANDIDATE_PREF_PEER_REFLEXIVE;
break;
case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
p += ICE_CANDIDATE_PREF_SERVER_REFLEXIVE;
break;
case ICE_CANDIDATE_TYPE_RELAYED:
p += ICE_CANDIDATE_PREF_RELAYED;
break;
default:
break;
}
p <<= 16;
switch (family) {
case AF_INET:
p += 32767;
break;
case AF_INET6:
p += 65535;
break;
default:
break;
}
p -= CLAMP(index, 0, 32767);
p <<= 8;
p += 256 - CLAMP(component, 1, 256);
return p;
}

103
thirdparty/libjuice/src/ice.h vendored Normal file
View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_ICE_H
#define JUICE_ICE_H
#include "addr.h"
#include "juice.h"
#include "timestamp.h"
#include <stdbool.h>
#include <stdint.h>
#define ICE_MAX_CANDIDATES_COUNT 20 // ~ 500B * 20 = 10KB
typedef enum ice_candidate_type {
ICE_CANDIDATE_TYPE_UNKNOWN,
ICE_CANDIDATE_TYPE_HOST,
ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
ICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
ICE_CANDIDATE_TYPE_RELAYED,
} ice_candidate_type_t;
// RFC 8445: The RECOMMENDED values for type preferences are 126 for host candidates, 110 for
// peer-reflexive candidates, 100 for server-reflexive candidates, and 0 for relayed candidates.
#define ICE_CANDIDATE_PREF_HOST 126
#define ICE_CANDIDATE_PREF_PEER_REFLEXIVE 110
#define ICE_CANDIDATE_PREF_SERVER_REFLEXIVE 100
#define ICE_CANDIDATE_PREF_RELAYED 0
typedef struct ice_candidate {
ice_candidate_type_t type;
uint32_t priority;
int component;
char foundation[32 + 1]; // 1 to 32 characters
char transport[32 + 1];
char hostname[256 + 1];
char service[32 + 1];
addr_record_t resolved;
} ice_candidate_t;
typedef struct ice_description {
char ice_ufrag[256 + 1]; // 4 to 256 characters
char ice_pwd[256 + 1]; // 22 to 256 characters
ice_candidate_t candidates[ICE_MAX_CANDIDATES_COUNT];
int candidates_count;
bool finished;
} ice_description_t;
typedef enum ice_candidate_pair_state {
ICE_CANDIDATE_PAIR_STATE_PENDING,
ICE_CANDIDATE_PAIR_STATE_SUCCEEDED,
ICE_CANDIDATE_PAIR_STATE_FAILED,
ICE_CANDIDATE_PAIR_STATE_FROZEN,
} ice_candidate_pair_state_t;
typedef struct ice_candidate_pair {
ice_candidate_t *local;
ice_candidate_t *remote;
uint64_t priority;
ice_candidate_pair_state_t state;
bool nominated;
bool nomination_requested;
timestamp_t consent_expiry;
} ice_candidate_pair_t;
typedef enum ice_resolve_mode {
ICE_RESOLVE_MODE_SIMPLE,
ICE_RESOLVE_MODE_LOOKUP,
} ice_resolve_mode_t;
#define ICE_PARSE_ERROR -1
#define ICE_PARSE_IGNORED -2
#define ICE_PARSE_MISSING_UFRAG -3
#define ICE_PARSE_MISSING_PWD -4
int ice_parse_sdp(const char *sdp, ice_description_t *description);
int ice_parse_candidate_sdp(const char *line, ice_candidate_t *candidate);
int ice_create_local_description(ice_description_t *description);
int ice_create_local_candidate(ice_candidate_type_t type, int component, int index,
const addr_record_t *record, ice_candidate_t *candidate);
int ice_resolve_candidate(ice_candidate_t *candidate, ice_resolve_mode_t mode);
int ice_add_candidate(ice_candidate_t *candidate, ice_description_t *description);
void ice_sort_candidates(ice_description_t *description);
ice_candidate_t *ice_find_candidate_from_addr(ice_description_t *description,
const addr_record_t *record,
ice_candidate_type_t type);
int ice_generate_sdp(const ice_description_t *description, char *buffer, size_t size);
int ice_generate_candidate_sdp(const ice_candidate_t *candidate, char *buffer, size_t size);
int ice_create_candidate_pair(ice_candidate_t *local, ice_candidate_t *remote, bool is_controlling,
ice_candidate_pair_t *pair); // local or remote might be NULL
int ice_update_candidate_pair(ice_candidate_pair_t *pair, bool is_controlling);
int ice_candidates_count(const ice_description_t *description, ice_candidate_type_t type);
uint32_t ice_compute_priority(ice_candidate_type_t type, int family, int component, int index);
#endif

207
thirdparty/libjuice/src/juice.c vendored Normal file
View File

@@ -0,0 +1,207 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "juice.h"
#include "addr.h"
#include "agent.h"
#include "ice.h"
#ifndef NO_SERVER
#include "server.h"
#endif
#include <stdio.h>
JUICE_EXPORT juice_agent_t *juice_create(const juice_config_t *config) {
if (!config)
return NULL;
return agent_create(config);
}
JUICE_EXPORT void juice_destroy(juice_agent_t *agent) {
if (agent)
agent_destroy(agent);
}
JUICE_EXPORT int juice_gather_candidates(juice_agent_t *agent) {
if (!agent)
return JUICE_ERR_INVALID;
if (agent_gather_candidates(agent) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_get_local_description(juice_agent_t *agent, char *buffer, size_t size) {
if (!agent || (!buffer && size))
return JUICE_ERR_INVALID;
if (agent_get_local_description(agent, buffer, size) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_set_remote_description(juice_agent_t *agent, const char *sdp) {
if (!agent || !sdp)
return JUICE_ERR_INVALID;
if (agent_set_remote_description(agent, sdp) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_add_remote_candidate(juice_agent_t *agent, const char *sdp) {
if (!agent || !sdp)
return JUICE_ERR_INVALID;
if (agent_add_remote_candidate(agent, sdp) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_set_remote_gathering_done(juice_agent_t *agent) {
if (!agent)
return JUICE_ERR_INVALID;
if (agent_set_remote_gathering_done(agent) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_send(juice_agent_t *agent, const char *data, size_t size) {
if (!agent || (!data && size))
return JUICE_ERR_INVALID;
if (agent_send(agent, data, size, 0) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_send_diffserv(juice_agent_t *agent, const char *data, size_t size, int ds) {
if (!agent || (!data && size))
return JUICE_ERR_INVALID;
if (agent_send(agent, data, size, ds) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT juice_state_t juice_get_state(juice_agent_t *agent) { return agent_get_state(agent); }
JUICE_EXPORT int juice_get_selected_candidates(juice_agent_t *agent, char *local, size_t local_size,
char *remote, size_t remote_size) {
if (!agent || (!local && local_size) || (!remote && remote_size))
return JUICE_ERR_INVALID;
ice_candidate_t local_cand, remote_cand;
if (agent_get_selected_candidate_pair(agent, &local_cand, &remote_cand))
return JUICE_ERR_NOT_AVAIL;
if (local_size && ice_generate_candidate_sdp(&local_cand, local, local_size) < 0)
return JUICE_ERR_FAILED;
if (remote_size && ice_generate_candidate_sdp(&remote_cand, remote, remote_size) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT int juice_get_selected_addresses(juice_agent_t *agent, char *local, size_t local_size,
char *remote, size_t remote_size) {
if (!agent || (!local && local_size) || (!remote && remote_size))
return JUICE_ERR_INVALID;
ice_candidate_t local_cand, remote_cand;
if (agent_get_selected_candidate_pair(agent, &local_cand, &remote_cand))
return JUICE_ERR_NOT_AVAIL;
if (local_size && addr_record_to_string(&local_cand.resolved, local, local_size) < 0)
return JUICE_ERR_FAILED;
if (remote_size && addr_record_to_string(&remote_cand.resolved, remote, remote_size) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
}
JUICE_EXPORT const char *juice_state_to_string(juice_state_t state) {
switch (state) {
case JUICE_STATE_DISCONNECTED:
return "disconnected";
case JUICE_STATE_GATHERING:
return "gathering";
case JUICE_STATE_CONNECTING:
return "connecting";
case JUICE_STATE_CONNECTED:
return "connected";
case JUICE_STATE_COMPLETED:
return "completed";
case JUICE_STATE_FAILED:
return "failed";
default:
return "unknown";
}
}
JUICE_EXPORT juice_server_t *juice_server_create(const juice_server_config_t *config) {
#ifndef NO_SERVER
if (!config)
return NULL;
return server_create(config);
#else
(void)config;
JLOG_FATAL("The library was compiled without server support");
return NULL;
#endif
}
JUICE_EXPORT void juice_server_destroy(juice_server_t *server) {
#ifndef NO_SERVER
if (server)
server_destroy(server);
#else
(void)server;
#endif
}
JUICE_EXPORT uint16_t juice_server_get_port(juice_server_t *server) {
#ifndef NO_SERVER
return server ? server_get_port(server) : 0;
#else
(void)server;
return 0;
#endif
}
JUICE_EXPORT int juice_server_add_credentials(juice_server_t *server,
const juice_server_credentials_t *credentials,
unsigned long lifetime_ms) {
#ifndef NO_SERVER
if (!server || !credentials)
return JUICE_ERR_INVALID;
if (server_add_credentials(server, credentials, (timediff_t)lifetime_ms) < 0)
return JUICE_ERR_FAILED;
return JUICE_ERR_SUCCESS;
#else
(void)server;
(void)credentials;
(void)lifetime_ms;
return JUICE_ERR_INVALID;
#endif
}

129
thirdparty/libjuice/src/log.c vendored Normal file
View File

@@ -0,0 +1,129 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "log.h"
#include "thread.h" // for mutexes and atomics
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#define BUFFER_SIZE 4096
static const char *log_level_names[] = {"VERBOSE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
static const char *log_level_colors[] = {
"\x1B[90m", // grey
"\x1B[96m", // cyan
"\x1B[39m", // default foreground
"\x1B[93m", // yellow
"\x1B[91m", // red
"\x1B[97m\x1B[41m" // white on red
};
static mutex_t log_mutex = MUTEX_INITIALIZER;
static volatile juice_log_cb_t log_cb = NULL;
static atomic(juice_log_level_t) log_level = ATOMIC_VAR_INIT(JUICE_LOG_LEVEL_WARN);
static bool use_color(void) {
#ifdef _WIN32
return false;
#else
return isatty(fileno(stdout)) != 0;
#endif
}
static int get_localtime(const time_t *t, struct tm *buf) {
#ifdef _WIN32
// Windows does not have POSIX localtime_r...
return localtime_s(buf, t) == 0 ? 0 : -1;
#else // POSIX
return localtime_r(t, buf) != NULL ? 0 : -1;
#endif
}
JUICE_EXPORT void juice_set_log_level(juice_log_level_t level) { atomic_store(&log_level, level); }
JUICE_EXPORT void juice_set_log_handler(juice_log_cb_t cb) {
mutex_lock(&log_mutex);
log_cb = cb;
mutex_unlock(&log_mutex);
}
bool juice_log_is_enabled(juice_log_level_t level) {
return level != JUICE_LOG_LEVEL_NONE && level >= atomic_load(&log_level);
}
void juice_log_write(juice_log_level_t level, const char *file, int line, const char *fmt, ...) {
if (!juice_log_is_enabled(level))
return;
mutex_lock(&log_mutex);
#if !RELEASE
const char *filename = file + strlen(file);
while (filename != file && *filename != '/' && *filename != '\\')
--filename;
if (filename != file)
++filename;
#else
(void)file;
(void)line;
#endif
if (log_cb) {
char message[BUFFER_SIZE];
int len = 0;
#if !RELEASE
len = snprintf(message, BUFFER_SIZE, "%s:%d: ", filename, line);
if (len < 0)
return;
#endif
if (len < BUFFER_SIZE) {
va_list args;
va_start(args, fmt);
vsnprintf(message + len, BUFFER_SIZE - len, fmt, args);
va_end(args);
}
log_cb(level, message);
} else {
time_t t = time(NULL);
struct tm lt;
char buffer[16];
if (get_localtime(&t, &lt) != 0 || strftime(buffer, 16, "%H:%M:%S", &lt) == 0)
buffer[0] = '\0';
if (use_color())
fprintf(stdout, "%s", log_level_colors[level]);
fprintf(stdout, "%s %-7s ", buffer, log_level_names[level]);
#if !RELEASE
fprintf(stdout, "%s:%d: ", filename, line);
#endif
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
if (use_color())
fprintf(stdout, "%s", "\x1B[0m\x1B[0K");
fprintf(stdout, "\n");
fflush(stdout);
}
mutex_unlock(&log_mutex);
}

33
thirdparty/libjuice/src/log.h vendored Normal file
View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_LOG_H
#define JUICE_LOG_H
#include "juice.h"
#include <stdarg.h>
bool juice_log_is_enabled(juice_log_level_t level);
void juice_log_write(juice_log_level_t level, const char *file, int line, const char *fmt, ...);
#define JLOG_VERBOSE(...) juice_log_write(JUICE_LOG_LEVEL_VERBOSE, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_DEBUG(...) juice_log_write(JUICE_LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_INFO(...) juice_log_write(JUICE_LOG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_WARN(...) juice_log_write(JUICE_LOG_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_ERROR(...) juice_log_write(JUICE_LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_FATAL(...) juice_log_write(JUICE_LOG_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__)
#define JLOG_VERBOSE_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_VERBOSE)
#define JLOG_DEBUG_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_DEBUG)
#define JLOG_INFO_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_INFO)
#define JLOG_WARN_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_WARN)
#define JLOG_ERROR_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_ERROR)
#define JLOG_FATAL_ENABLED juice_log_is_enabled(JUICE_LOG_LEVEL_FATAL)
#endif // JUICE_LOG_H

741
thirdparty/libjuice/src/picohash.h vendored Normal file
View File

@@ -0,0 +1,741 @@
/**
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef _picohash_h_
#define _picohash_h_
#include <assert.h>
#include <inttypes.h>
#include <string.h>
#ifdef _WIN32
/* assume Windows is little endian */
#elif defined __BIG_ENDIAN__
#define _PICOHASH_BIG_ENDIAN
#elif defined __LITTLE_ENDIAN__
/* override */
#elif defined __BYTE_ORDER
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define _PICOHASH_BIG_ENDIAN
#endif
#else // ! defined __LITTLE_ENDIAN__
#include <endian.h> // machine/endian.h
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define _PICOHASH_BIG_ENDIAN
#endif
#endif
#define PICOHASH_MD5_BLOCK_LENGTH 64
#define PICOHASH_MD5_DIGEST_LENGTH 16
typedef struct {
uint_fast32_t lo, hi;
uint_fast32_t a, b, c, d;
unsigned char buffer[64];
uint_fast32_t block[PICOHASH_MD5_DIGEST_LENGTH];
} _picohash_md5_ctx_t;
static void _picohash_md5_init(_picohash_md5_ctx_t *ctx);
static void _picohash_md5_update(_picohash_md5_ctx_t *ctx, const void *data, size_t size);
static void _picohash_md5_final(_picohash_md5_ctx_t *ctx, void *digest);
#define PICOHASH_SHA1_BLOCK_LENGTH 64
#define PICOHASH_SHA1_DIGEST_LENGTH 20
typedef struct {
uint32_t buffer[PICOHASH_SHA1_BLOCK_LENGTH / 4];
uint32_t state[PICOHASH_SHA1_DIGEST_LENGTH / 4];
uint64_t byteCount;
uint8_t bufferOffset;
} _picohash_sha1_ctx_t;
static void _picohash_sha1_init(_picohash_sha1_ctx_t *ctx);
static void _picohash_sha1_update(_picohash_sha1_ctx_t *ctx, const void *input, size_t len);
static void _picohash_sha1_final(_picohash_sha1_ctx_t *ctx, void *digest);
#define PICOHASH_SHA256_BLOCK_LENGTH 64
#define PICOHASH_SHA256_DIGEST_LENGTH 32
#define PICOHASH_SHA224_BLOCK_LENGTH PICOHASH_SHA256_BLOCK_LENGTH
#define PICOHASH_SHA224_DIGEST_LENGTH 28
typedef struct {
uint64_t length;
uint32_t state[PICOHASH_SHA256_DIGEST_LENGTH / 4];
uint32_t curlen;
unsigned char buf[PICOHASH_SHA256_BLOCK_LENGTH];
} _picohash_sha256_ctx_t;
static void _picohash_sha256_init(_picohash_sha256_ctx_t *ctx);
static void _picohash_sha256_update(_picohash_sha256_ctx_t *ctx, const void *data, size_t len);
static void _picohash_sha256_final(_picohash_sha256_ctx_t *ctx, void *digest);
static void _picohash_sha224_init(_picohash_sha256_ctx_t *ctx);
static void _picohash_sha224_final(_picohash_sha256_ctx_t *ctx, void *digest);
#define PICOHASH_MAX_BLOCK_LENGTH 64
#define PICOHASH_MAX_DIGEST_LENGTH 32
typedef struct {
union {
_picohash_md5_ctx_t _md5;
_picohash_sha1_ctx_t _sha1;
_picohash_sha256_ctx_t _sha256;
};
size_t block_length;
size_t digest_length;
void (*_reset)(void *ctx);
void (*_update)(void *ctx, const void *input, size_t len);
void (*_final)(void *ctx, void *digest);
struct {
unsigned char key[PICOHASH_MAX_BLOCK_LENGTH];
void (*hash_reset)(void *ctx);
void (*hash_final)(void *ctx, void *digest);
} _hmac;
} picohash_ctx_t;
static void picohash_init_md5(picohash_ctx_t *ctx);
static void picohash_init_sha1(picohash_ctx_t *ctx);
static void picohash_init_sha224(picohash_ctx_t *ctx);
static void picohash_init_sha256(picohash_ctx_t *ctx);
static void picohash_update(picohash_ctx_t *ctx, const void *input, size_t len);
static void picohash_final(picohash_ctx_t *ctx, void *digest);
static void picohash_reset(picohash_ctx_t *ctx);
static void picohash_init_hmac(picohash_ctx_t *ctx, void (*initf)(picohash_ctx_t *), const void *key, size_t key_len);
/* following are private definitions */
/*
* The basic MD5 functions.
*
* F is optimized compared to its RFC 1321 definition just like in Colin
* Plumb's implementation.
*/
#define _PICOHASH_MD5_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define _PICOHASH_MD5_G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
#define _PICOHASH_MD5_H(x, y, z) ((x) ^ (y) ^ (z))
#define _PICOHASH_MD5_I(x, y, z) ((y) ^ ((x) | ~(z)))
/*
* The MD5 transformation for all four rounds.
*/
#define _PICOHASH_MD5_STEP(f, a, b, c, d, x, t, s) \
(a) += f((b), (c), (d)) + (x) + (t); \
(a) = (((a) << (s)) | (((a)&0xffffffff) >> (32 - (s)))); \
(a) += (b);
/*
* SET reads 4 input bytes in little-endian byte order and stores them
* in a properly aligned word in host byte order.
*
* Paul-Louis Ageneau: Removed optimization for little-endian architectures
* as it resulted in incorrect behavior when compiling with gcc optimizations.
*/
#define _PICOHASH_MD5_SET(n) \
(ctx->block[(n)] = (uint_fast32_t)ptr[(n)*4] | ((uint_fast32_t)ptr[(n)*4 + 1] << 8) | ((uint_fast32_t)ptr[(n)*4 + 2] << 16) | \
((uint_fast32_t)ptr[(n)*4 + 3] << 24))
#define _PICOHASH_MD5_GET(n) (ctx->block[(n)])
/*
* This processes one or more 64-byte data blocks, but does NOT update
* the bit counters. There're no alignment requirements.
*/
static const void *_picohash_md5_body(_picohash_md5_ctx_t *ctx, const void *data, size_t size)
{
const unsigned char *ptr;
uint_fast32_t a, b, c, d;
uint_fast32_t saved_a, saved_b, saved_c, saved_d;
ptr = data;
a = ctx->a;
b = ctx->b;
c = ctx->c;
d = ctx->d;
do {
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
/* Round 1 */
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, a, b, c, d, _PICOHASH_MD5_SET(0), 0xd76aa478, 7)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, d, a, b, c, _PICOHASH_MD5_SET(1), 0xe8c7b756, 12)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, c, d, a, b, _PICOHASH_MD5_SET(2), 0x242070db, 17)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, b, c, d, a, _PICOHASH_MD5_SET(3), 0xc1bdceee, 22)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, a, b, c, d, _PICOHASH_MD5_SET(4), 0xf57c0faf, 7)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, d, a, b, c, _PICOHASH_MD5_SET(5), 0x4787c62a, 12)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, c, d, a, b, _PICOHASH_MD5_SET(6), 0xa8304613, 17)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, b, c, d, a, _PICOHASH_MD5_SET(7), 0xfd469501, 22)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, a, b, c, d, _PICOHASH_MD5_SET(8), 0x698098d8, 7)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, d, a, b, c, _PICOHASH_MD5_SET(9), 0x8b44f7af, 12)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, c, d, a, b, _PICOHASH_MD5_SET(10), 0xffff5bb1, 17)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, b, c, d, a, _PICOHASH_MD5_SET(11), 0x895cd7be, 22)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, a, b, c, d, _PICOHASH_MD5_SET(12), 0x6b901122, 7)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, d, a, b, c, _PICOHASH_MD5_SET(13), 0xfd987193, 12)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, c, d, a, b, _PICOHASH_MD5_SET(14), 0xa679438e, 17)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_F, b, c, d, a, _PICOHASH_MD5_SET(15), 0x49b40821, 22)
/* Round 2 */
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, a, b, c, d, _PICOHASH_MD5_GET(1), 0xf61e2562, 5)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, d, a, b, c, _PICOHASH_MD5_GET(6), 0xc040b340, 9)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, c, d, a, b, _PICOHASH_MD5_GET(11), 0x265e5a51, 14)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, b, c, d, a, _PICOHASH_MD5_GET(0), 0xe9b6c7aa, 20)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, a, b, c, d, _PICOHASH_MD5_GET(5), 0xd62f105d, 5)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, d, a, b, c, _PICOHASH_MD5_GET(10), 0x02441453, 9)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, c, d, a, b, _PICOHASH_MD5_GET(15), 0xd8a1e681, 14)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, b, c, d, a, _PICOHASH_MD5_GET(4), 0xe7d3fbc8, 20)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, a, b, c, d, _PICOHASH_MD5_GET(9), 0x21e1cde6, 5)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, d, a, b, c, _PICOHASH_MD5_GET(14), 0xc33707d6, 9)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, c, d, a, b, _PICOHASH_MD5_GET(3), 0xf4d50d87, 14)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, b, c, d, a, _PICOHASH_MD5_GET(8), 0x455a14ed, 20)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, a, b, c, d, _PICOHASH_MD5_GET(13), 0xa9e3e905, 5)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, d, a, b, c, _PICOHASH_MD5_GET(2), 0xfcefa3f8, 9)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, c, d, a, b, _PICOHASH_MD5_GET(7), 0x676f02d9, 14)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_G, b, c, d, a, _PICOHASH_MD5_GET(12), 0x8d2a4c8a, 20)
/* Round 3 */
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, a, b, c, d, _PICOHASH_MD5_GET(5), 0xfffa3942, 4)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, d, a, b, c, _PICOHASH_MD5_GET(8), 0x8771f681, 11)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, c, d, a, b, _PICOHASH_MD5_GET(11), 0x6d9d6122, 16)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, b, c, d, a, _PICOHASH_MD5_GET(14), 0xfde5380c, 23)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, a, b, c, d, _PICOHASH_MD5_GET(1), 0xa4beea44, 4)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, d, a, b, c, _PICOHASH_MD5_GET(4), 0x4bdecfa9, 11)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, c, d, a, b, _PICOHASH_MD5_GET(7), 0xf6bb4b60, 16)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, b, c, d, a, _PICOHASH_MD5_GET(10), 0xbebfbc70, 23)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, a, b, c, d, _PICOHASH_MD5_GET(13), 0x289b7ec6, 4)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, d, a, b, c, _PICOHASH_MD5_GET(0), 0xeaa127fa, 11)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, c, d, a, b, _PICOHASH_MD5_GET(3), 0xd4ef3085, 16)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, b, c, d, a, _PICOHASH_MD5_GET(6), 0x04881d05, 23)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, a, b, c, d, _PICOHASH_MD5_GET(9), 0xd9d4d039, 4)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, d, a, b, c, _PICOHASH_MD5_GET(12), 0xe6db99e5, 11)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, c, d, a, b, _PICOHASH_MD5_GET(15), 0x1fa27cf8, 16)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_H, b, c, d, a, _PICOHASH_MD5_GET(2), 0xc4ac5665, 23)
/* Round 4 */
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, a, b, c, d, _PICOHASH_MD5_GET(0), 0xf4292244, 6)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, d, a, b, c, _PICOHASH_MD5_GET(7), 0x432aff97, 10)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, c, d, a, b, _PICOHASH_MD5_GET(14), 0xab9423a7, 15)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, b, c, d, a, _PICOHASH_MD5_GET(5), 0xfc93a039, 21)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, a, b, c, d, _PICOHASH_MD5_GET(12), 0x655b59c3, 6)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, d, a, b, c, _PICOHASH_MD5_GET(3), 0x8f0ccc92, 10)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, c, d, a, b, _PICOHASH_MD5_GET(10), 0xffeff47d, 15)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, b, c, d, a, _PICOHASH_MD5_GET(1), 0x85845dd1, 21)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, a, b, c, d, _PICOHASH_MD5_GET(8), 0x6fa87e4f, 6)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, d, a, b, c, _PICOHASH_MD5_GET(15), 0xfe2ce6e0, 10)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, c, d, a, b, _PICOHASH_MD5_GET(6), 0xa3014314, 15)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, b, c, d, a, _PICOHASH_MD5_GET(13), 0x4e0811a1, 21)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, a, b, c, d, _PICOHASH_MD5_GET(4), 0xf7537e82, 6)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, d, a, b, c, _PICOHASH_MD5_GET(11), 0xbd3af235, 10)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, c, d, a, b, _PICOHASH_MD5_GET(2), 0x2ad7d2bb, 15)
_PICOHASH_MD5_STEP(_PICOHASH_MD5_I, b, c, d, a, _PICOHASH_MD5_GET(9), 0xeb86d391, 21)
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx->a = a;
ctx->b = b;
ctx->c = c;
ctx->d = d;
return ptr;
}
inline void _picohash_md5_init(_picohash_md5_ctx_t *ctx)
{
ctx->a = 0x67452301;
ctx->b = 0xefcdab89;
ctx->c = 0x98badcfe;
ctx->d = 0x10325476;
ctx->lo = 0;
ctx->hi = 0;
}
inline void _picohash_md5_update(_picohash_md5_ctx_t *ctx, const void *data, size_t size)
{
uint_fast32_t saved_lo;
unsigned long used, free;
saved_lo = ctx->lo;
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
ctx->hi++;
ctx->hi += (uint_fast32_t)(size >> 29);
used = saved_lo & 0x3f;
if (used) {
free = 64 - used;
if (size < free) {
memcpy(&ctx->buffer[used], data, size);
return;
}
memcpy(&ctx->buffer[used], data, free);
data = (const unsigned char *)data + free;
size -= free;
_picohash_md5_body(ctx, ctx->buffer, 64);
}
if (size >= 64) {
data = _picohash_md5_body(ctx, data, size & ~(unsigned long)0x3f);
size &= 0x3f;
}
memcpy(ctx->buffer, data, size);
}
inline void _picohash_md5_final(_picohash_md5_ctx_t *ctx, void *_digest)
{
unsigned char *digest = _digest;
unsigned long used, free;
used = ctx->lo & 0x3f;
ctx->buffer[used++] = 0x80;
free = 64 - used;
if (free < 8) {
memset(&ctx->buffer[used], 0, free);
_picohash_md5_body(ctx, ctx->buffer, 64);
used = 0;
free = 64;
}
memset(&ctx->buffer[used], 0, free - 8);
ctx->lo <<= 3;
ctx->buffer[56] = ctx->lo;
ctx->buffer[57] = ctx->lo >> 8;
ctx->buffer[58] = ctx->lo >> 16;
ctx->buffer[59] = ctx->lo >> 24;
ctx->buffer[60] = ctx->hi;
ctx->buffer[61] = ctx->hi >> 8;
ctx->buffer[62] = ctx->hi >> 16;
ctx->buffer[63] = ctx->hi >> 24;
_picohash_md5_body(ctx, ctx->buffer, 64);
digest[0] = ctx->a;
digest[1] = ctx->a >> 8;
digest[2] = ctx->a >> 16;
digest[3] = ctx->a >> 24;
digest[4] = ctx->b;
digest[5] = ctx->b >> 8;
digest[6] = ctx->b >> 16;
digest[7] = ctx->b >> 24;
digest[8] = ctx->c;
digest[9] = ctx->c >> 8;
digest[10] = ctx->c >> 16;
digest[11] = ctx->c >> 24;
digest[12] = ctx->d;
digest[13] = ctx->d >> 8;
digest[14] = ctx->d >> 16;
digest[15] = ctx->d >> 24;
memset(ctx, 0, sizeof(*ctx));
}
#define _PICOHASH_SHA1_K0 0x5a827999
#define _PICOHASH_SHA1_K20 0x6ed9eba1
#define _PICOHASH_SHA1_K40 0x8f1bbcdc
#define _PICOHASH_SHA1_K60 0xca62c1d6
static inline uint32_t _picohash_sha1_rol32(uint32_t number, uint8_t bits)
{
return ((number << bits) | (number >> (32 - bits)));
}
static inline void _picohash_sha1_hash_block(_picohash_sha1_ctx_t *s)
{
uint8_t i;
uint32_t a, b, c, d, e, t;
a = s->state[0];
b = s->state[1];
c = s->state[2];
d = s->state[3];
e = s->state[4];
for (i = 0; i < 80; i++) {
if (i >= 16) {
t = s->buffer[(i + 13) & 15] ^ s->buffer[(i + 8) & 15] ^ s->buffer[(i + 2) & 15] ^ s->buffer[i & 15];
s->buffer[i & 15] = _picohash_sha1_rol32(t, 1);
}
if (i < 20) {
t = (d ^ (b & (c ^ d))) + _PICOHASH_SHA1_K0;
} else if (i < 40) {
t = (b ^ c ^ d) + _PICOHASH_SHA1_K20;
} else if (i < 60) {
t = ((b & c) | (d & (b | c))) + _PICOHASH_SHA1_K40;
} else {
t = (b ^ c ^ d) + _PICOHASH_SHA1_K60;
}
t += _picohash_sha1_rol32(a, 5) + e + s->buffer[i & 15];
e = d;
d = c;
c = _picohash_sha1_rol32(b, 30);
b = a;
a = t;
}
s->state[0] += a;
s->state[1] += b;
s->state[2] += c;
s->state[3] += d;
s->state[4] += e;
}
static inline void _picohash_sha1_add_uncounted(_picohash_sha1_ctx_t *s, uint8_t data)
{
uint8_t *const b = (uint8_t *)s->buffer;
#ifdef _PICOHASH_BIG_ENDIAN
b[s->bufferOffset] = data;
#else
b[s->bufferOffset ^ 3] = data;
#endif
s->bufferOffset++;
if (s->bufferOffset == PICOHASH_SHA1_BLOCK_LENGTH) {
_picohash_sha1_hash_block(s);
s->bufferOffset = 0;
}
}
inline void _picohash_sha1_init(_picohash_sha1_ctx_t *s)
{
s->state[0] = 0x67452301;
s->state[1] = 0xefcdab89;
s->state[2] = 0x98badcfe;
s->state[3] = 0x10325476;
s->state[4] = 0xc3d2e1f0;
s->byteCount = 0;
s->bufferOffset = 0;
}
inline void _picohash_sha1_update(_picohash_sha1_ctx_t *s, const void *_data, size_t len)
{
const uint8_t *data = _data;
for (; len != 0; --len) {
++s->byteCount;
_picohash_sha1_add_uncounted(s, *data++);
}
}
inline void _picohash_sha1_final(_picohash_sha1_ctx_t *s, void *digest)
{
// Pad with 0x80 followed by 0x00 until the end of the block
_picohash_sha1_add_uncounted(s, 0x80);
while (s->bufferOffset != 56)
_picohash_sha1_add_uncounted(s, 0x00);
// Append length in the last 8 bytes
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 53)); // Shifting to multiply by 8
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 45)); // as SHA-1 supports bitstreams
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 37)); // as well as byte.
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 29));
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 21));
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 13));
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount >> 5));
_picohash_sha1_add_uncounted(s, (uint8_t)(s->byteCount << 3));
#ifndef SHA_BIG_ENDIAN
{ // Swap byte order back
int i;
for (i = 0; i < 5; i++) {
s->state[i] = (((s->state[i]) << 24) & 0xff000000) | (((s->state[i]) << 8) & 0x00ff0000) |
(((s->state[i]) >> 8) & 0x0000ff00) | (((s->state[i]) >> 24) & 0x000000ff);
}
}
#endif
memcpy(digest, s->state, sizeof(s->state));
}
#define _picohash_sha256_ch(x, y, z) (z ^ (x & (y ^ z)))
#define _picohash_sha256_maj(x, y, z) (((x | y) & z) | (x & y))
#define _picohash_sha256_s(x, y) \
(((((uint32_t)(x)&0xFFFFFFFFUL) >> (uint32_t)((y)&31)) | ((uint32_t)(x) << (uint32_t)(32 - ((y)&31)))) & 0xFFFFFFFFUL)
#define _picohash_sha256_r(x, n) (((x)&0xFFFFFFFFUL) >> (n))
#define _picohash_sha256_sigma0(x) (_picohash_sha256_s(x, 2) ^ _picohash_sha256_s(x, 13) ^ _picohash_sha256_s(x, 22))
#define _picohash_sha256_sigma1(x) (_picohash_sha256_s(x, 6) ^ _picohash_sha256_s(x, 11) ^ _picohash_sha256_s(x, 25))
#define _picohash_sha256_gamma0(x) (_picohash_sha256_s(x, 7) ^ _picohash_sha256_s(x, 18) ^ _picohash_sha256_r(x, 3))
#define _picohash_sha256_gamma1(x) (_picohash_sha256_s(x, 17) ^ _picohash_sha256_s(x, 19) ^ _picohash_sha256_r(x, 10))
#define _picohash_sha256_rnd(a, b, c, d, e, f, g, h, i) \
t0 = h + _picohash_sha256_sigma1(e) + _picohash_sha256_ch(e, f, g) + K[i] + W[i]; \
t1 = _picohash_sha256_sigma0(a) + _picohash_sha256_maj(a, b, c); \
d += t0; \
h = t0 + t1;
static inline void _picohash_sha256_compress(_picohash_sha256_ctx_t *ctx, unsigned char *buf)
{
static const uint32_t K[64] = {
0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL,
0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL,
0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL,
0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL,
0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL,
0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL,
0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL};
uint32_t S[8], W[64], t, t0, t1;
int i;
/* copy state into S */
for (i = 0; i < 8; i++)
S[i] = ctx->state[i];
/* copy the state into 512-bits into W[0..15] */
for (i = 0; i < 16; i++)
W[i] =
(uint32_t)buf[4 * i] << 24 | (uint32_t)buf[4 * i + 1] << 16 | (uint32_t)buf[4 * i + 2] << 8 | (uint32_t)buf[4 * i + 3];
/* fill W[16..63] */
for (i = 16; i < 64; i++)
W[i] = _picohash_sha256_gamma1(W[i - 2]) + W[i - 7] + _picohash_sha256_gamma0(W[i - 15]) + W[i - 16];
/* Compress */
for (i = 0; i < 64; ++i) {
_picohash_sha256_rnd(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i);
t = S[7];
S[7] = S[6];
S[6] = S[5];
S[5] = S[4];
S[4] = S[3];
S[3] = S[2];
S[2] = S[1];
S[1] = S[0];
S[0] = t;
}
/* feedback */
for (i = 0; i < 8; i++)
ctx->state[i] = ctx->state[i] + S[i];
}
static inline void _picohash_sha256_do_final(_picohash_sha256_ctx_t *ctx, void *digest, size_t len)
{
unsigned char *out = digest;
size_t i;
/* increase the length of the message */
ctx->length += ctx->curlen * 8;
/* append the '1' bit */
ctx->buf[ctx->curlen++] = (unsigned char)0x80;
/* if the length is currently above 56 bytes we append zeros
* then compress. Then we can fall back to padding zeros and length
* encoding like normal.
*/
if (ctx->curlen > 56) {
while (ctx->curlen < 64) {
ctx->buf[ctx->curlen++] = (unsigned char)0;
}
_picohash_sha256_compress(ctx, ctx->buf);
ctx->curlen = 0;
}
/* pad upto 56 bytes of zeroes */
while (ctx->curlen < 56) {
ctx->buf[ctx->curlen++] = (unsigned char)0;
}
/* store length */
for (i = 0; i != 8; ++i)
ctx->buf[56 + i] = (unsigned char)(ctx->length >> (56 - 8 * i));
_picohash_sha256_compress(ctx, ctx->buf);
/* copy output */
for (i = 0; i != len / 4; ++i) {
out[i * 4] = ctx->state[i] >> 24;
out[i * 4 + 1] = ctx->state[i] >> 16;
out[i * 4 + 2] = ctx->state[i] >> 8;
out[i * 4 + 3] = ctx->state[i];
}
}
inline void _picohash_sha256_init(_picohash_sha256_ctx_t *ctx)
{
ctx->curlen = 0;
ctx->length = 0;
ctx->state[0] = 0x6A09E667UL;
ctx->state[1] = 0xBB67AE85UL;
ctx->state[2] = 0x3C6EF372UL;
ctx->state[3] = 0xA54FF53AUL;
ctx->state[4] = 0x510E527FUL;
ctx->state[5] = 0x9B05688CUL;
ctx->state[6] = 0x1F83D9ABUL;
ctx->state[7] = 0x5BE0CD19UL;
}
inline void _picohash_sha256_update(_picohash_sha256_ctx_t *ctx, const void *data, size_t len)
{
const unsigned char *in = data;
size_t n;
while (len > 0) {
if (ctx->curlen == 0 && len >= PICOHASH_SHA256_BLOCK_LENGTH) {
_picohash_sha256_compress(ctx, (unsigned char *)in);
ctx->length += PICOHASH_SHA256_BLOCK_LENGTH * 8;
in += PICOHASH_SHA256_BLOCK_LENGTH;
len -= PICOHASH_SHA256_BLOCK_LENGTH;
} else {
n = PICOHASH_SHA256_BLOCK_LENGTH - ctx->curlen;
if (n > len)
n = len;
memcpy(ctx->buf + ctx->curlen, in, n);
ctx->curlen += (uint32_t)n;
in += n;
len -= n;
if (ctx->curlen == 64) {
_picohash_sha256_compress(ctx, ctx->buf);
ctx->length += 8 * PICOHASH_SHA256_BLOCK_LENGTH;
ctx->curlen = 0;
}
}
}
}
inline void _picohash_sha256_final(_picohash_sha256_ctx_t *ctx, void *digest)
{
_picohash_sha256_do_final(ctx, digest, PICOHASH_SHA256_DIGEST_LENGTH);
}
inline void _picohash_sha224_init(_picohash_sha256_ctx_t *ctx)
{
ctx->curlen = 0;
ctx->length = 0;
ctx->state[0] = 0xc1059ed8UL;
ctx->state[1] = 0x367cd507UL;
ctx->state[2] = 0x3070dd17UL;
ctx->state[3] = 0xf70e5939UL;
ctx->state[4] = 0xffc00b31UL;
ctx->state[5] = 0x68581511UL;
ctx->state[6] = 0x64f98fa7UL;
ctx->state[7] = 0xbefa4fa4UL;
}
inline void _picohash_sha224_final(_picohash_sha256_ctx_t *ctx, void *digest)
{
_picohash_sha256_do_final(ctx, digest, PICOHASH_SHA224_DIGEST_LENGTH);
}
inline void picohash_init_md5(picohash_ctx_t *ctx)
{
ctx->block_length = PICOHASH_MD5_BLOCK_LENGTH;
ctx->digest_length = PICOHASH_MD5_DIGEST_LENGTH;
ctx->_reset = (void *)_picohash_md5_init;
ctx->_update = (void *)_picohash_md5_update;
ctx->_final = (void *)_picohash_md5_final;
_picohash_md5_init(&ctx->_md5);
}
inline void picohash_init_sha1(picohash_ctx_t *ctx)
{
ctx->block_length = PICOHASH_SHA1_BLOCK_LENGTH;
ctx->digest_length = PICOHASH_SHA1_DIGEST_LENGTH;
ctx->_reset = (void *)_picohash_sha1_init;
ctx->_update = (void *)_picohash_sha1_update;
ctx->_final = (void *)_picohash_sha1_final;
_picohash_sha1_init(&ctx->_sha1);
}
inline void picohash_init_sha224(picohash_ctx_t *ctx)
{
ctx->block_length = PICOHASH_SHA224_BLOCK_LENGTH;
ctx->digest_length = PICOHASH_SHA224_DIGEST_LENGTH;
ctx->_reset = (void *)_picohash_sha224_init;
ctx->_update = (void *)_picohash_sha256_update;
ctx->_final = (void *)_picohash_sha224_final;
_picohash_sha224_init(&ctx->_sha256);
}
inline void picohash_init_sha256(picohash_ctx_t *ctx)
{
ctx->block_length = PICOHASH_SHA256_BLOCK_LENGTH;
ctx->digest_length = PICOHASH_SHA256_DIGEST_LENGTH;
ctx->_reset = (void *)_picohash_sha256_init;
ctx->_update = (void *)_picohash_sha256_update;
ctx->_final = (void *)_picohash_sha256_final;
_picohash_sha256_init(&ctx->_sha256);
}
inline void picohash_update(picohash_ctx_t *ctx, const void *input, size_t len)
{
ctx->_update(ctx, input, len);
}
inline void picohash_final(picohash_ctx_t *ctx, void *digest)
{
ctx->_final(ctx, digest);
}
inline void picohash_reset(picohash_ctx_t *ctx)
{
ctx->_reset(ctx);
}
static inline void _picohash_hmac_apply_key(picohash_ctx_t *ctx, unsigned char delta)
{
size_t i;
for (i = 0; i != ctx->block_length; ++i)
ctx->_hmac.key[i] ^= delta;
picohash_update(ctx, ctx->_hmac.key, ctx->block_length);
for (i = 0; i != ctx->block_length; ++i)
ctx->_hmac.key[i] ^= delta;
}
static void _picohash_hmac_final(picohash_ctx_t *ctx, void *digest)
{
unsigned char inner_digest[PICOHASH_MAX_DIGEST_LENGTH];
ctx->_hmac.hash_final(ctx, inner_digest);
ctx->_hmac.hash_reset(ctx);
_picohash_hmac_apply_key(ctx, 0x5c);
picohash_update(ctx, inner_digest, ctx->digest_length);
memset(inner_digest, 0, ctx->digest_length);
ctx->_hmac.hash_final(ctx, digest);
}
static inline void _picohash_hmac_reset(picohash_ctx_t *ctx)
{
ctx->_hmac.hash_reset(ctx);
_picohash_hmac_apply_key(ctx, 0x36);
}
inline void picohash_init_hmac(picohash_ctx_t *ctx, void (*initf)(picohash_ctx_t *), const void *key, size_t key_len)
{
initf(ctx);
memset(ctx->_hmac.key, 0, ctx->block_length);
if (key_len > ctx->block_length) {
/* hash the key if it is too long */
picohash_update(ctx, key, key_len);
picohash_final(ctx, ctx->_hmac.key);
ctx->_hmac.hash_reset(ctx);
} else {
memcpy(ctx->_hmac.key, key, key_len);
}
/* replace reset and final function */
ctx->_hmac.hash_reset = ctx->_reset;
ctx->_hmac.hash_final = ctx->_final;
ctx->_reset = (void *)_picohash_hmac_reset;
ctx->_final = (void *)_picohash_hmac_final;
/* start calculating the inner hash */
_picohash_hmac_apply_key(ctx, 0x36);
}
#endif

126
thirdparty/libjuice/src/random.c vendored Normal file
View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "random.h"
#include "log.h"
#include "thread.h" // for mutexes
#include <math.h>
#include <stdbool.h>
#include <time.h>
// getrandom() is not available in Android NDK API < 28 and needs glibc >= 2.25
#if defined(__linux__) && !defined(__ANDROID__) && (!defined(__GLIBC__) || __GLIBC__ > 2 || __GLIBC_MINOR__ >= 25)
#include <errno.h>
#include <sys/random.h>
static int random_bytes(void *buf, size_t size) {
ssize_t ret = getrandom(buf, size, 0);
if (ret < 0) {
JLOG_WARN("getrandom failed, errno=%d", errno);
return -1;
}
if ((size_t)ret < size) {
JLOG_WARN("getrandom returned too few bytes, size=%zu, returned=%zu", size, (size_t)ret);
return -1;
}
return 0;
}
#elif defined(_WIN32)
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Windows 7
#endif
#include <windows.h>
//
#include <bcrypt.h>
static int random_bytes(void *buf, size_t size) {
// Requires Windows 7 or later
NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)size, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
return !status ? 0 : -1;
}
#else
static int random_bytes(void *buf, size_t size) {
(void)buf;
(void)size;
return -1;
}
#endif
static unsigned int generate_seed() {
#ifdef _WIN32
return (unsigned int)GetTickCount();
#else
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == 0)
return (unsigned int)(ts.tv_sec ^ ts.tv_nsec);
else
return (unsigned int)time(NULL);
#endif
}
void juice_random(void *buf, size_t size) {
if (random_bytes(buf, size) == 0)
return;
// rand() is not thread-safe
static mutex_t rand_mutex = MUTEX_INITIALIZER;
mutex_lock(&rand_mutex);
static bool srandom_called = false;
#if defined(__linux__) || defined(__unix__) || defined(__APPLE__)
#define random_func random
#define srandom_func srandom
if (!srandom_called)
JLOG_DEBUG("Using random() for random bytes");
#else
#define random_func rand
#define srandom_func srand
if (!srandom_called)
JLOG_WARN("Falling back on rand() for random bytes");
#endif
if (!srandom_called) {
srandom_func(generate_seed());
srandom_called = true;
}
// RAND_MAX is guaranteed to be at least 2^15 - 1
uint8_t *bytes = buf;
for (size_t i = 0; i < size; ++i)
bytes[i] = (uint8_t)((random_func() & 0x7f80) >> 7);
mutex_unlock(&rand_mutex);
}
void juice_random_str64(char *buf, size_t size) {
static const char chars64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t i = 0;
for (i = 0; i + 1 < size; ++i) {
uint8_t byte = 0;
juice_random(&byte, 1);
buf[i] = chars64[byte & 0x3F];
}
buf[i] = '\0';
}
uint32_t juice_rand32(void) {
uint32_t r = 0;
juice_random(&r, sizeof(r));
return r;
}
uint64_t juice_rand64(void) {
uint64_t r = 0;
juice_random(&r, sizeof(r));
return r;
}

21
thirdparty/libjuice/src/random.h vendored Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_RANDOM_H
#define JUICE_RANDOM_H
#include <stdint.h>
#include <stdlib.h>
void juice_random(void *buf, size_t size);
void juice_random_str64(char *buf, size_t size);
uint32_t juice_rand32(void);
uint64_t juice_rand64(void);
#endif // JUICE_RANDOM_H

1143
thirdparty/libjuice/src/server.c vendored Normal file

File diff suppressed because it is too large Load Diff

123
thirdparty/libjuice/src/server.h vendored Normal file
View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_SERVER_H
#define JUICE_SERVER_H
#ifndef NO_SERVER
#include "addr.h"
#include "juice.h"
#include "socket.h"
#include "stun.h"
#include "thread.h"
#include "timestamp.h"
#include "turn.h"
#include <stdbool.h>
#include <stdint.h>
#define SERVER_DEFAULT_REALM "libjuice"
#define SERVER_DEFAULT_MAX_ALLOCATIONS 1000 // should be 1024-1 or less to be safe for poll()
#define SERVER_DEFAULT_MAX_PEERS 16
#define SERVER_NONCE_KEY_SIZE 32
// RFC 8656: The server [...] SHOULD expire the nonce at least once every hour during the lifetime
// of the allocation
#define SERVER_NONCE_KEY_LIFETIME 600 * 1000 // 10 min
typedef enum server_turn_alloc_state {
SERVER_TURN_ALLOC_EMPTY,
SERVER_TURN_ALLOC_DELETED,
SERVER_TURN_ALLOC_FULL
} server_turn_alloc_state_t;
typedef struct server_turn_alloc {
server_turn_alloc_state_t state;
addr_record_t record;
juice_server_credentials_t *credentials;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
timestamp_t timestamp;
socket_t sock;
turn_map_t map;
} server_turn_alloc_t;
typedef struct juice_credentials_list {
struct juice_credentials_list *next;
juice_server_credentials_t credentials;
uint8_t userhash[USERHASH_SIZE];
timestamp_t timestamp;
} juice_credentials_list_t;
typedef struct juice_server {
juice_server_config_t config; // Note config.credentials will be empty
juice_credentials_list_t *credentials; // Credentials are stored in this list
uint8_t nonce_key[SERVER_NONCE_KEY_SIZE];
timestamp_t nonce_key_timestamp;
socket_t sock;
thread_t thread;
mutex_t mutex;
bool thread_stopped;
server_turn_alloc_t *allocs;
int allocs_count;
} juice_server_t;
juice_server_t *server_create(const juice_server_config_t *config);
void server_do_destroy(juice_server_t *server);
void server_destroy(juice_server_t *server);
uint16_t server_get_port(juice_server_t *server);
int server_add_credentials(juice_server_t *server, const juice_server_credentials_t *credentials,
timediff_t lifetime);
juice_credentials_list_t *server_do_add_credentials(juice_server_t *server,
const juice_server_credentials_t *credentials,
timediff_t lifetime); // internal
void server_run(juice_server_t *server);
int server_send(juice_server_t *agent, const addr_record_t *dst, const char *data, size_t size);
int server_stun_send(juice_server_t *server, const addr_record_t *dst, const stun_message_t *msg,
const char *password // password may be NULL
);
int server_recv(juice_server_t *server);
int server_forward(juice_server_t *server, server_turn_alloc_t *alloc);
int server_input(juice_server_t *agent, char *buf, size_t len, const addr_record_t *src);
int server_interrupt(juice_server_t *server);
int server_bookkeeping(juice_server_t *agent, timestamp_t *next_timestamp);
void server_get_nonce(juice_server_t *server, const addr_record_t *src, char *nonce);
void server_prepare_credentials(juice_server_t *server, const addr_record_t *src,
const juice_server_credentials_t *credentials, stun_message_t *msg);
int server_dispatch_stun(juice_server_t *server, void *buf, size_t size, stun_message_t *msg,
const addr_record_t *src);
int server_answer_stun_binding(juice_server_t *server, const uint8_t *transaction_id,
const addr_record_t *src);
int server_answer_stun_error(juice_server_t *server, const uint8_t *transaction_id,
const addr_record_t *src, stun_method_t method, unsigned int code,
const juice_server_credentials_t *credentials);
int server_process_stun_binding(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src);
int server_process_turn_allocate(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src, juice_server_credentials_t *credentials);
int server_process_turn_create_permission(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src,
const juice_server_credentials_t *credentials);
int server_process_turn_channel_bind(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src,
const juice_server_credentials_t *credentials);
int server_process_turn_send(juice_server_t *server, const stun_message_t *msg,
const addr_record_t *src);
int server_process_channel_data(juice_server_t *server, char *buf, size_t len,
const addr_record_t *src);
#endif // ifndef NO_SERVER
#endif

132
thirdparty/libjuice/src/socket.h vendored Normal file
View File

@@ -0,0 +1,132 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_SOCKET_H
#define JUICE_SOCKET_H
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Windows 7
#endif
#ifndef __MSVCRT_VERSION__
#define __MSVCRT_VERSION__ 0x0601
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
//
#include <iphlpapi.h>
#include <windows.h>
#ifdef __MINGW32__
#include <sys/stat.h>
#include <sys/time.h>
#ifndef IPV6_V6ONLY
#define IPV6_V6ONLY 27
#endif
#endif
#define NO_IFADDRS
#define NO_PMTUDISC
typedef SOCKET socket_t;
typedef SOCKADDR sockaddr;
typedef ULONG ctl_t;
typedef DWORD sockopt_t;
#define sockerrno ((int)WSAGetLastError())
#define IP_DONTFRAG IP_DONTFRAGMENT
#define HOST_NAME_MAX 256
#define poll WSAPoll
typedef ULONG nfds_t;
#define SEADDRINUSE WSAEADDRINUSE
#define SEINTR WSAEINTR
#define SEAGAIN WSAEWOULDBLOCK
#define SEACCES WSAEACCES
#define SEWOULDBLOCK WSAEWOULDBLOCK
#define SEINPROGRESS WSAEINPROGRESS
#define SECONNREFUSED WSAECONNREFUSED
#define SECONNRESET WSAECONNRESET
#define SENETRESET WSAENETRESET
#define SEMSGSIZE WSAEMSGSIZE
#else // assume POSIX
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef __linux__
#define NO_PMTUDISC
#endif
#ifdef __ANDROID__
#define NO_IFADDRS
#else
#include <ifaddrs.h>
#endif
typedef int socket_t;
typedef int ctl_t;
typedef int sockopt_t;
#define sockerrno errno
#define INVALID_SOCKET -1
#define ioctlsocket ioctl
#define closesocket close
#define SEADDRINUSE EADDRINUSE
#define SEINTR EINTR
#define SEAGAIN EAGAIN
#define SEACCES EACCES
#define SEWOULDBLOCK EWOULDBLOCK
#define SEINPROGRESS EINPROGRESS
#define SECONNREFUSED ECONNREFUSED
#define SECONNRESET ECONNRESET
#define SENETRESET ENETRESET
#define SEMSGSIZE EMSGSIZE
#endif // _WIN32
#ifndef IN6_IS_ADDR_LOOPBACK
#define IN6_IS_ADDR_LOOPBACK(a) \
(((const uint32_t *)(a))[0] == 0 && ((const uint32_t *)(a))[1] == 0 && \
((const uint32_t *)(a))[2] == 0 && ((const uint32_t *)(a))[3] == htonl(1))
#endif
#ifndef IN6_IS_ADDR_LINKLOCAL
#define IN6_IS_ADDR_LINKLOCAL(a) \
((((const uint32_t *)(a))[0] & htonl(0xffc00000)) == htonl(0xfe800000))
#endif
#ifndef IN6_IS_ADDR_SITELOCAL
#define IN6_IS_ADDR_SITELOCAL(a) \
((((const uint32_t *)(a))[0] & htonl(0xffc00000)) == htonl(0xfec00000))
#endif
#ifndef IN6_IS_ADDR_V4MAPPED
#define IN6_IS_ADDR_V4MAPPED(a) \
((((const uint32_t *)(a))[0] == 0) && (((const uint32_t *)(a))[1] == 0) && \
(((const uint32_t *)(a))[2] == htonl(0xFFFF)))
#endif
#endif // JUICE_SOCKET_H

1236
thirdparty/libjuice/src/stun.c vendored Normal file

File diff suppressed because it is too large Load Diff

376
thirdparty/libjuice/src/stun.h vendored Normal file
View File

@@ -0,0 +1,376 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_STUN_H
#define JUICE_STUN_H
#include "juice.h"
#include "addr.h"
#include "hash.h"
#include "hmac.h"
#include <stdbool.h>
#include <stdint.h>
#pragma pack(push, 1)
/*
* STUN message header (20 bytes)
* See https://www.rfc-editor.org/rfc/rfc8489.html#section-5
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |0 0| STUN Message Type | Message Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Magic Cookie = 0x2112A442 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | Transaction ID (96 bits) |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
#define STUN_TRANSACTION_ID_SIZE 12
struct stun_header {
uint16_t type;
uint16_t length;
uint32_t magic;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
};
/*
* Format of STUN Message Type Field
*
* 0 1
* 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
* |M |M |M|M|M|C|M|M|M|C|M|M|M|M|
* |11|10|9|8|7|1|6|5|4|0|3|2|1|0|
* +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
* Request: C=b00
* Indication: C=b01
* Response: C=b10 (success)
* C=b11 (error)
*/
#define STUN_CLASS_MASK 0x0110
typedef enum stun_class {
STUN_CLASS_REQUEST = 0x0000,
STUN_CLASS_INDICATION = 0x0010,
STUN_CLASS_RESP_SUCCESS = 0x0100,
STUN_CLASS_RESP_ERROR = 0x0110
} stun_class_t;
typedef enum stun_method {
STUN_METHOD_BINDING = 0x0001,
// Methods for TURN
// See https://www.rfc-editor.org/rfc/rfc8656.html#section-17
STUN_METHOD_ALLOCATE = 0x003,
STUN_METHOD_REFRESH = 0x004,
STUN_METHOD_SEND = 0x006,
STUN_METHOD_DATA = 0x007,
STUN_METHOD_CREATE_PERMISSION = 0x008,
STUN_METHOD_CHANNEL_BIND = 0x009
} stun_method_t;
#define STUN_IS_RESPONSE(msg_class) (msg_class & 0x0100)
/*
* STUN attribute header
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type | Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Value (variable) ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_attr {
uint16_t type;
uint16_t length;
uint8_t value[];
};
typedef enum stun_attr_type {
// Comprehension-required
STUN_ATTR_MAPPED_ADDRESS = 0x0001,
STUN_ATTR_USERNAME = 0x0006,
STUN_ATTR_MESSAGE_INTEGRITY = 0x0008,
STUN_ATTR_ERROR_CODE = 0x0009,
STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000A,
STUN_ATTR_REALM = 0x0014,
STUN_ATTR_NONCE = 0x0015,
STUN_ATTR_MESSAGE_INTEGRITY_SHA256 = 0x001C,
STUN_ATTR_PASSWORD_ALGORITHM = 0x001D,
STUN_ATTR_USERHASH = 0x001E,
STUN_ATTR_XOR_MAPPED_ADDRESS = 0x0020,
STUN_ATTR_PRIORITY = 0x0024,
STUN_ATTR_USE_CANDIDATE = 0x0025,
// Comprehension-optional
STUN_ATTR_PASSWORD_ALGORITHMS = 0x8002,
STUN_ATTR_ALTERNATE_DOMAIN = 0x8003,
STUN_ATTR_SOFTWARE = 0x8022,
STUN_ATTR_ALTERNATE_SERVER = 0x8023,
STUN_ATTR_FINGERPRINT = 0x8028,
STUN_ATTR_ICE_CONTROLLED = 0x8029,
STUN_ATTR_ICE_CONTROLLING = 0x802A,
// Attributes for TURN
// See https://www.rfc-editor.org/rfc/rfc8656.html#section-18
STUN_ATTR_CHANNEL_NUMBER = 0x000C,
STUN_ATTR_LIFETIME = 0x000D,
STUN_ATTR_XOR_PEER_ADDRESS = 0x0012,
STUN_ATTR_DATA = 0x0013,
STUN_ATTR_XOR_RELAYED_ADDRESS = 0x0016,
STUN_ATTR_EVEN_PORT = 0x0018,
STUN_ATTR_REQUESTED_TRANSPORT = 0x0019,
STUN_ATTR_DONT_FRAGMENT = 0x001A,
STUN_ATTR_RESERVATION_TOKEN = 0x0022
} stun_attr_type_t;
#define STUN_IS_OPTIONAL_ATTR(attr_type) (attr_type & 0x8000)
/*
* STUN attribute value for MAPPED-ADDRESS or XOR-MAPPED-ADDRESS
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |X X X X X X X X| Family | Port or X-Port |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | Address or X-Address (32 bits or 128 bits) |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_value_mapped_address {
uint8_t padding;
uint8_t family;
uint16_t port;
uint8_t address[];
};
typedef enum stun_address_family {
STUN_ADDRESS_FAMILY_IPV4 = 0x01,
STUN_ADDRESS_FAMILY_IPV6 = 0x02,
} stun_address_family_t;
/*
* STUN attribute value for ERROR-CODE
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved, should be 0 |Class| Number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reason Phrase (variable) ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_value_error_code {
uint16_t reserved;
uint8_t code_class; // lower 3 bits only, higher bits are reserved
uint8_t code_number;
uint8_t reason[];
};
#define STUN_ERROR_INTERNAL_VALIDATION_FAILED 599
/*
* STUN attribute for CHANNEL-NUMBER
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Channel Number | RFFU = 0 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_value_channel_number {
uint16_t channel_number;
uint16_t reserved;
};
/*
* STUN attribute for EVEN-PORT
*
* 0
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |R| RFFU |
* +-+-+-+-+-+-+-+-+
*/
struct stun_value_even_port {
uint8_t r;
};
/*
* STUN attribute for REQUESTED-TRANSPORT
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Protocol | RFFU |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct stun_value_requested_transport {
uint8_t protocol;
uint8_t reserved1;
uint16_t reserved2;
};
/*
* STUN attribute value for PASSWORD-ALGORITHM and PASSWORD-ALGORITHMS
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Algorithm 1 | Algorithm 1 Parameters Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Algorithm 1 Parameters (variable)
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Algorithm 2 | Algorithm 2 Parameters Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Algorithm 2 Parameters (variable)
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | ...
*/
struct stun_value_password_algorithm {
uint16_t algorithm;
uint16_t parameters_length;
uint8_t parameters[];
};
typedef enum stun_password_algorithm {
STUN_PASSWORD_ALGORITHM_UNSET = 0x0000,
STUN_PASSWORD_ALGORITHM_MD5 = 0x0001,
STUN_PASSWORD_ALGORITHM_SHA256 = 0x0002,
} stun_password_algorithm_t;
#pragma pack(pop)
// The value of USERNAME is a variable-length value. It MUST contain a UTF-8 [RFC3629] encoded
// sequence of less than 513 bytes [...]
#define STUN_MAX_USERNAME_LEN 513 + 1
// The REALM attribute [...] MUST be a UTF-8 [RFC3629] encoded sequence of less than 128 characters
// (which can be as long as 763 bytes)
#define STUN_MAX_REALM_LEN 763 + 1
// The NONCE attribute may be present in requests and responses. It [...] MUST be less than 128
// characters (which can be as long as 763 bytes)
#define STUN_MAX_NONCE_LEN 763 + 1
// The value of SOFTWARE is variable length. It MUST be a UTF-8 [RFC3629] encoded sequence of less
// than 128 characters (which can be as long as 763 bytes)
#define STUN_MAX_SOFTWARE_LEN 763 + 1
// The reason phrase MUST be a UTF-8-encoded [RFC3629] sequence of fewer than 128 characters (which
// can be as long as 509 bytes when encoding them or 763 bytes when decoding them).
#define STUN_MAX_ERROR_REASON_LEN 763 + 1
#define STUN_MAX_PASSWORD_LEN STUN_MAX_USERNAME_LEN
// Nonce cookie prefix as specified in https://www.rfc-editor.org/rfc/rfc8489.html#section-9.2
#define STUN_NONCE_COOKIE "obMatJos2"
#define STUN_NONCE_COOKIE_LEN 9
// USERHASH is a SHA256 digest
#define USERHASH_SIZE HASH_SHA256_SIZE
// STUN Security Feature bits as defined in https://www.rfc-editor.org/rfc/rfc8489.html#section-18.1
// See errata about bit order: https://www.rfc-editor.org/errata_search.php?rfc=8489
// Bits are assigned starting from the least significant side of the bit set, so Bit 0 is the rightmost bit, and Bit 23 is the leftmost bit.
// Bit 0: Password algorithms
// Bit 1: Username anonymity
// Bit 2-23: Unassigned
#define STUN_SECURITY_PASSWORD_ALGORITHMS_BIT 0x01
#define STUN_SECURITY_USERNAME_ANONYMITY_BIT 0x02
#define STUN_MAX_PASSWORD_ALGORITHMS_VALUE_SIZE 256
typedef struct stun_credentials {
char username[STUN_MAX_USERNAME_LEN];
char realm[STUN_MAX_REALM_LEN];
char nonce[STUN_MAX_NONCE_LEN];
uint8_t userhash[USERHASH_SIZE];
bool enable_userhash;
stun_password_algorithm_t password_algorithm;
uint8_t password_algorithms_value[STUN_MAX_PASSWORD_ALGORITHMS_VALUE_SIZE];
size_t password_algorithms_value_size;
} stun_credentials_t;
typedef struct stun_message {
stun_class_t msg_class;
stun_method_t msg_method;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
unsigned int error_code;
uint32_t priority;
uint64_t ice_controlling;
uint64_t ice_controlled;
bool use_candidate;
addr_record_t mapped;
stun_credentials_t credentials;
// Only for reading
bool has_integrity;
bool has_fingerprint;
// TURN
addr_record_t peer;
addr_record_t relayed;
addr_record_t alternate_server;
const char *data;
size_t data_size;
uint32_t lifetime;
uint16_t channel_number;
bool lifetime_set;
bool even_port;
bool next_port;
bool dont_fragment;
bool requested_transport;
uint64_t reservation_token;
} stun_message_t;
int stun_write(void *buf, size_t size, const stun_message_t *msg,
const char *password); // password may be NULL
int stun_write_header(void *buf, size_t size, stun_class_t class, stun_method_t method,
const uint8_t *transaction_id);
size_t stun_update_header_length(void *buf, size_t length);
int stun_write_attr(void *buf, size_t size, uint16_t type, const void *value, size_t length);
int stun_write_value_mapped_address(void *buf, size_t size, const struct sockaddr *addr,
socklen_t addrlen, const uint8_t *mask);
bool is_stun_datagram(const void *data, size_t size);
int stun_read(void *data, size_t size, stun_message_t *msg);
int stun_read_attr(const void *data, size_t size, stun_message_t *msg, uint8_t *begin,
uint8_t *attr_begin, uint32_t *security_bits);
int stun_read_value_mapped_address(const void *data, size_t size, addr_record_t *mapped,
const uint8_t *mask);
bool stun_check_integrity(void *buf, size_t size, const stun_message_t *msg, const char *password);
void stun_compute_userhash(const char *username, const char *realm, uint8_t *out);
void stun_prepend_nonce_cookie(char *nonce);
void stun_process_credentials(const stun_credentials_t *credentials, stun_credentials_t *dst);
const char *stun_get_error_reason(unsigned int code);
// Export for tests
JUICE_EXPORT bool _juice_is_stun_datagram(const void *data, size_t size);
JUICE_EXPORT int _juice_stun_read(void *data, size_t size, stun_message_t *msg);
JUICE_EXPORT bool _juice_stun_check_integrity(void *buf, size_t size, const stun_message_t *msg,
const char *password);
#endif

116
thirdparty/libjuice/src/thread.h vendored Normal file
View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_THREAD_H
#define JUICE_THREAD_H
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Windows 7
#endif
#ifndef __MSVCRT_VERSION__
#define __MSVCRT_VERSION__ 0x0601
#endif
#include <windows.h>
typedef HANDLE mutex_t;
typedef HANDLE thread_t;
typedef DWORD thread_return_t;
#define THREAD_CALL __stdcall
#define MUTEX_INITIALIZER NULL
#define MUTEX_PLAIN 0x0
#define MUTEX_RECURSIVE 0x0 // mutexes are recursive on Windows
static inline int mutex_init_impl(mutex_t *m) {
return ((*(m) = CreateMutex(NULL, FALSE, NULL)) != NULL ? 0 : (int)GetLastError());
}
static inline int mutex_lock_impl(volatile mutex_t *m) {
// Atomically initialize the mutex on first lock
if (*(m) == NULL) {
HANDLE cm = CreateMutex(NULL, FALSE, NULL);
if (cm == NULL)
return (int)GetLastError();
if (InterlockedCompareExchangePointer(m, cm, NULL) != NULL)
CloseHandle(cm);
}
return WaitForSingleObject(*m, INFINITE) != WAIT_FAILED ? 0 : (int)GetLastError();
}
#define mutex_init(m, flags) mutex_init_impl(m)
#define mutex_lock(m) mutex_lock_impl(m)
#define mutex_unlock(m) (void)ReleaseMutex(*(m))
#define mutex_destroy(m) (void)CloseHandle(*(m))
static inline void thread_join_impl(thread_t t, thread_return_t *res) {
WaitForSingleObject(t, INFINITE);
if (res)
GetExitCodeThread(t, res);
CloseHandle(t);
}
#define thread_init(t, func, arg) \
((*(t) = CreateThread(NULL, 0, func, arg, 0, NULL)) != NULL ? 0 : (int)GetLastError())
#define thread_join(t, res) thread_join_impl(t, res)
#else // POSIX
#include <pthread.h>
typedef pthread_mutex_t mutex_t;
typedef pthread_t thread_t;
typedef void *thread_return_t;
#define THREAD_CALL
#define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER
#define MUTEX_PLAIN PTHREAD_MUTEX_NORMAL
#define MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE
static inline int mutex_init_impl(mutex_t *m, int flags) {
pthread_mutexattr_t mutexattr;
pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_settype(&mutexattr, flags);
int ret = pthread_mutex_init(m, &mutexattr);
pthread_mutexattr_destroy(&mutexattr);
return ret;
}
#define mutex_init(m, flags) mutex_init_impl(m, flags)
#define mutex_lock(m) pthread_mutex_lock(m)
#define mutex_unlock(m) (void)pthread_mutex_unlock(m)
#define mutex_destroy(m) (void)pthread_mutex_destroy(m)
#define thread_init(t, func, arg) pthread_create(t, NULL, func, arg)
#define thread_join(t, res) (void)pthread_join(t, res)
#endif // ifdef _WIN32
#if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
#include <stdatomic.h>
#define atomic(T) _Atomic(T)
#define atomic_ptr(T) _Atomic(T*)
#else // no atomics
// Since we don't need compare-and-swap, just assume store and load are atomic
#define atomic(T) volatile T
#define atomic_ptr(T) T* volatile
#define atomic_store(a, v) (void)(*(a) = (v))
#define atomic_load(a) (*(a))
#define ATOMIC_VAR_INIT(v) (v)
#endif // if atomics
#endif // JUICE_THREAD_H

45
thirdparty/libjuice/src/timestamp.c vendored Normal file
View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "timestamp.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <time.h>
// clock_gettime() is not implemented on older versions of OS X (< 10.12)
#if defined(__APPLE__) && !defined(CLOCK_MONOTONIC)
#include <sys/time.h>
#define CLOCK_MONOTONIC 0
int clock_gettime(int clk_id, struct timespec *t) {
(void)clk_id;
// gettimeofday() does not return monotonic time but it should be good enough.
struct timeval now;
if (gettimeofday(&now, NULL))
return -1;
t->tv_sec = now.tv_sec;
t->tv_nsec = now.tv_usec * 1000;
return 0;
}
#endif // defined(__APPLE__) && !defined(CLOCK_MONOTONIC)
#endif
timestamp_t current_timestamp() {
#ifdef _WIN32
return (timestamp_t)GetTickCount();
#else // POSIX
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
return 0;
return (timestamp_t)ts.tv_sec * 1000 + (timestamp_t)ts.tv_nsec / 1000000;
#endif
}

20
thirdparty/libjuice/src/timestamp.h vendored Normal file
View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_TIMESTAMP_H
#define JUICE_TIMESTAMP_H
#include <stdint.h>
#include <stdlib.h>
typedef int64_t timestamp_t;
typedef timestamp_t timediff_t;
timestamp_t current_timestamp();
#endif

495
thirdparty/libjuice/src/turn.c vendored Normal file
View File

@@ -0,0 +1,495 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "turn.h"
#include "log.h"
#include "random.h"
#include "socket.h"
#include <string.h>
static bool memory_is_zero(const void *data, size_t size) {
const char *d = data;
for (size_t i = 0; i < size; ++i)
if (d[i])
return false;
return true;
}
static uint16_t random_channel_number() {
/*
* RFC 8656 12. Channels
* The ChannelData message (see Section 12.4) starts with a two-byte
* field that carries the channel number. The values of this field are
* allocated as follows:
*
* +------------------------+--------------------------------------+
* | 0x0000 through 0x3FFF: | These values can never be used for |
* | | channel numbers. |
* +------------------------+--------------------------------------+
* | 0x4000 through 0x4FFF: | These values are the allowed channel |
* | | numbers (4096 possible values). |
* +------------------------+--------------------------------------+
* | 0x5000 through 0xFFFF: | Reserved (For DTLS-SRTP multiplexing |
* | | collision avoidance, see [RFC7983]). |
* +------------------------+--------------------------------------+
*/
uint16_t r;
juice_random(&r, 2);
return 0x4000 | (r & 0x0FFF);
}
bool is_channel_data(const void *data, size_t size) {
// According RFC 8656, first byte in [64..79] is TURN Channel
if (size == 0)
return false;
uint8_t b = *((const uint8_t *)data);
return b >= 64 && b <= 79;
}
bool is_valid_channel(uint16_t channel) { return channel >= 0x4000; }
int turn_wrap_channel_data(char *buffer, size_t size, const char *data, size_t data_size,
uint16_t channel) {
if (!is_valid_channel(channel)) {
JLOG_WARN("Invalid channel number: 0x%hX", channel);
return -1;
}
if (data_size >= 65536) {
JLOG_WARN("ChannelData is too long, size=%zu", size);
return -1;
}
if (size < sizeof(struct channel_data_header) + data_size) {
JLOG_WARN("Buffer is too small to add ChannelData header, size=%zu, needed=%zu", size,
sizeof(struct channel_data_header) + data_size);
return -1;
}
memmove(buffer + sizeof(struct channel_data_header), data, data_size);
struct channel_data_header *header = (struct channel_data_header *)buffer;
header->channel_number = htons((uint16_t)channel);
header->length = htons((uint16_t)data_size);
return (int)(sizeof(struct channel_data_header) + data_size);
}
static int find_ordered_channel_rec(turn_entry_t *const ordered_channels[], uint16_t channel,
int begin, int end) {
int d = end - begin;
if (d <= 0)
return begin;
int pivot = begin + d / 2;
const turn_entry_t *entry = ordered_channels[pivot];
if (channel < entry->channel)
return find_ordered_channel_rec(ordered_channels, channel, begin, pivot);
else if (channel > entry->channel)
return find_ordered_channel_rec(ordered_channels, channel, pivot + 1, end);
else
return pivot;
}
static int find_ordered_channel(const turn_map_t *map, uint16_t channel) {
return find_ordered_channel_rec(map->ordered_channels, channel, 0, map->channels_count);
}
static int find_ordered_transaction_id_rec(turn_entry_t *const ordered_transaction_ids[],
const uint8_t *transaction_id, int begin, int end) {
int d = end - begin;
if (d <= 0)
return begin;
int pivot = begin + d / 2;
const turn_entry_t *entry = ordered_transaction_ids[pivot];
int ret = memcmp(transaction_id, entry->transaction_id, STUN_TRANSACTION_ID_SIZE);
if (ret < 0)
return find_ordered_transaction_id_rec(ordered_transaction_ids, transaction_id, begin,
pivot);
else if (ret > 0)
return find_ordered_transaction_id_rec(ordered_transaction_ids, transaction_id, pivot + 1,
end);
else
return pivot;
}
static int find_ordered_transaction_id(const turn_map_t *map, const uint8_t *transaction_id) {
return find_ordered_transaction_id_rec(map->ordered_transaction_ids, transaction_id, 0,
map->transaction_ids_count);
}
static void remove_ordered_transaction_id(turn_map_t *map, const uint8_t *transaction_id) {
int pos = find_ordered_transaction_id(map, transaction_id);
if (pos < map->transaction_ids_count) {
memmove(map->ordered_transaction_ids + pos, map->ordered_transaction_ids + pos + 1,
(map->transaction_ids_count - (pos + 1)) * sizeof(turn_entry_t *));
map->transaction_ids_count--;
}
}
/*
static void remove_ordered_channel(turn_map_t *map, uint16_t channel) {
int pos = find_ordered_channel(map, channel);
if (pos < map->channels_count) {
memmove(map->ordered_channels + pos, map->ordered_channels + pos + 1,
(map->channels_count - (pos + 1)) * sizeof(turn_entry_t *));
map->channels_count--;
}
}
static void delete_entry(turn_map_t *map, turn_entry_t *entry) {
if (entry->type == TURN_ENTRY_TYPE_EMPTY || entry->type == TURN_ENTRY_TYPE_DELETED)
return;
if (!memory_is_zero(entry->transaction_id, STUN_TRANSACTION_ID_SIZE))
remove_ordered_transaction_id(map, entry->transaction_id);
if (entry->type == TURN_ENTRY_TYPE_CHANNEL && entry->channel)
remove_ordered_channel(map, entry->channel);
memset(entry, 0, sizeof(*entry));
entry->type = TURN_ENTRY_TYPE_DELETED;
}
*/
static turn_entry_t *find_entry(turn_map_t *map, const addr_record_t *record,
turn_entry_type_t type, bool allow_deleted) {
unsigned long key = (addr_record_hash(record, false) + (int)type) % map->map_size;
unsigned long pos = key;
while (true) {
turn_entry_t *entry = map->map + pos;
if (entry->type == TURN_ENTRY_TYPE_EMPTY ||
(entry->type == type && addr_record_is_equal(&entry->record, record, false)))
break;
if (allow_deleted && entry->type == TURN_ENTRY_TYPE_DELETED)
break;
pos = (pos + 1) % map->map_size;
if (pos == key) {
JLOG_VERBOSE("TURN map is full");
return NULL;
}
}
return map->map + pos;
}
static bool update_timestamp(turn_map_t *map, turn_entry_type_t type, const uint8_t *transaction_id,
const addr_record_t *record, timediff_t duration) {
turn_entry_t *entry;
if (record) {
entry = find_entry(map, record, type, true);
if (!entry)
return false;
if (entry->type == type) {
if (memcmp(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE) == 0)
return true;
} else {
entry->type = type;
entry->record = *record;
}
if (!memory_is_zero(entry->transaction_id, STUN_TRANSACTION_ID_SIZE))
remove_ordered_transaction_id(map, entry->transaction_id);
memcpy(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE);
} else {
int pos = find_ordered_transaction_id(map, transaction_id);
if (pos == map->transaction_ids_count)
return false;
entry = map->ordered_transaction_ids[pos];
if (entry->type != type ||
memcmp(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE) != 0)
return false;
}
entry->timestamp = current_timestamp() + duration;
entry->fresh_transaction_id = false;
return true;
}
int turn_init_map(turn_map_t *map, int size) {
memset(map, 0, sizeof(*map));
map->map_size = size * 2;
map->channels_count = 0;
map->transaction_ids_count = 0;
map->map = calloc(map->map_size, sizeof(turn_entry_t));
map->ordered_channels = calloc(map->map_size, sizeof(turn_entry_t *));
map->ordered_transaction_ids = calloc(map->map_size, sizeof(turn_entry_t *));
if (!map->map || !map->ordered_channels || !map->ordered_transaction_ids) {
JLOG_ERROR("Failed to allocate TURN map of size %d", size);
turn_destroy_map(map);
return -1;
}
return 0;
}
void turn_destroy_map(turn_map_t *map) {
free(map->map);
free(map->ordered_channels);
free(map->ordered_transaction_ids);
}
bool turn_set_permission(turn_map_t *map, const uint8_t *transaction_id,
const addr_record_t *record, timediff_t duration) {
return update_timestamp(map, TURN_ENTRY_TYPE_PERMISSION, transaction_id, record, duration);
}
bool turn_has_permission(turn_map_t *map, const addr_record_t *record) {
turn_entry_t *entry = find_entry(map, record, TURN_ENTRY_TYPE_PERMISSION, false);
if (!entry || entry->type != TURN_ENTRY_TYPE_PERMISSION)
return false;
return current_timestamp() < entry->timestamp;
}
bool turn_bind_channel(turn_map_t *map, const addr_record_t *record, const uint8_t *transaction_id,
uint16_t channel, timediff_t duration) {
if (!is_valid_channel(channel)) {
JLOG_ERROR("Invalid channel number: 0x%hX", channel);
return false;
}
turn_entry_t *entry = find_entry(map, record, TURN_ENTRY_TYPE_CHANNEL, true);
if (!entry)
return false;
if (entry->type == TURN_ENTRY_TYPE_CHANNEL && entry->channel) {
if (entry->channel != channel) {
JLOG_WARN("The record is already bound to a channel");
return false;
}
entry->timestamp = current_timestamp() + duration;
return true;
}
int pos = find_ordered_channel(map, channel);
if (pos < map->channels_count) {
const turn_entry_t *other_entry = map->ordered_channels[pos];
if (other_entry->channel == channel) {
JLOG_WARN("The channel is already bound to a record");
return false;
}
}
if (entry->type != TURN_ENTRY_TYPE_CHANNEL) {
entry->type = TURN_ENTRY_TYPE_CHANNEL;
entry->record = *record;
}
memmove(map->ordered_channels + pos + 1, map->ordered_channels + pos,
(map->channels_count - pos) * sizeof(turn_entry_t *));
map->ordered_channels[pos] = entry;
map->channels_count++;
entry->channel = channel;
entry->timestamp = current_timestamp() + duration;
if (transaction_id) {
memcpy(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE);
entry->fresh_transaction_id = true;
}
return true;
}
bool turn_bind_random_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel,
timediff_t duration) {
uint16_t c;
do {
c = random_channel_number();
} while (turn_find_channel(map, c, NULL));
if (!turn_bind_channel(map, record, NULL, c, duration))
return false;
if (channel)
*channel = c;
return true;
}
bool turn_bind_current_channel(turn_map_t *map, const uint8_t *transaction_id,
const addr_record_t *record, timediff_t duration) {
return update_timestamp(map, TURN_ENTRY_TYPE_CHANNEL, transaction_id, record, duration);
}
bool turn_get_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel) {
turn_entry_t *entry = find_entry(map, record, TURN_ENTRY_TYPE_CHANNEL, false);
if (!entry || entry->type != TURN_ENTRY_TYPE_CHANNEL)
return false;
if (channel)
*channel = entry->channel;
return true;
}
bool turn_get_bound_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel) {
turn_entry_t *entry = find_entry(map, record, TURN_ENTRY_TYPE_CHANNEL, false);
if (!entry || entry->type != TURN_ENTRY_TYPE_CHANNEL)
return false;
if (!entry->channel || current_timestamp() >= entry->timestamp)
return false;
if (channel)
*channel = entry->channel;
return true;
}
bool turn_find_channel(turn_map_t *map, uint16_t channel, addr_record_t *record) {
if (!is_valid_channel(channel)) {
JLOG_WARN("Invalid channel number: 0x%hX", channel);
return false;
}
int pos = find_ordered_channel(map, channel);
if (pos == map->channels_count)
return false;
const turn_entry_t *entry = map->ordered_channels[pos];
if (entry->channel != channel)
return false;
if (record)
*record = entry->record;
return true;
}
bool turn_find_bound_channel(turn_map_t *map, uint16_t channel, addr_record_t *record) {
if (!is_valid_channel(channel)) {
JLOG_WARN("Invalid channel number: 0x%hX", channel);
return false;
}
int pos = find_ordered_channel(map, channel);
if (pos == map->channels_count)
return false;
const turn_entry_t *entry = map->ordered_channels[pos];
if (entry->channel != channel || current_timestamp() >= entry->timestamp)
return false;
if (record)
*record = entry->record;
return true;
}
static bool set_transaction_id(turn_map_t *map, turn_entry_type_t type, const addr_record_t *record,
const uint8_t *transaction_id) {
if (type != TURN_ENTRY_TYPE_PERMISSION && type != TURN_ENTRY_TYPE_CHANNEL)
return false;
turn_entry_t *entry = find_entry(map, record, type, true);
if (!entry)
return false;
if (entry->type == type && !memory_is_zero(entry->transaction_id, STUN_TRANSACTION_ID_SIZE))
remove_ordered_transaction_id(map, entry->transaction_id);
int pos = find_ordered_transaction_id(map, transaction_id);
memmove(map->ordered_transaction_ids + pos + 1, map->ordered_transaction_ids + pos,
(map->transaction_ids_count - pos) * sizeof(turn_entry_t *));
map->ordered_transaction_ids[pos] = entry;
map->transaction_ids_count++;
if (entry->type != type) {
entry->type = type;
entry->record = *record;
}
memcpy(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE);
entry->fresh_transaction_id = true;
return true;
}
static bool find_transaction_id(turn_map_t *map, const uint8_t *transaction_id,
addr_record_t *record) {
int pos = find_ordered_transaction_id(map, transaction_id);
if (pos == map->transaction_ids_count)
return false;
const turn_entry_t *entry = map->ordered_transaction_ids[pos];
if (memcmp(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE) != 0)
return false;
if (record)
*record = entry->record;
return true;
}
static bool set_random_transaction_id(turn_map_t *map, turn_entry_type_t type,
const addr_record_t *record, uint8_t *transaction_id) {
turn_entry_t *entry = find_entry(map, record, type, false);
if (entry && entry->fresh_transaction_id) {
if (transaction_id)
memcpy(transaction_id, entry->transaction_id, STUN_TRANSACTION_ID_SIZE);
return true;
}
uint8_t tid[STUN_TRANSACTION_ID_SIZE];
do {
juice_random(tid, STUN_TRANSACTION_ID_SIZE);
} while (find_transaction_id(map, tid, NULL));
if (!set_transaction_id(map, type, record, tid))
return false;
if (transaction_id)
memcpy(transaction_id, tid, STUN_TRANSACTION_ID_SIZE);
return true;
}
bool turn_set_permission_transaction_id(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id) {
return set_transaction_id(map, TURN_ENTRY_TYPE_PERMISSION, record, transaction_id);
}
bool turn_set_channel_transaction_id(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id) {
return set_transaction_id(map, TURN_ENTRY_TYPE_CHANNEL, record, transaction_id);
}
bool turn_set_random_permission_transaction_id(turn_map_t *map, const addr_record_t *record,
uint8_t *transaction_id) {
return set_random_transaction_id(map, TURN_ENTRY_TYPE_PERMISSION, record, transaction_id);
}
bool turn_set_random_channel_transaction_id(turn_map_t *map, const addr_record_t *record,
uint8_t *transaction_id) {
return set_random_transaction_id(map, TURN_ENTRY_TYPE_CHANNEL, record, transaction_id);
}
bool turn_retrieve_transaction_id(turn_map_t *map, const uint8_t *transaction_id,
addr_record_t *record) {
int pos = find_ordered_transaction_id(map, transaction_id);
if (pos == map->transaction_ids_count)
return false;
turn_entry_t *entry = map->ordered_transaction_ids[pos];
if (memcmp(entry->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE) != 0)
return false;
if (record)
*record = entry->record;
entry->fresh_transaction_id = false;
return true;
}

111
thirdparty/libjuice/src/turn.h vendored Normal file
View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_TURN_H
#define JUICE_TURN_H
#include "addr.h"
#include "ice.h"
#include "juice.h"
#include "log.h"
#include "stun.h"
#include "timestamp.h"
#include <stdint.h>
#pragma pack(push, 1)
/*
* TURN ChannelData Message
* See https://www.rfc-editor.org/rfc/rfc8656.html#section-12.4
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Channel Number | Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* / Application Data /
* / /
* | |
* | +-------------------------------+
* | |
* +-------------------------------+
*/
struct channel_data_header {
uint16_t channel_number;
uint16_t length;
};
#pragma pack(pop)
bool is_channel_data(const void *data, size_t size);
bool is_valid_channel(uint16_t channel);
int turn_wrap_channel_data(char *buffer, size_t size, const char *data, size_t data_size,
uint16_t channel);
// TURN state map
typedef enum turn_entry_type {
TURN_ENTRY_TYPE_EMPTY = 0,
TURN_ENTRY_TYPE_DELETED,
TURN_ENTRY_TYPE_PERMISSION,
TURN_ENTRY_TYPE_CHANNEL
} turn_entry_type_t;
typedef struct turn_entry {
turn_entry_type_t type;
timestamp_t timestamp;
addr_record_t record;
uint8_t transaction_id[STUN_TRANSACTION_ID_SIZE];
uint16_t channel;
bool fresh_transaction_id;
} turn_entry_t;
typedef struct turn_map {
turn_entry_t *map;
turn_entry_t **ordered_channels;
turn_entry_t **ordered_transaction_ids;
int map_size;
int channels_count;
int transaction_ids_count;
} turn_map_t;
int turn_init_map(turn_map_t *map, int size);
void turn_destroy_map(turn_map_t *map);
bool turn_set_permission(turn_map_t *map, const uint8_t *transaction_id,
const addr_record_t *record, // record may be NULL
timediff_t duration);
bool turn_has_permission(turn_map_t *map, const addr_record_t *record);
bool turn_bind_channel(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id, // transaction_id may be NULL
uint16_t channel, timediff_t duration);
bool turn_bind_random_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel,
timediff_t duration);
bool turn_bind_current_channel(turn_map_t *map, const uint8_t *transaction_id,
const addr_record_t *record, // record may be NULL
timediff_t duration);
bool turn_get_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel);
bool turn_get_bound_channel(turn_map_t *map, const addr_record_t *record, uint16_t *channel);
bool turn_find_channel(turn_map_t *map, uint16_t channel, addr_record_t *record);
bool turn_find_bound_channel(turn_map_t *map, uint16_t channel, addr_record_t *record);
bool turn_set_permission_transaction_id(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id);
bool turn_set_channel_transaction_id(turn_map_t *map, const addr_record_t *record,
const uint8_t *transaction_id);
bool turn_set_random_permission_transaction_id(turn_map_t *map, const addr_record_t *record,
uint8_t *transaction_id);
bool turn_set_random_channel_transaction_id(turn_map_t *map, const addr_record_t *record,
uint8_t *transaction_id);
bool turn_retrieve_transaction_id(turn_map_t *map, const uint8_t *transaction_id,
addr_record_t *record);
#endif

604
thirdparty/libjuice/src/udp.c vendored Normal file
View File

@@ -0,0 +1,604 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "udp.h"
#include "addr.h"
#include "log.h"
#include "random.h"
#include "thread.h" // for mutexes
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
static struct addrinfo *find_family(struct addrinfo *ai_list, int family) {
struct addrinfo *ai = ai_list;
while (ai && ai->ai_family != family)
ai = ai->ai_next;
return ai;
}
static uint16_t get_next_port_in_range(uint16_t begin, uint16_t end) {
if (begin == 0)
begin = 1024;
if (end == 0)
end = 0xFFFF;
if (begin == end)
return begin;
static volatile uint32_t count = 0;
if (count == 0)
count = juice_rand32();
static mutex_t mutex = MUTEX_INITIALIZER;
mutex_lock(&mutex);
uint32_t diff = end > begin ? end - begin : 0;
uint16_t next = begin + count++ % (diff + 1);
mutex_unlock(&mutex);
return next;
}
socket_t udp_create_socket(const udp_socket_config_t *config) {
socket_t sock = INVALID_SOCKET;
// Obtain local Address
struct addrinfo *ai_list = NULL;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
if (getaddrinfo(config->bind_address, "0", &hints, &ai_list) != 0) {
JLOG_ERROR("getaddrinfo for binding address failed, errno=%d", sockerrno);
return INVALID_SOCKET;
}
// Create socket
struct addrinfo *ai = NULL;
const int families[2] = {AF_INET6, AF_INET}; // Prefer IPv6
const char *names[2] = {"IPv6", "IPv4"};
for (int i = 0; i < 2; ++i) {
ai = find_family(ai_list, families[i]);
if (!ai)
continue;
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sock == INVALID_SOCKET) {
JLOG_WARN("UDP socket creation for %s family failed, errno=%d", names[i], sockerrno);
continue;
}
break;
}
if (sock == INVALID_SOCKET) {
JLOG_ERROR("UDP socket creation failed: no suitable address family");
goto error;
}
assert(ai != NULL);
// Listen on both IPv6 and IPv4
const sockopt_t disabled = 0;
if (ai->ai_family == AF_INET6)
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&disabled, sizeof(disabled));
// Set DF flag
#ifndef NO_PMTUDISC
const sockopt_t val = IP_PMTUDISC_DO;
setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, (const char *)&val, sizeof(val));
#ifdef IPV6_MTU_DISCOVER
if (ai->ai_family == AF_INET6)
setsockopt(sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, (const char *)&val, sizeof(val));
#endif
#else
// It seems Mac OS lacks a way to set the DF flag...
const sockopt_t enabled = 1;
#ifdef IP_DONTFRAG
setsockopt(sock, IPPROTO_IP, IP_DONTFRAG, (const char *)&enabled, sizeof(enabled));
#endif
#ifdef IPV6_DONTFRAG
if (ai->ai_family == AF_INET6)
setsockopt(sock, IPPROTO_IPV6, IPV6_DONTFRAG, (const char *)&enabled, sizeof(enabled));
#endif
#endif
// Set buffer size up to 1 MiB for performance
const sockopt_t buffer_size = 1 * 1024 * 1024;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (const char *)&buffer_size, sizeof(buffer_size));
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char *)&buffer_size, sizeof(buffer_size));
ctl_t nbio = 1;
if (ioctlsocket(sock, FIONBIO, &nbio)) {
JLOG_ERROR("Setting non-blocking mode on UDP socket failed, errno=%d", sockerrno);
goto error;
}
// Bind it
if (config->port_begin == 0 && config->port_end == 0) {
if (bind(sock, ai->ai_addr, (socklen_t)ai->ai_addrlen) == 0) {
JLOG_DEBUG("UDP socket bound to %s:%hu",
config->bind_address ? config->bind_address : "any", udp_get_port(sock));
freeaddrinfo(ai_list);
return sock;
}
JLOG_ERROR("UDP socket binding failed, errno=%d", sockerrno);
} else if (config->port_begin == config->port_end) {
uint16_t port = config->port_begin;
struct sockaddr_storage addr;
socklen_t addrlen = (socklen_t)ai->ai_addrlen;
memcpy(&addr, ai->ai_addr, addrlen);
addr_set_port((struct sockaddr *)&addr, port);
if (bind(sock, (struct sockaddr *)&addr, addrlen) == 0) {
JLOG_DEBUG("UDP socket bound to %s:%hu",
config->bind_address ? config->bind_address : "any", port);
freeaddrinfo(ai_list);
return sock;
}
JLOG_ERROR("UDP socket binding failed on port %hu, errno=%d", port, sockerrno);
} else {
struct sockaddr_storage addr;
socklen_t addrlen = (socklen_t)ai->ai_addrlen;
memcpy(&addr, ai->ai_addr, addrlen);
int retries = config->port_end - config->port_begin;
do {
uint16_t port = get_next_port_in_range(config->port_begin, config->port_end);
addr_set_port((struct sockaddr *)&addr, port);
if (bind(sock, (struct sockaddr *)&addr, addrlen) == 0) {
JLOG_DEBUG("UDP socket bound to %s:%hu",
config->bind_address ? config->bind_address : "any", port);
freeaddrinfo(ai_list);
return sock;
}
} while ((sockerrno == SEADDRINUSE || sockerrno == SEACCES) && retries-- > 0);
JLOG_ERROR("UDP socket binding failed on port range %s:[%hu,%hu], errno=%d",
config->bind_address ? config->bind_address : "any", config->port_begin,
config->port_end, sockerrno);
}
error:
freeaddrinfo(ai_list);
if (sock != INVALID_SOCKET)
closesocket(sock);
return INVALID_SOCKET;
}
int udp_recvfrom(socket_t sock, char *buffer, size_t size, addr_record_t *src) {
while (true) {
src->len = sizeof(src->addr);
int len =
recvfrom(sock, buffer, (socklen_t)size, 0, (struct sockaddr *)&src->addr, &src->len);
if (len >= 0) {
addr_unmap_inet6_v4mapped((struct sockaddr *)&src->addr, &src->len);
} else if (sockerrno == SECONNRESET || sockerrno == SENETRESET ||
sockerrno == SECONNREFUSED) {
// On Windows, if a UDP socket receives an ICMP port unreachable response after
// sending a datagram, this error is stored, and the next call to recvfrom() returns
// WSAECONNRESET (port unreachable) or WSAENETRESET (TTL expired).
// Therefore, it may be ignored.
JLOG_DEBUG("Ignoring %s returned by recvfrom",
sockerrno == SECONNRESET
? "ECONNRESET"
: (sockerrno == SENETRESET ? "ENETRESET" : "ECONNREFUSED"));
continue;
}
return len;
}
}
int udp_sendto(socket_t sock, const char *data, size_t size, const addr_record_t *dst) {
#ifndef __linux__
addr_record_t tmp = *dst;
addr_record_t name;
name.len = sizeof(name.addr);
if (getsockname(sock, (struct sockaddr *)&name.addr, &name.len) == 0) {
if (name.addr.ss_family == AF_INET6)
addr_map_inet6_v4mapped(&tmp.addr, &tmp.len);
} else {
JLOG_WARN("getsockname failed, errno=%d", sockerrno);
}
return sendto(sock, data, (socklen_t)size, 0, (const struct sockaddr *)&tmp.addr, tmp.len);
#else
return sendto(sock, data, size, 0, (const struct sockaddr *)&dst->addr, dst->len);
#endif
}
int udp_sendto_self(socket_t sock, const char *data, size_t size) {
addr_record_t local;
if (udp_get_local_addr(sock, AF_UNSPEC, &local) < 0)
return -1;
int ret;
#ifndef __linux__
// We know local has the same address family as sock here
ret = sendto(sock, data, (socklen_t)size, 0, (const struct sockaddr *)&local.addr, local.len);
#else
ret = sendto(sock, data, size, 0, (const struct sockaddr *)&local.addr, local.len);
#endif
if (ret >= 0 || local.addr.ss_family != AF_INET6)
return ret;
// Fallback as IPv6 may be disabled on the loopback interface
if (udp_get_local_addr(sock, AF_INET, &local) < 0)
return -1;
#ifndef __linux__
addr_map_inet6_v4mapped(&local.addr, &local.len);
return sendto(sock, data, (socklen_t)size, 0, (const struct sockaddr *)&local.addr, local.len);
#else
return sendto(sock, data, size, 0, (const struct sockaddr *)&local.addr, local.len);
#endif
}
int udp_set_diffserv(socket_t sock, int ds) {
#ifdef _WIN32
// IP_TOS has been intentionally broken on Windows in favor of a convoluted proprietary
// mechanism called qWave. Thank you Microsoft!
// TODO: Investigate if DSCP can be still set directly without administrator flow configuration.
(void)sock;
(void)ds;
JLOG_INFO("IP Differentiated Services are not supported on Windows");
return -1;
#else
addr_record_t name;
name.len = sizeof(name.addr);
if (getsockname(sock, (struct sockaddr *)&name.addr, &name.len) < 0) {
JLOG_WARN("getsockname failed, errno=%d", sockerrno);
return -1;
}
switch (name.addr.ss_family) {
case AF_INET:
#ifdef IP_TOS
if (setsockopt(sock, IPPROTO_IP, IP_TOS, &ds, sizeof(ds)) < 0) {
JLOG_WARN("Setting IP ToS failed, errno=%d", sockerrno);
return -1;
}
return 0;
#else
JLOG_INFO("Setting IP ToS is not supported");
return -1;
#endif
case AF_INET6:
#ifdef IPV6_TCLASS
if (setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, &ds, sizeof(ds)) < 0) {
JLOG_WARN("Setting IPv6 traffic class failed, errno=%d", sockerrno);
return -1;
}
#ifdef IP_TOS
// Attempt to also set IP_TOS for IPv4, in case the system requires it
setsockopt(sock, IPPROTO_IP, IP_TOS, &ds, sizeof(ds));
#endif
return 0;
#else
JLOG_INFO("Setting IPv6 traffic class is not supported");
return -1;
#endif
default:
return -1;
}
#endif
}
uint16_t udp_get_port(socket_t sock) {
addr_record_t record;
if (udp_get_bound_addr(sock, &record) < 0)
return 0;
return addr_get_port((struct sockaddr *)&record.addr);
}
int udp_get_bound_addr(socket_t sock, addr_record_t *record) {
record->len = sizeof(record->addr);
if (getsockname(sock, (struct sockaddr *)&record->addr, &record->len)) {
JLOG_WARN("getsockname failed, errno=%d", sockerrno);
return -1;
}
return 0;
}
int udp_get_local_addr(socket_t sock, int family_hint, addr_record_t *record) {
if (udp_get_bound_addr(sock, record) < 0)
return -1;
// If the socket is bound to a particular address, return it
if (!addr_is_any((struct sockaddr *)&record->addr)) {
if (record->addr.ss_family == AF_INET && family_hint == AF_INET6)
addr_map_inet6_v4mapped(&record->addr, &record->len);
return 0;
}
if (record->addr.ss_family == AF_INET6 && family_hint == AF_INET) {
// Generate an IPv4 instead (socket is listening to any IPv4 or IPv6)
uint16_t port = addr_get_port((struct sockaddr *)&record->addr);
if (port == 0)
return -1;
struct sockaddr_in *sin = (struct sockaddr_in *)&record->addr;
memset(sin, 0, sizeof(*sin));
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
record->len = sizeof(*sin);
}
switch (record->addr.ss_family) {
case AF_INET: {
struct sockaddr_in *sin = (struct sockaddr_in *)&record->addr;
const uint8_t localhost[4] = {127, 0, 0, 1};
memcpy(&sin->sin_addr, localhost, 4);
break;
}
case AF_INET6: {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&record->addr;
uint8_t *b = (uint8_t *)&sin6->sin6_addr;
memset(b, 0, 15);
b[15] = 0x01; // localhost
break;
}
default:
// Ignore
break;
}
if (record->addr.ss_family == AF_INET && family_hint == AF_INET6)
addr_map_inet6_v4mapped(&record->addr, &record->len);
return 0;
}
// Helper function to check if a similar address already exists in records
// This function ignores the port
static int has_duplicate_addr(struct sockaddr *addr, const addr_record_t *records, size_t count) {
for (size_t i = 0; i < count; ++i) {
const addr_record_t *record = records + i;
if (record->addr.ss_family == addr->sa_family) {
switch (addr->sa_family) {
case AF_INET: {
// For IPv4, compare the whole address
const struct sockaddr_in *rsin = (const struct sockaddr_in *)&record->addr;
const struct sockaddr_in *asin = (const struct sockaddr_in *)addr;
if (memcmp(&rsin->sin_addr, &asin->sin_addr, 4) == 0)
return true;
break;
}
case AF_INET6: {
// For IPv6, compare the network part only
const struct sockaddr_in6 *rsin6 = (const struct sockaddr_in6 *)&record->addr;
const struct sockaddr_in6 *asin6 = (const struct sockaddr_in6 *)addr;
if (memcmp(&rsin6->sin6_addr, &asin6->sin6_addr, 8) == 0) // compare first 64 bits
return true;
break;
}
}
}
}
return false;
}
#if !defined(_WIN32) && defined(NO_IFADDRS)
// Helper function to get the IPv6 address of the default interface
static int get_local_default_inet6(uint16_t port, struct sockaddr_in6 *result) {
const char *dummy_host = "2001:db8::1"; // dummy public unreachable address
const uint16_t dummy_port = 9; // discard port
struct sockaddr_in6 sin6;
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_port = htons(dummy_port);
if (inet_pton(AF_INET6, dummy_host, &sin6.sin6_addr) != 1)
return -1;
socket_t sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET)
return -1;
if (connect(sock, (const struct sockaddr *)&sin6, sizeof(sin6)))
goto error;
socklen_t result_len = sizeof(*result);
if (getsockname(sock, (struct sockaddr *)result, &result_len))
goto error;
if (result_len != sizeof(*result))
goto error;
addr_set_port((struct sockaddr *)result, port);
closesocket(sock);
return 0;
error:
closesocket(sock);
return -1;
}
#endif
int udp_get_addrs(socket_t sock, addr_record_t *records, size_t count) {
addr_record_t bound;
if (udp_get_bound_addr(sock, &bound) < 0) {
JLOG_ERROR("Getting UDP bound address failed");
return -1;
}
if (!addr_is_any((struct sockaddr *)&bound.addr)) {
if (count > 0)
records[0] = bound;
return 1;
}
uint16_t port = addr_get_port((struct sockaddr *)&bound.addr);
// RFC 8445 5.1.1.1. Host Candidates:
// Addresses from a loopback interface MUST NOT be included in the candidate addresses.
// [...]
// If gathering one or more host candidates that correspond to an IPv6 address that was
// generated using a mechanism that prevents location tracking [RFC7721], host candidates
// that correspond to IPv6 addresses that do allow location tracking, are configured on the
// same interface, and are part of the same network prefix MUST NOT be gathered. Similarly,
// when host candidates corresponding to an IPv6 address generated using a mechanism that
// prevents location tracking are gathered, then host candidates corresponding to IPv6
// link-local addresses [RFC4291] MUST NOT be gathered. The IPv6 default address selection
// specification [RFC6724] specifies that temporary addresses [RFC4941] are to be preferred
// over permanent addresses.
// IPv6 IIDs generated by modern systems are opaque so there is no way to reliably differentiate
// privacy-enabled IPv6 addresses here. Therefore, we hope the preferred addresses are listed
// first, and we never list link-local addresses.
addr_record_t *current = records;
addr_record_t *end = records + count;
int ret = 0;
#if JUICE_ENABLE_LOCALHOST_ADDRESS
// Add localhost for test purposes
addr_record_t local;
if (bound.addr.ss_family == AF_INET6 && udp_get_local_addr(sock, AF_INET6, &local) == 0) {
++ret;
if (current != end) {
*current = local;
++current;
}
}
if (udp_get_local_addr(sock, AF_INET, &local) == 0) {
++ret;
if (current != end) {
*current = local;
++current;
}
}
#endif
#ifdef _WIN32
char buf[4096];
DWORD len = 0;
if (WSAIoctl(sock, SIO_ADDRESS_LIST_QUERY, NULL, 0, buf, sizeof(buf), &len, NULL, NULL)) {
JLOG_ERROR("WSAIoctl with SIO_ADDRESS_LIST_QUERY failed, errno=%d", WSAGetLastError());
return -1;
}
SOCKET_ADDRESS_LIST *list = (SOCKET_ADDRESS_LIST *)buf;
for (int i = 0; i < list->iAddressCount; ++i) {
struct sockaddr *sa = list->Address[i].lpSockaddr;
socklen_t len = list->Address[i].iSockaddrLength;
if ((sa->sa_family == AF_INET ||
(sa->sa_family == AF_INET6 && bound.addr.ss_family == AF_INET6)) &&
!addr_is_local(sa)) {
if (!has_duplicate_addr(sa, records, current - records)) {
++ret;
if (current != end) {
memcpy(&current->addr, sa, len);
current->len = len;
addr_unmap_inet6_v4mapped((struct sockaddr *)&current->addr, &current->len);
addr_set_port((struct sockaddr *)&current->addr, port);
++current;
}
}
}
}
#else // POSIX
#ifndef NO_IFADDRS
struct ifaddrs *ifas;
if (getifaddrs(&ifas)) {
JLOG_ERROR("getifaddrs failed, errno=%d", sockerrno);
return -1;
}
for (struct ifaddrs *ifa = ifas; ifa; ifa = ifa->ifa_next) {
unsigned int flags = ifa->ifa_flags;
if (!(flags & IFF_UP) || (flags & IFF_LOOPBACK))
continue;
if (strcmp(ifa->ifa_name, "docker0") == 0)
continue;
struct sockaddr *sa = ifa->ifa_addr;
socklen_t len;
if (sa &&
(sa->sa_family == AF_INET ||
(sa->sa_family == AF_INET6 && bound.addr.ss_family == AF_INET6)) &&
!addr_is_local(sa) && (len = addr_get_len(sa)) > 0) {
if (!has_duplicate_addr(sa, records, current - records)) {
++ret;
if (current != end) {
memcpy(&current->addr, sa, len);
current->len = len;
addr_set_port((struct sockaddr *)&current->addr, port);
++current;
}
}
}
}
freeifaddrs(ifas);
#else // NO_IFADDRS defined
char buf[4096];
struct ifconf ifc;
memset(&ifc, 0, sizeof(ifc));
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (ioctlsocket(sock, SIOCGIFCONF, &ifc)) {
JLOG_ERROR("ioctl for SIOCGIFCONF failed, errno=%d", sockerrno);
return -1;
}
bool ifconf_has_inet6 = false;
int n = ifc.ifc_len / sizeof(struct ifreq);
for (int i = 0; i < n; ++i) {
struct ifreq *ifr = ifc.ifc_req + i;
struct sockaddr *sa = &ifr->ifr_addr;
if (sa->sa_family == AF_INET6)
ifconf_has_inet6 = true;
socklen_t len;
if ((sa->sa_family == AF_INET ||
(sa->sa_family == AF_INET6 && bound.addr.ss_family == AF_INET6)) &&
!addr_is_local(sa) && (len = addr_get_len(sa)) > 0) {
if (!has_duplicate_addr(sa, records, current - records)) {
++ret;
if (current != end) {
memcpy(&current->addr, sa, len);
current->len = len;
addr_set_port((struct sockaddr *)&current->addr, port);
++current;
}
}
}
}
if (!ifconf_has_inet6 && bound.addr.ss_family == AF_INET6) {
struct sockaddr_in6 sin6;
if (get_local_default_inet6(port, &sin6) == 0) {
if (!addr_is_local((const struct sockaddr *)&sin6)) {
++ret;
if (current != end) {
memcpy(&current->addr, &sin6, sizeof(sin6));
current->len = sizeof(sin6);
++current;
}
}
}
}
#endif
#endif
return ret;
}

33
thirdparty/libjuice/src/udp.h vendored Normal file
View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifndef JUICE_UDP_H
#define JUICE_UDP_H
#include "addr.h"
#include "socket.h"
#include <stdint.h>
typedef struct udp_socket_config {
const char *bind_address;
uint16_t port_begin;
uint16_t port_end;
} udp_socket_config_t;
socket_t udp_create_socket(const udp_socket_config_t *config);
int udp_recvfrom(socket_t sock, char *buffer, size_t size, addr_record_t *src);
int udp_sendto(socket_t sock, const char *data, size_t size, const addr_record_t *dst);
int udp_sendto_self(socket_t sock, const char *data, size_t size);
int udp_set_diffserv(socket_t sock, int ds);
uint16_t udp_get_port(socket_t sock);
int udp_get_bound_addr(socket_t sock, addr_record_t *record);
int udp_get_local_addr(socket_t sock, int family, addr_record_t *record); // family may be AF_UNSPEC
int udp_get_addrs(socket_t sock, addr_record_t *records, size_t count);
#endif // JUICE_UDP_H