src/transport/xqc_fec.c (1,300 lines of code) (raw):
/**
* @copyright Copyright (c) 2022, Alibaba Group Holding Limited
*/
#include "src/transport/xqc_fec.h"
#include "src/transport/xqc_fec_scheme.h"
#include "src/transport/xqc_conn.h"
#include "src/transport/xqc_send_queue.h"
#include "src/transport/xqc_packet_out.h"
#define XQC_FEC_MAX_SCHEME_VAL 32
#define MAX_FEC_CODE_RATE (20)
xqc_int_t
xqc_set_valid_encoder_scheme_cb(xqc_fec_code_callback_t *callback, xqc_int_t scheme)
{
switch (scheme) {
#ifdef XQC_ENABLE_RSC
case XQC_REED_SOLOMON_CODE:
callback->xqc_fec_init = xqc_reed_solomon_code_cb.xqc_fec_init;
callback->xqc_fec_init_one = xqc_reed_solomon_code_cb.xqc_fec_init_one;
callback->xqc_fec_encode = xqc_reed_solomon_code_cb.xqc_fec_encode;
return XQC_OK;
#endif
#ifdef XQC_ENABLE_XOR
case XQC_XOR_CODE:
callback->xqc_fec_init = xqc_xor_code_cb.xqc_fec_init;
callback->xqc_fec_init_one = xqc_xor_code_cb.xqc_fec_init_one;
callback->xqc_fec_encode = xqc_xor_code_cb.xqc_fec_encode;
return XQC_OK;
#endif
#ifdef XQC_ENABLE_PKM
case XQC_PACKET_MASK_CODE:
callback->xqc_fec_init = xqc_packet_mask_code_cb.xqc_fec_init;
callback->xqc_fec_init_one = xqc_packet_mask_code_cb.xqc_fec_init_one;
callback->xqc_fec_encode = xqc_packet_mask_code_cb.xqc_fec_encode;
return XQC_OK;
#endif
}
return -XQC_EFEC_SCHEME_ERROR;
}
xqc_int_t
xqc_set_valid_decoder_scheme_cb(xqc_fec_code_callback_t *callback, xqc_int_t scheme)
{
switch (scheme) {
#ifdef XQC_ENABLE_RSC
case XQC_REED_SOLOMON_CODE:
callback->xqc_fec_decode = xqc_reed_solomon_code_cb.xqc_fec_decode;
return XQC_OK;
#endif
#ifdef XQC_ENABLE_XOR
case XQC_XOR_CODE:
callback->xqc_fec_decode = xqc_xor_code_cb.xqc_fec_decode;
return XQC_OK;
#endif
#ifdef XQC_ENABLE_PKM
case XQC_PACKET_MASK_CODE:
callback->xqc_fec_decode_one = xqc_packet_mask_code_cb.xqc_fec_decode_one;
return XQC_OK;
#endif
}
return -XQC_EFEC_SCHEME_ERROR;
}
unsigned char *
xqc_get_fec_scheme_str(xqc_fec_schemes_e scheme)
{
switch (scheme) {
case XQC_REED_SOLOMON_CODE:
return "Reed-Solomon";
case XQC_XOR_CODE:
return "XOR";
case XQC_PACKET_MASK_CODE:
return "Packet-Mask";
default:
return "NO_FEC";
}
}
unsigned char*
xqc_get_fec_enc_level_str(xqc_fec_level_e fec_level)
{
switch (fec_level) {
case XQC_FEC_CONN_LEVEL:
return "FEC_CONN_LEVEL";
case XQC_FEC_STREAM_LEVEL:
return "FEC_STREAM_LEVEL";
default:
return "UNDEFINED";
}
}
unsigned char *
xqc_get_fec_mp_mode_str(xqc_fec_ctl_t *fec_ctl)
{
if (fec_ctl == NULL) {
return "NO_FEC";
}
if (fec_ctl->fec_mp_mode == XQC_FEC_MP_USE_STB) {
if (fec_ctl->fec_rep_path_id != XQC_MAX_UINT64_VALUE) {
return "USE_STB_PATH";
} else {
return "NO_AVAI_STB_PATH";
}
}
return "DEFAULT";
}
xqc_int_t
xqc_set_final_scheme(xqc_connection_t *conn, xqc_fec_schemes_e *local_fec_schemes_buff, xqc_int_t *local_fec_schemes_buff_len,
xqc_fec_schemes_e *remote_fec_schemes_buff, xqc_int_t remote_fec_schemes_buff_len)
{
uint32_t p, schemes_flag;
xqc_int_t i, ret;
if (*local_fec_schemes_buff_len == 0 || remote_fec_schemes_buff_len == 0) {
return 0;
}
p = schemes_flag = 0;
ret = 0;
for (i = 0; i < remote_fec_schemes_buff_len; i++) {
if (remote_fec_schemes_buff[i] > XQC_FEC_MAX_SCHEME_VAL) {
continue;
}
p = 1 << remote_fec_schemes_buff[i];
schemes_flag |= p;
}
/* 初始化schemes_flag */
for (i = 0; i < *local_fec_schemes_buff_len; i++) {
if (schemes_flag & (1 << local_fec_schemes_buff[i])) {
ret = local_fec_schemes_buff[i];
break;
}
}
return ret;
}
xqc_int_t
xqc_set_fec_scheme(uint64_t in, xqc_fec_schemes_e *out)
{
switch (in) {
case XQC_REED_SOLOMON_CODE:
*out = XQC_REED_SOLOMON_CODE;
return XQC_OK;
case XQC_XOR_CODE:
*out = XQC_XOR_CODE;
return XQC_OK;
case XQC_PACKET_MASK_CODE:
*out = XQC_PACKET_MASK_CODE;
return XQC_OK;
default:
break;
}
return -XQC_EFEC_SCHEME_ERROR;
}
xqc_int_t
xqc_set_fec_schemes(const xqc_fec_schemes_e *schemes, xqc_int_t schemes_len,
xqc_fec_schemes_e *fec_schemes_buff, xqc_int_t *fec_schemes_buff_len)
{
xqc_int_t i = 0, j = 0;
for (i = 0; i < XQC_FEC_MAX_SCHEME_NUM; i++)
{
fec_schemes_buff[i] = 0;
}
*fec_schemes_buff_len = 0;
for (i = 0, j = 0; i < schemes_len && j < XQC_FEC_MAX_SCHEME_NUM; i++) {
switch (schemes[i]) {
case XQC_XOR_CODE:
fec_schemes_buff[j] = XQC_XOR_CODE;
j++;
break;
case XQC_REED_SOLOMON_CODE:
fec_schemes_buff[j] = XQC_REED_SOLOMON_CODE;
j++;
break;
case XQC_PACKET_MASK_CODE:
fec_schemes_buff[j] = XQC_PACKET_MASK_CODE;
j++;
break;
default:
break;
}
if (j != *fec_schemes_buff_len) {
*fec_schemes_buff_len = j;
}
}
return XQC_OK;
}
/**
* @brief
* return XQC_TRUE if obj and cmp_buff has SAME payload content,
* otherwise return XQC_FALSE
* @param obj
* @param cmp_buff
* @return xqc_bool_t
*/
xqc_bool_t
xqc_fec_object_compare(xqc_fec_object_t *obj, unsigned char *cmp_buff)
{
size_t obj_size;
unsigned char *obj_buff;
if (!obj->is_valid) {
return XQC_FALSE;
}
obj_size = obj->payload_size;
obj_buff = obj->payload;
return xqc_memcmp(obj_buff, cmp_buff, obj_size) == 0 ? XQC_TRUE : XQC_FALSE;
}
xqc_int_t
xqc_send_repair_packets_ahead(xqc_connection_t *conn, xqc_list_head_t *prev, uint8_t fec_bm_mode)
{
uint32_t i, fss_esi, repair_num, cur_syb_num, tmp_repair_num;
xqc_int_t ret;
unsigned char *repair_key_p;
if (fec_bm_mode >= XQC_BLOCK_MODE_LEN) {
xqc_log(conn->log, XQC_LOG_ERROR, "|invalid fec_bm_mode:%d|", fec_bm_mode);
return -XQC_EPARAM;
}
cur_syb_num = conn->fec_ctl->fec_send_symbol_num[fec_bm_mode];
fss_esi = conn->fec_ctl->fec_send_block_num[fec_bm_mode];
repair_num = conn->fec_ctl->fec_send_required_repair_num[fec_bm_mode];
tmp_repair_num = 0;
if (cur_syb_num <= 1) {
/* TODO: whether protect if only one src syb? */
return XQC_OK;
}
for (i = 0; i < repair_num; i++) {
if (!conn->fec_ctl->fec_send_repair_key[fec_bm_mode][i].is_valid) {
continue;
}
xqc_packet_out_t *packet_out = xqc_write_one_repair_packet(conn, fss_esi, tmp_repair_num, fec_bm_mode);
if (packet_out == NULL) {
xqc_log(conn->log ,XQC_LOG_ERROR, "|quic_fec|generate one repair packet error");
return -XQC_EFEC_SYMBOL_ERROR;
}
tmp_repair_num++;
conn->fec_ctl->fec_send_ahead++;
xqc_send_queue_move_to_head(&packet_out->po_list, prev);
prev = &packet_out->po_list;
}
xqc_fec_ctl_init_send_params(conn, fec_bm_mode);
return XQC_OK;
}
xqc_int_t
xqc_send_repair_packets(xqc_connection_t *conn, xqc_fec_schemes_e scheme, xqc_list_head_t *prev,
uint8_t fec_bm_mode)
{
uint32_t i, fss_esi, repair_num, pm_size, tmp_repair_num;
xqc_int_t ret;
unsigned char *pm_p;
if (fec_bm_mode >= XQC_BLOCK_MODE_LEN) {
xqc_log(conn->log, XQC_LOG_ERROR, "|invalid fec_bm_mode:%d|", fec_bm_mode);
return -XQC_EPARAM;
}
repair_num = conn->fec_ctl->fec_send_required_repair_num[fec_bm_mode];
fss_esi = conn->fec_ctl->fec_send_block_num[fec_bm_mode];
tmp_repair_num = 0;
if (repair_num > XQC_REPAIR_LEN) {
xqc_log(conn->log ,XQC_LOG_ERROR, "|quic_fec|repair number exceeds buff size");
return -XQC_EFEC_SYMBOL_ERROR;
}
if (repair_num > conn->fec_ctl->fec_send_symbol_num[fec_bm_mode]) {
xqc_log(conn->log, XQC_LOG_ERROR, "|source symbols number or repair symbol number invalid|src:%d|rpr:%d|", conn->fec_ctl->fec_send_symbol_num[fec_bm_mode], repair_num);
return -XQC_EFEC_SYMBOL_ERROR;
}
switch (scheme) {
case XQC_REED_SOLOMON_CODE:
case XQC_XOR_CODE:
/* Generate repair packets */
ret = xqc_write_repair_packets(conn, fss_esi, prev, repair_num, fec_bm_mode);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_process_fec_protected_packet|xqc_gen_repair_packet error|");
return -XQC_EWRITE_PKT;
}
break;
case XQC_PACKET_MASK_CODE:
for (i = 0; i < repair_num; i++) {
pm_p = conn->fec_ctl->fec_send_decode_matrix[fec_bm_mode][i];
if (xqc_fec_object_compare(&conn->fec_ctl->fec_send_repair_key[fec_bm_mode][i], pm_p)) {
xqc_packet_out_t *packet_out = xqc_write_one_repair_packet(conn, fss_esi, tmp_repair_num, fec_bm_mode);
if (packet_out == NULL) {
xqc_log(conn->log ,XQC_LOG_ERROR, "|quic_fec|generate one repair packet error");
continue;
}
tmp_repair_num++;
xqc_send_queue_move_to_head(&packet_out->po_list, prev);
prev = &packet_out->po_list;
}
}
break;
default:
xqc_log(conn->log ,XQC_LOG_ERROR, "|quic_fec|error type of fec scheme");
return -XQC_EFEC_SCHEME_ERROR;
}
return XQC_OK;
}
xqc_int_t
xqc_is_packet_fec_protected(xqc_connection_t *conn, xqc_packet_out_t *packet_out)
{
if (packet_out->po_flag & XQC_POF_USE_FEC
&& packet_out->po_frame_types & conn->conn_settings.fec_params.fec_protected_frames)
{
return XQC_OK;
}
return -XQC_EFEC_NOT_SUPPORT_FEC;
}
xqc_int_t
xqc_check_fec_params(xqc_connection_t *conn, xqc_int_t src_symbol_num, xqc_int_t repair_symbol_num,
xqc_int_t max_window_size, xqc_int_t symbol_size)
{
if (repair_symbol_num < 0 || repair_symbol_num > XQC_REPAIR_LEN || repair_symbol_num > src_symbol_num) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|invalid fec repair symbol:%d|src_num:%d|", repair_symbol_num, src_symbol_num);
return -XQC_EFEC_SCHEME_ERROR;
}
if (repair_symbol_num == 0) {
xqc_log(conn->log, XQC_LOG_WARN, "|quic_fec|xqc_fec_encoder|current code rate is too low to generate repair packets.");
return -XQC_EFEC_SYMBOL_ERROR;
}
if (src_symbol_num <= 0 || src_symbol_num > XQC_FEC_MAX_SYMBOL_NUM_PBLOCK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|src_symbol_num invalid|%d|", src_symbol_num);
return -XQC_EFEC_SYMBOL_ERROR;
}
if (max_window_size <= 0 || max_window_size > XQC_SYMBOL_CACHE_LEN) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|max_window_size invalid|%d|", max_window_size);
return -XQC_EFEC_SYMBOL_ERROR;
}
if (symbol_size < 0 || symbol_size > XQC_MAX_SYMBOL_SIZE) {
xqc_log(conn->log, XQC_LOG_WARN, "|quic_fec|symbol_size invalid|%d|", symbol_size);
return -XQC_EFEC_SYMBOL_ERROR;
}
return XQC_OK;
}
/* process fec protected packet with stream mode */
xqc_int_t
xqc_process_fec_protected_packet(xqc_connection_t *conn, xqc_packet_out_t *packet_out)
{
uint8_t fec_bm_mode;
xqc_int_t i, ret, fss_esi, header_len, payload_len, max_src_symbol_num, repair_symbol_num;
xqc_fec_schemes_e encoder_scheme;
unsigned char *p;
header_len = packet_out->po_payload - packet_out->po_buf;
payload_len = packet_out->po_used_size - header_len;
fec_bm_mode = packet_out->po_stream_fec_blk_mode;
max_src_symbol_num = xqc_get_fec_blk_size(conn, fec_bm_mode);
repair_symbol_num = conn->fec_ctl->fec_send_required_repair_num[fec_bm_mode];
encoder_scheme = conn->conn_settings.fec_params.fec_encoder_scheme;
ret = xqc_check_fec_params(conn, max_src_symbol_num, repair_symbol_num, conn->conn_settings.fec_params.fec_max_window_size, payload_len);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_is_fec_params_valid|fec params invalid|");
return -XQC_EPARAM;
}
/* attach sid frame to current packet */
ret = xqc_write_sid_frame_to_one_packet(conn, packet_out);
if (ret == -XQC_EFEC_TOLERABLE_ERROR) {
return XQC_OK;
} else if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_write_sid_frame_to_one_packet error|");
return -XQC_EFEC_SYMBOL_ERROR;
}
/* FEC encoder */
ret = xqc_fec_encoder(conn, packet_out->po_payload, payload_len, fec_bm_mode);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_fec_encoder error|");
xqc_fec_ctl_init_send_params(conn, fec_bm_mode);
return ret;
}
conn->fec_ctl->fec_send_symbol_num[fec_bm_mode] += 1;
/* Try to generate repair packets, only succeed when send_symbol_numbers satisfy the limits */
if (conn->fec_ctl->fec_send_symbol_num[fec_bm_mode] == max_src_symbol_num) {
ret = xqc_send_repair_packets(conn, conn->conn_settings.fec_params.fec_encoder_scheme, &packet_out->po_list, fec_bm_mode);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_send_repair_packets error: %d|", ret);
}
xqc_fec_ctl_init_send_params(conn, fec_bm_mode);
}
return XQC_OK;
}
void
xqc_stream_fec_init(xqc_stream_t *stream)
{
double fec_code_rate;
xqc_connection_t *conn;
conn = stream->stream_conn;
// if FEC negotiation success, set current block size
if (conn->conn_settings.fec_params.fec_encoder_scheme == XQC_PACKET_MASK_CODE) {
// set fec symbol number in trans stream in the first round
if (stream->stream_fec_ctl.stream_fec_syb_num != 0) {
conn->conn_settings.fec_params.fec_code_rate = stream->stream_fec_ctl.fec_code_rate;
xqc_fec_on_stream_size_changed(stream);
}
}
}
xqc_int_t
xqc_process_fec_protected_packet_moq(xqc_stream_t *stream)
{
uint8_t fec_bm_mode;
xqc_int_t ret, header_len, payload_len, max_src_symbol_num, repair_symbol_num;
xqc_list_head_t *pos, *next;
xqc_list_head_t *fec_enc_pkts;
xqc_packet_out_t *packet_out;
xqc_connection_t *conn;
xqc_send_queue_t *send_queue;
fec_bm_mode = 0;
conn = stream->stream_conn;
send_queue = conn->conn_send_queue;
// fec_enc_pkts = &stream->stream_fec_ctl.stream_fec_send_packets;
fec_enc_pkts = stream->stream_fec_ctl.stream_fec_head;
xqc_stream_fec_init(stream);
max_src_symbol_num = stream->stream_fec_ctl.stream_fec_syb_num;
repair_symbol_num = conn->fec_ctl->fec_send_required_repair_num[fec_bm_mode];
ret = xqc_check_fec_params(conn, xqc_min(max_src_symbol_num, XQC_FEC_MAX_SYMBOL_NUM_PBLOCK), repair_symbol_num, conn->conn_settings.fec_params.fec_max_window_size, 0);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_is_fec_params_valid|fec params invalid|");
return -XQC_EFEC_SYMBOL_ERROR;
}
xqc_list_for_each_safe(pos, next, fec_enc_pkts) {
packet_out = xqc_list_entry(pos, xqc_packet_out_t, po_list);
header_len = packet_out->po_payload - packet_out->po_buf;
payload_len = packet_out->po_used_size - header_len;
if (payload_len < 0 || payload_len > XQC_MAX_SYMBOL_SIZE) {
continue;
}
/* attach sid frame to current packet */
ret = xqc_write_sid_frame_to_one_packet(conn, packet_out);
if (ret == -XQC_EFEC_TOLERABLE_ERROR) {
return XQC_OK;
} else if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_write_sid_frame_to_one_packet error|");
return -XQC_EFEC_SYMBOL_ERROR;
}
/* FEC encoder */
ret = xqc_fec_encoder(conn, packet_out->po_payload, payload_len, fec_bm_mode);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_fec_encoder error|");
xqc_fec_ctl_init_send_params(conn, fec_bm_mode);
return ret;
}
conn->fec_ctl->fec_send_symbol_num[fec_bm_mode] += 1;
/* if source symbols number GT max symbol number(48), generate repair packets in ahead */
if (conn->fec_ctl->fec_send_symbol_num[fec_bm_mode] == XQC_FEC_MAX_SYMBOL_NUM_PBLOCK) {
ret = xqc_send_repair_packets(conn, XQC_PACKET_MASK_CODE, (&conn->conn_send_queue->sndq_send_packets)->prev, 0);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_send_repair_packets error: %d|", ret);
break;
}
stream->stream_stats.fec_send_rpr_cnt += repair_symbol_num;
xqc_fec_ctl_init_send_params(conn, 0);
stream->stream_fec_ctl.stream_fec_syb_num -= XQC_FEC_MAX_SYMBOL_NUM_PBLOCK;
if (stream->stream_fec_ctl.stream_fec_syb_num != 0) {
xqc_fec_on_stream_size_changed(stream);
repair_symbol_num = conn->fec_ctl->fec_send_required_repair_num[fec_bm_mode];
}
}
if (pos == stream->stream_fec_ctl.stream_fec_tail) {
break;
}
}
if (stream->stream_fec_ctl.stream_fec_syb_num != 0) {
/* Try to generate repair packets, only succeed when send_symbol_numbers satisfy the limits */
ret = xqc_send_repair_packets(conn, XQC_PACKET_MASK_CODE, (&conn->conn_send_queue->sndq_send_packets)->prev, 0);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|xqc_send_repair_packets error: %d|", ret);
} else {
stream->stream_stats.fec_send_rpr_cnt += repair_symbol_num;
}
}
ret = xqc_fec_ctl_init_send_params(conn, 0);
stream->stream_fec_ctl.stream_fec_syb_num = 0;
stream->stream_fec_ctl.stream_fec_head = stream->stream_fec_ctl.stream_fec_tail = NULL;
return XQC_OK;
}
xqc_int_t
xqc_gen_src_payload_id(xqc_fec_ctl_t *fec_ctl, uint64_t *payload_id, uint8_t bm_idx)
{
xqc_connection_t *conn = fec_ctl->conn;
if (fec_ctl->fec_send_block_num[bm_idx] > XQC_FEC_MAX_BLOCK_NUM || fec_ctl->fec_send_symbol_num[bm_idx] > XQC_FEC_MAX_SYMBOL_NUM) {
return -XQC_EFEC_SYMBOL_ERROR;
}
*payload_id = fec_ctl->fec_send_block_num[bm_idx] << 8 | fec_ctl->fec_send_symbol_num[bm_idx];
return XQC_OK;
}
xqc_fec_ctl_t *
xqc_fec_ctl_create(xqc_connection_t *conn)
{
xqc_int_t i, j;
uint32_t repair_num;
xqc_fec_ctl_t *fec_ctl = NULL;
fec_ctl = xqc_calloc(1, sizeof(xqc_fec_ctl_t));
if (fec_ctl == NULL) {
return NULL;
}
fec_ctl->conn = conn;
if (conn->conn_settings.fec_params.fec_code_rate == 0) {
fec_ctl->fec_send_required_repair_num[XQC_DEFAULT_SIZE_REQ] = 1;
} else {
repair_num = xqc_max(1, conn->conn_settings.fec_params.fec_max_symbol_num_per_block * conn->conn_settings.fec_params.fec_code_rate);
fec_ctl->fec_send_required_repair_num[XQC_DEFAULT_SIZE_REQ] = xqc_min(repair_num, XQC_REPAIR_LEN);
}
if (conn->conn_settings.enable_multipath) {
fec_ctl->fec_mp_mode = conn->conn_settings.fec_params.fec_mp_mode;
}
fec_ctl->fec_rep_path_id = XQC_MAX_UINT64_VALUE;
for (i = 0; i < XQC_REPAIR_LEN; i++) {
unsigned char *recv_syb_p = xqc_calloc(XQC_MAX_SYMBOL_SIZE, sizeof(unsigned char));
if (recv_syb_p == NULL) {
goto process_emalloc;
}
xqc_set_object_value(&fec_ctl->fec_gen_repair_symbols_buff[i], 0, recv_syb_p, 0);
}
for (i = 0; i < XQC_BLOCK_MODE_LEN; i++) {
if (i == XQC_SLIM_SIZE_REQ) {
continue;
}
fec_ctl->fec_send_block_num[i] = i;
for (j = 0; j < XQC_REPAIR_LEN; j++) {
unsigned char *key_p = xqc_calloc(XQC_MAX_RPR_KEY_SIZE, sizeof(unsigned char));
if (key_p == NULL) {
goto process_emalloc;
}
xqc_set_object_value(&fec_ctl->fec_send_repair_key[i][j], 0, key_p, 0);
unsigned char *syb_p = xqc_calloc(XQC_MAX_SYMBOL_SIZE, sizeof(unsigned char));
if (syb_p == NULL) {
goto process_emalloc;
}
xqc_set_object_value(&fec_ctl->fec_send_repair_symbols_buff[i][j], 0, syb_p, 0);
}
}
for (i = 0; i < XQC_FEC_BLOCK_NUM; i++) {
fec_ctl->latest_stream_id[i] = -1;
}
// FEC 2.0: init repair symbols list and source symbols list
fec_ctl->fec_src_syb_num = 0;
fec_ctl->fec_rpr_syb_num = 0;
xqc_init_list_head(&fec_ctl->fec_recv_rpr_syb_list);
xqc_init_list_head(&fec_ctl->fec_recv_src_syb_list);
xqc_init_list_head(&fec_ctl->fec_free_src_list);
xqc_init_list_head(&fec_ctl->fec_free_rpr_list);
return fec_ctl;
process_emalloc:
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|create_fec_ctl fail to malloc for params");
xqc_fec_ctl_destroy(fec_ctl);
return NULL;
}
void
xqc_fec_ctl_destroy(xqc_fec_ctl_t *fec_ctl)
{
xqc_int_t i, j;
xqc_list_head_t *pos, *next;
fec_ctl->fec_flow_id = 0;
for (i = 0; i < XQC_REPAIR_LEN; i++) {
if (fec_ctl->fec_gen_repair_symbols_buff[i].payload != NULL) {
xqc_free(fec_ctl->fec_gen_repair_symbols_buff[i].payload);
fec_ctl->fec_gen_repair_symbols_buff[i].is_valid = 0;
}
}
for (i = 0; i < XQC_BLOCK_MODE_LEN; i++) {
if (i == XQC_SLIM_SIZE_REQ) {
continue;
}
fec_ctl->fec_send_symbol_num[i] = 0;
fec_ctl->fec_send_block_num[i] = 0;
for (j = 0; j < XQC_REPAIR_LEN; j++) {
if (fec_ctl->fec_send_repair_key[i][j].payload != NULL) {
xqc_free(fec_ctl->fec_send_repair_key[i][j].payload);
fec_ctl->fec_send_repair_key[i][j].is_valid = 0;
}
if (fec_ctl->fec_send_repair_symbols_buff[i][j].payload != NULL) {
xqc_free(fec_ctl->fec_send_repair_symbols_buff[i][j].payload);
fec_ctl->fec_send_repair_symbols_buff[i][j].is_valid = 0;
}
}
}
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_recv_src_syb_list) {
xqc_fec_src_syb_t *symbol = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
xqc_free(symbol->payload);
xqc_free(symbol);
}
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_recv_rpr_syb_list) {
xqc_fec_rpr_syb_t *symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
xqc_free(symbol->payload);
xqc_free(symbol);
}
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_free_src_list) {
xqc_fec_src_syb_t *symbol = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
xqc_free(symbol->payload);
xqc_free(symbol);
}
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_free_rpr_list) {
xqc_fec_rpr_syb_t *symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
xqc_free(symbol->payload);
xqc_free(symbol->repair_key);
xqc_free(symbol->recv_mask);
xqc_free(symbol);
}
xqc_free(fec_ctl);
}
void
xqc_set_fec_blk_size(xqc_connection_t *conn, xqc_transport_params_t params)
{
switch (params.fec_version) {
case XQC_FEC_02:
xqc_memcpy(conn->fec_ctl->fec_send_block_mode_size, fec_blk_size_v2, XQC_BLOCK_MODE_LEN);
break;
default:
break;
}
}
uint8_t
xqc_get_fec_blk_size(xqc_connection_t *conn, uint8_t blk_md) {
if (blk_md == XQC_DEFAULT_SIZE_REQ) {
return xqc_min(XQC_FEC_MAX_SYMBOL_NUM_PBLOCK, xqc_max(0, conn->conn_settings.fec_params.fec_max_symbol_num_per_block));
}
return conn->fec_ctl->fec_send_block_mode_size[blk_md];
}
xqc_int_t
xqc_fec_ctl_save_symbol(unsigned char **symbol_buff_addr, const unsigned char *data,
xqc_int_t data_len)
{
if (*symbol_buff_addr == NULL) {
return -XQC_EMALLOC;
}
xqc_memset(*symbol_buff_addr, 0, data_len);
xqc_memcpy(*symbol_buff_addr, data, data_len);
return XQC_OK;
}
xqc_int_t
xqc_fec_ctl_init_send_params(xqc_connection_t *conn, uint8_t bm_idx)
{
double loss_rate;
uint32_t send_repair_num;
xqc_int_t i, symbol_size, key_size;
xqc_fec_ctl_t *fec_ctl = conn->fec_ctl;
symbol_size = key_size = 0;
fec_ctl->fec_send_symbol_num[bm_idx] = 0;
for (i = 0 ; i < XQC_REPAIR_LEN; i++) {
if (conn->conn_settings.fec_params.fec_encoder_scheme != XQC_REED_SOLOMON_CODE) {
if (fec_ctl->fec_send_repair_key[bm_idx][i].is_valid) {
fec_ctl->fec_send_repair_key[bm_idx][i].payload_size = XQC_MAX_RPR_KEY_SIZE;
xqc_init_object_value(&fec_ctl->fec_send_repair_key[bm_idx][i]);
}
}
if (fec_ctl->fec_send_repair_symbols_buff[bm_idx][i].is_valid) {
fec_ctl->fec_send_repair_symbols_buff[bm_idx][i].payload_size = XQC_MAX_SYMBOL_SIZE;
xqc_init_object_value(&fec_ctl->fec_send_repair_symbols_buff[bm_idx][i]);
}
}
// each time init send param, add 1 to send_block_num, so that symbol from different block won't be mixed
if (conn->fec_ctl->fec_send_block_num[bm_idx] >= XQC_FEC_MAX_BLOCK_NUM - XQC_BLOCK_MODE_LEN) {
conn->fec_ctl->fec_send_block_num[bm_idx] = bm_idx;
} else {
conn->fec_ctl->fec_send_block_num[bm_idx] += XQC_BLOCK_MODE_LEN;
}
if (conn->conn_settings.fec_params.fec_code_rate == 0 && conn->conn_settings.fec_params.fec_encoder_scheme != XQC_XOR_CODE) {
loss_rate = xqc_conn_recent_loss_rate(conn);
send_repair_num = xqc_min(XQC_REPAIR_LEN, xqc_max(1, (int)(loss_rate * xqc_get_fec_blk_size(conn, bm_idx) / 100)));
if (conn->fec_ctl->fec_send_required_repair_num[bm_idx] != send_repair_num) {
// edit encode repair key
conn->fec_ctl->fec_send_required_repair_num[bm_idx] = send_repair_num;
conn->conn_settings.fec_callback.xqc_fec_init_one(conn, bm_idx);
}
}
return XQC_OK;
}
void
xqc_set_object_value(xqc_fec_object_t *object, xqc_int_t is_valid,
unsigned char *payload, size_t size)
{
object->is_valid = is_valid;
object->payload = payload;
object->payload_size = xqc_min(XQC_MAX_SYMBOL_SIZE, size);
}
void
xqc_init_object_value(xqc_fec_object_t *object)
{
object->is_valid = 0;
xqc_memset(object->payload, 0, object->payload_size);
object->payload_size = 0;
}
void
xqc_init_src_symbol_value(xqc_fec_src_syb_t *symbol)
{
symbol->block_id = 0;
symbol->symbol_idx = 0;
xqc_memset(symbol->payload, 0, xqc_min(XQC_MAX_SYMBOL_SIZE, symbol->payload_size));
symbol->payload_size = 0;
}
void
xqc_init_rpr_symbol_value(xqc_fec_rpr_syb_t *symbol)
{
symbol->block_id = 0;
symbol->symbol_idx = 0;
xqc_memset(symbol->payload, 0, XQC_MAX_SYMBOL_SIZE);
symbol->payload_size = 0;
xqc_memset(symbol->repair_key, 0, XQC_MAX_RPR_KEY_SIZE);
xqc_memset(symbol->recv_mask, 0, XQC_MAX_RPR_KEY_SIZE);
symbol->repair_key_size = 0;
}
void
xqc_remove_src_symbol_from_list(xqc_fec_ctl_t *fec_ctl, xqc_fec_src_syb_t *src_symbol)
{
xqc_list_del_init(&src_symbol->fec_list);
xqc_init_src_symbol_value(src_symbol);
// save payload for further uses
xqc_list_add_tail(&src_symbol->fec_list, &fec_ctl->fec_free_src_list);
fec_ctl->fec_src_syb_num--;
}
void
xqc_remove_rpr_symbol_from_list(xqc_fec_ctl_t *fec_ctl, xqc_fec_rpr_syb_t *rpr_symbol)
{
xqc_list_del_init(&rpr_symbol->fec_list);
xqc_init_rpr_symbol_value(rpr_symbol);
// save payload for further uses
xqc_list_add_tail(&rpr_symbol->fec_list, &fec_ctl->fec_free_rpr_list);
fec_ctl->fec_rpr_syb_num--;
}
xqc_int_t
xqc_fec_ctl_init_recv_params(xqc_fec_ctl_t *fec_ctl, uint64_t block_id)
{
xqc_int_t j, symbol_size, key_size;
xqc_list_head_t *pos, *next;
// FEC 2.0 update symbols list
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_recv_src_syb_list) {
xqc_fec_src_syb_t *src_symbol = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
if (src_symbol->block_id > block_id) {
break;
}
if (src_symbol->block_id == block_id) {
xqc_remove_src_symbol_from_list(fec_ctl, src_symbol);
}
}
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_recv_rpr_syb_list) {
xqc_fec_rpr_syb_t *rpr_symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
if (rpr_symbol->block_id > block_id) {
break;
}
if (rpr_symbol->block_id == block_id) {
xqc_remove_rpr_symbol_from_list(fec_ctl, rpr_symbol);
}
}
return XQC_OK;
}
xqc_int_t
xqc_negotiate_fec_schemes(xqc_connection_t *conn, xqc_transport_params_t params)
{
xqc_int_t ret, encode_scheme, decode_scheme;
xqc_trans_settings_t *ls = &conn->local_settings;
ret = -XQC_EFEC_NOT_SUPPORT_FEC;
/*
* 如果对端发来了多种FEC schemes选择,接收端需要选择其中的一种FEC schemes并重新encode local_settings
* AS Server:
* 如果FEC协商成功 且 收到的FEC schemes列表长度大于1,需要进行选择;
* 随后需要进行重新设置并encode选择完成后的schemes list,其长度必须为1;
* AS Client:
* 需要设置服务端选择的FEC Scheme至conn_settings
*/
if (params.fec_version == XQC_ERR_FEC_VERSION) {
xqc_log(conn->log, XQC_LOG_DEBUG, "|remote fec version is not supported.");
conn->fec_neg_fail_reason |= XQC_OLD_FEC_VERSION;
return ret;
}
if (ls->enable_encode_fec && params.enable_decode_fec) {
// server should provide only 1 scheme if negotiation success;
if (conn->conn_type == XQC_CONN_TYPE_CLIENT
&& params.fec_decoder_schemes_num > 1)
{
conn->fec_neg_fail_reason |= XQC_CLIENT_RECEIVE_INV_DEC;
goto set_decoder;
}
encode_scheme = xqc_set_final_scheme(conn, ls->fec_encoder_schemes, &ls->fec_encoder_schemes_num,
params.fec_decoder_schemes, params.fec_decoder_schemes_num);
if (encode_scheme == 0) {
ls->enable_encode_fec = 0;
conn->conn_settings.enable_encode_fec = 0;
conn->fec_neg_fail_reason |= XQC_NO_COMMON_FEC_ENC;
xqc_log(conn->log, XQC_LOG_DEBUG, "|quic_fec|negotiate fec encoder schemes failed.");
goto set_decoder;
}
// set valid encoder scheme
ret = xqc_set_valid_encoder_scheme_cb(&conn->conn_settings.fec_callback, encode_scheme);
if (ret != XQC_OK) {
ls->enable_encode_fec = 0;
conn->conn_settings.enable_encode_fec = 0;
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|set fec encoder cb failed with scheme num: %d", encode_scheme);
goto set_decoder;
}
conn->conn_settings.fec_params.fec_encoder_scheme = encode_scheme;
// change fec scheme in local_settings
ls->fec_encoder_schemes[0] = conn->conn_settings.fec_params.fec_encoder_scheme;
ls->fec_encoder_schemes_num = 1;
xqc_log(conn->log, XQC_LOG_INFO, "|set final encoder fec scheme: %s|fec_level: %s|",
xqc_get_fec_scheme_str(conn->conn_settings.fec_params.fec_encoder_scheme), xqc_get_fec_enc_level_str(conn->conn_settings.fec_level));
ret = XQC_OK;
}
set_decoder:
if (ls->enable_decode_fec && params.enable_encode_fec) {
// server should provide only 1 scheme if negotiation success;
if (conn->conn_type == XQC_CONN_TYPE_CLIENT
&& params.fec_encoder_schemes_num > 1)
{
conn->fec_neg_fail_reason |= XQC_CLIENT_RECEIVE_INV_ENC;
goto end;
}
decode_scheme = xqc_set_final_scheme(conn, ls->fec_decoder_schemes, &ls->fec_decoder_schemes_num,
params.fec_encoder_schemes, params.fec_encoder_schemes_num);
if (decode_scheme == 0) {
ls->enable_decode_fec = 0;
conn->conn_settings.enable_decode_fec = 0;
conn->fec_neg_fail_reason |= XQC_NO_COMMON_FEC_DEC;
xqc_log(conn->log, XQC_LOG_DEBUG, "|quic_fec|negotiate fec decoder schemes failed.");
goto end;
}
// set valid encoder scheme
ret = xqc_set_valid_decoder_scheme_cb(&conn->conn_settings.fec_callback, decode_scheme);
if (ret != XQC_OK) {
ls->enable_decode_fec = 0;
conn->conn_settings.enable_decode_fec = 0;
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|set fec decoder cb failed with scheme num: %d", decode_scheme);
goto end;
}
conn->conn_settings.fec_params.fec_decoder_scheme = decode_scheme;
// change fec scheme in local_settings
ls->fec_decoder_schemes[0] = conn->conn_settings.fec_params.fec_decoder_scheme;
ls->fec_decoder_schemes_num = 1;
xqc_log(conn->log, XQC_LOG_INFO, "|set final decoder fec scheme: %s",
xqc_get_fec_scheme_str(conn->conn_settings.fec_params.fec_decoder_scheme));
ret = XQC_OK;
}
end:
if (conn->conn_type == XQC_CONN_TYPE_CLIENT) {
if (conn->conn_settings.fec_params.fec_encoder_scheme == 0) {
conn->conn_settings.enable_encode_fec = 0;
xqc_log(conn->log, XQC_LOG_DEBUG, "|quic_fec|negotiate fec encoder schemes failed.");
}
if (conn->conn_settings.fec_params.fec_decoder_scheme == 0) {
conn->conn_settings.enable_decode_fec = 0;
xqc_log(conn->log, XQC_LOG_DEBUG, "|quic_fec|negotiate fec decoder schemes failed.");
}
}
return ret;
}
xqc_int_t
xqc_insert_src_symbol_by_seq(xqc_connection_t *conn, xqc_list_head_t *symbol_list,
uint64_t block_id, uint64_t symbol_idx, xqc_int_t *blk_output,
unsigned char *symbol, xqc_int_t symbol_size)
{
xqc_list_head_t *pos, *next;
xqc_int_t ret, blk_num_flag;
xqc_fec_src_syb_t *src_symbol;
ret = 0;
blk_num_flag = 1;
src_symbol = NULL;
xqc_list_for_each_reverse_safe(pos, next, symbol_list) {
xqc_fec_src_syb_t *cur_symbol = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
if (block_id < cur_symbol->block_id) {
continue;
}
if (block_id == cur_symbol->block_id) {
if (symbol_idx < cur_symbol->symbol_idx) {
continue;
} else if (cur_symbol->symbol_idx == symbol_idx) {
// current symbol already exists.
return -XQC_EFEC_TOLERABLE_ERROR;
}
}
break;
}
// push into src symbol list;
src_symbol = xqc_build_src_symbol(conn, block_id, symbol_idx, symbol, symbol_size);
if (src_symbol == NULL) {
xqc_log(conn->log, XQC_LOG_DEBUG, "|quic_fec|build source symbol error|block_id:%d|symbol_idx:%d|symbol_size:%d", block_id, symbol_idx, symbol_size);
return -XQC_EMALLOC;
}
// insert into proper position
xqc_list_add(&src_symbol->fec_list, pos);
*blk_output += 1;
return XQC_OK;
}
xqc_int_t
xqc_insert_rpr_symbol_by_seq(xqc_connection_t *conn, xqc_list_head_t *symbol_list,
xqc_fec_rpr_syb_t *tmp_rpr_symbol, xqc_int_t *blk_output, xqc_fec_rpr_syb_t **rpr_symbol)
{
xqc_list_head_t *pos, *next;
xqc_int_t ret, blk_num_flag, window_limit, block_id, symbol_idx;
ret = 0;
blk_num_flag = 1;
window_limit = conn->conn_settings.fec_params.fec_max_window_size;
*rpr_symbol = NULL;
block_id = tmp_rpr_symbol->block_id;
symbol_idx = tmp_rpr_symbol->symbol_idx;
if (block_id < 0) {
return -XQC_EFEC_SYMBOL_ERROR;
}
xqc_list_for_each_reverse_safe(pos, next, symbol_list) {
xqc_fec_rpr_syb_t *cur_symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
if (block_id < cur_symbol->block_id) {
continue;
}
if (block_id == cur_symbol->block_id) {
if (symbol_idx < cur_symbol->symbol_idx) {
continue;
} else if (cur_symbol->symbol_idx == symbol_idx) {
// current symbol already exists.
return -XQC_EFEC_TOLERABLE_ERROR;
}
}
break;
}
// insert into rpr symbol list;
*rpr_symbol = xqc_build_rpr_symbol(conn, tmp_rpr_symbol);
if (*rpr_symbol == NULL) {
xqc_log(conn->log, XQC_LOG_DEBUG, "|quic_fec|build repair symbol error|block_id:%d|symbol_idx:%d|symbol_size:%d", block_id, symbol_idx, tmp_rpr_symbol->payload_size);
return -XQC_EMALLOC;
}
// insert into proper position
xqc_list_add(&(*rpr_symbol)->fec_list, pos);
*blk_output += 1;
return XQC_OK;
}
xqc_fec_src_syb_t *
xqc_create_src_symbol()
{
xqc_fec_src_syb_t *src_symbol = (xqc_fec_src_syb_t *)xqc_calloc(1, sizeof(xqc_fec_src_syb_t));
if (src_symbol == NULL) {
return NULL;
}
src_symbol->payload = xqc_calloc(XQC_MAX_SYMBOL_SIZE, sizeof(unsigned char));
if (src_symbol->payload == NULL) {
xqc_free(src_symbol);
return NULL;
}
return src_symbol;
}
xqc_fec_rpr_syb_t *
xqc_create_rpr_symbol()
{
xqc_fec_rpr_syb_t *rpr_symbol = (xqc_fec_rpr_syb_t *)xqc_calloc(1, sizeof(xqc_fec_rpr_syb_t));
if (rpr_symbol == NULL) {
return NULL;
}
rpr_symbol->payload = xqc_calloc(XQC_MAX_SYMBOL_SIZE, sizeof(unsigned char));
rpr_symbol->repair_key = xqc_calloc(XQC_MAX_RPR_KEY_SIZE, sizeof(unsigned char));
rpr_symbol->recv_mask = xqc_calloc(XQC_MAX_RPR_KEY_SIZE, sizeof(unsigned char));
if (rpr_symbol->payload == NULL || rpr_symbol->repair_key == NULL || rpr_symbol->recv_mask == NULL) {
if (rpr_symbol->payload != NULL) {
xqc_free(rpr_symbol->payload);
}
if (rpr_symbol->repair_key != NULL) {
xqc_free(rpr_symbol->repair_key);
}
if (rpr_symbol->recv_mask != NULL) {
xqc_free(rpr_symbol->recv_mask);
}
xqc_free(rpr_symbol);
return NULL;
}
return rpr_symbol;
}
xqc_fec_src_syb_t *
xqc_new_src_symbol(xqc_list_head_t *fec_free_list)
{
// try to fetch an available symbol in free list, otherwise malloc a new space
xqc_list_head_t *pos, *next;
xqc_fec_src_syb_t *src_symbol;
xqc_list_for_each_safe(pos, next, fec_free_list) {
src_symbol = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
xqc_list_del_init(&src_symbol->fec_list);
if (src_symbol->payload == NULL) {
xqc_free(src_symbol);
return NULL;
}
return src_symbol;
}
return xqc_create_src_symbol();
}
xqc_fec_rpr_syb_t *
xqc_new_rpr_symbol(xqc_list_head_t *fec_free_list)
{
// try to fetch an available symbol in free list, otherwise malloc a new space
xqc_list_head_t *pos, *next;
xqc_fec_rpr_syb_t *rpr_symbol;
xqc_list_for_each_safe(pos, next, fec_free_list) {
rpr_symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
xqc_list_del_init(&rpr_symbol->fec_list);
return rpr_symbol;
}
return xqc_create_rpr_symbol();
}
xqc_fec_rpr_syb_t *
xqc_get_rpr_symbol(xqc_list_head_t *head, uint64_t block_id, uint64_t symbol_id)
{
xqc_list_head_t *pos, *next;
xqc_list_for_each_safe(pos, next, head) {
xqc_fec_rpr_syb_t *symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
if (symbol->block_id > block_id) {
break;
}
if (symbol->block_id == block_id) {
if (symbol->symbol_idx > symbol_id) {
break;
}
if (symbol->symbol_idx == symbol_id){
return symbol;
}
}
}
return NULL;
}
void
xqc_update_rpr_symbol_mask_on_src(xqc_list_head_t *head, xqc_int_t block_id,
xqc_int_t pi_sym_idx)
{
xqc_list_head_t *pos, *next;
xqc_int_t mask_offset;
mask_offset = pi_sym_idx / 8;
if (mask_offset >= XQC_MAX_RPR_KEY_SIZE) {
return;
}
// traverse rpr list
xqc_list_for_each_safe(pos, next, head) {
xqc_fec_rpr_syb_t *symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
if (symbol->block_id > block_id) {
break;
}
if (symbol->block_id == block_id
&& *(symbol->repair_key + mask_offset) & (1 << (7 - pi_sym_idx % 8)))
{
*(symbol->recv_mask + mask_offset) |= (1 << (7 - pi_sym_idx % 8));
}
}
}
void
xqc_update_rpr_symbol_mask_on_rpr(xqc_list_head_t *head, xqc_fec_rpr_syb_t *rpr_symbol)
{
xqc_list_head_t *pos, *next;
// traverse src list
xqc_list_for_each_safe(pos, next, head) {
xqc_fec_src_syb_t *symbol = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
xqc_int_t block_id, symbol_idx, mask_offset;
block_id = symbol->block_id;
symbol_idx = symbol->symbol_idx;
mask_offset = symbol_idx / 8;
if (mask_offset >= XQC_MAX_RPR_KEY_SIZE) {
continue;
}
if (block_id > rpr_symbol->block_id) {
break;
}
if (block_id == rpr_symbol->block_id
&& *(rpr_symbol->repair_key + mask_offset) & (1 << (7 - symbol_idx % 8)))
{
*(rpr_symbol->recv_mask + mask_offset) |= (1 << (7 - symbol_idx % 8));
}
}
}
xqc_fec_src_syb_t *
xqc_build_src_symbol(xqc_connection_t *conn, uint64_t block_id, uint64_t symbol_idx,
unsigned char *symbol, xqc_int_t symbol_size)
{
xqc_int_t is_repair_symbol = 0, ret;
xqc_list_head_t *fec_free_src_list;
xqc_fec_src_syb_t *src_symbol = NULL;
if (symbol_size > XQC_MAX_SYMBOL_SIZE) {
return src_symbol;
}
fec_free_src_list = &conn->fec_ctl->fec_free_src_list;
src_symbol = xqc_new_src_symbol(fec_free_src_list);
if (src_symbol == NULL) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|get src symbol error|");
return NULL;
}
src_symbol->block_id = block_id;
src_symbol->symbol_idx = symbol_idx;
xqc_memcpy(src_symbol->payload, symbol, symbol_size);
src_symbol->payload_size = symbol_size;
xqc_init_list_head(&src_symbol->fec_list);
return src_symbol;
}
xqc_fec_rpr_syb_t *
xqc_build_rpr_symbol(xqc_connection_t *conn, xqc_fec_rpr_syb_t *tmp_rpr_symbol)
{
size_t symbol_size, repair_key_size;
xqc_int_t is_repair_symbol = 1, ret;
xqc_list_head_t *fec_free_rpr_list;
xqc_fec_rpr_syb_t *rpr_symbol = NULL;
symbol_size = tmp_rpr_symbol->payload_size;
repair_key_size = tmp_rpr_symbol->repair_key_size;
if (symbol_size > XQC_MAX_SYMBOL_SIZE || repair_key_size > XQC_MAX_RPR_KEY_SIZE) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|invalid symbol params|symbol_size:%d|symbol_key_size:%d|", symbol_size, repair_key_size);
return rpr_symbol;
}
fec_free_rpr_list = &conn->fec_ctl->fec_free_rpr_list;
rpr_symbol = xqc_new_rpr_symbol(fec_free_rpr_list);
if (rpr_symbol == NULL || rpr_symbol->payload == NULL) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|get rpr symbol error|");
return rpr_symbol;
}
rpr_symbol->block_id = tmp_rpr_symbol->block_id;
rpr_symbol->symbol_idx = tmp_rpr_symbol->symbol_idx;
xqc_memcpy(rpr_symbol->payload, tmp_rpr_symbol->payload, symbol_size);
rpr_symbol->payload_size = symbol_size;
xqc_memcpy(rpr_symbol->repair_key, tmp_rpr_symbol->repair_key, repair_key_size);
rpr_symbol->repair_key_size = repair_key_size;
xqc_init_list_head(&rpr_symbol->fec_list);
return rpr_symbol;
}
xqc_int_t
xqc_get_min_src_blk_num(xqc_connection_t *conn)
{
xqc_fec_ctl_t *fec_ctl = conn->fec_ctl;
if (!xqc_list_empty(&fec_ctl->fec_recv_src_syb_list)) {
xqc_fec_src_syb_t *src_symbol = xqc_list_entry(fec_ctl->fec_recv_src_syb_list.next, xqc_fec_src_syb_t, fec_list);
return src_symbol->block_id;
}
return -1;
}
xqc_int_t
xqc_get_min_rpr_blk_num(xqc_connection_t *conn)
{
xqc_fec_ctl_t *fec_ctl = conn->fec_ctl;
if (!xqc_list_empty(&fec_ctl->fec_recv_rpr_syb_list)) {
xqc_fec_rpr_syb_t *rpr_symbol = xqc_list_entry(fec_ctl->fec_recv_rpr_syb_list.next, xqc_fec_rpr_syb_t, fec_list);
return rpr_symbol->block_id;
}
return -1;
}
xqc_bool_t
xqc_if_src_blk_exists(xqc_fec_ctl_t *fec_ctl, uint64_t block_id)
{
xqc_list_head_t *pos, *next;
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_recv_src_syb_list) {
xqc_fec_src_syb_t *src_symbol = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
if (src_symbol->block_id == block_id) {
return XQC_TRUE;
}
}
return XQC_FALSE;
}
xqc_bool_t
xqc_if_rpr_blk_exists(xqc_fec_ctl_t *fec_ctl, uint64_t block_id)
{
xqc_list_head_t *pos, *next;
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_recv_rpr_syb_list) {
xqc_fec_rpr_syb_t *rpr_symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
if (rpr_symbol->block_id == block_id) {
return XQC_TRUE;
}
}
return XQC_FALSE;
}
xqc_int_t
xqc_process_src_symbol(xqc_connection_t *conn, uint64_t block_id, uint64_t symbol_idx,
unsigned char *symbol, xqc_int_t symbol_size)
{
xqc_int_t ret, window_size, min_block_id;
xqc_fec_ctl_t *fec_ctl;
xqc_list_head_t *symbol_list, *rpr_list;
xqc_fec_src_syb_t *src_symbol;
window_size = conn->conn_settings.fec_params.fec_max_window_size;
fec_ctl = conn->fec_ctl;
symbol_list = &conn->fec_ctl->fec_recv_src_syb_list;
src_symbol = NULL;
if (block_id != 0
&& !xqc_if_src_blk_exists(conn->fec_ctl, block_id)
&& block_id <= conn->fec_ctl->fec_max_fin_blk_id)
{
return -XQC_EFEC_TOLERABLE_ERROR;
}
if (conn->fec_ctl->fec_src_syb_num > window_size
&& !xqc_if_src_blk_exists(conn->fec_ctl, block_id))
{
while (conn->fec_ctl->fec_src_syb_num > window_size) {
min_block_id = xqc_get_min_src_blk_num(conn);
if (min_block_id == -1) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|window_size and src_symbol_list length is not match");
return -XQC_EFEC_TOLERABLE_ERROR;
}
// flush the smallest old block
xqc_fec_ctl_init_recv_params(conn->fec_ctl, min_block_id);
}
}
// insert into src symbol_list according to block id and symbol idx
ret = xqc_insert_src_symbol_by_seq(conn, symbol_list, block_id, symbol_idx, &conn->fec_ctl->fec_src_syb_num, symbol, symbol_size);
if (ret != XQC_OK) {
xqc_log(conn->log, XQC_LOG_DEBUG, "|quic_fec|current symbol is already exists|block_id:%d|symbol_idx:%d", block_id, symbol_idx);
return ret;
}
// update repair symbol recv_mask
if (conn->conn_settings.fec_params.fec_decoder_scheme == XQC_PACKET_MASK_CODE) {
rpr_list = &conn->fec_ctl->fec_recv_rpr_syb_list;
xqc_update_rpr_symbol_mask_on_src(rpr_list, block_id, symbol_idx);
}
return XQC_OK;
}
xqc_int_t
xqc_process_rpr_symbol(xqc_connection_t *conn, xqc_fec_rpr_syb_t *tmp_rpr_symbol)
{
xqc_int_t ret, window_size, min_block_id, block_id;
xqc_fec_ctl_t *fec_ctl;
xqc_list_head_t *symbol_list, *src_list;
xqc_fec_rpr_syb_t *rpr_symbol;
window_size = conn->conn_settings.fec_params.fec_max_window_size;
fec_ctl = conn->fec_ctl;
symbol_list = &conn->fec_ctl->fec_recv_rpr_syb_list;
rpr_symbol = NULL;
min_block_id = xqc_get_min_rpr_blk_num(conn);
block_id = tmp_rpr_symbol->block_id;
if (block_id != 0
&& !xqc_if_src_blk_exists(conn->fec_ctl, block_id)
&& block_id <= conn->fec_ctl->fec_max_fin_blk_id)
{
return -XQC_EFEC_TOLERABLE_ERROR;
}
if (conn->fec_ctl->fec_rpr_syb_num > window_size
&& !xqc_if_rpr_blk_exists(conn->fec_ctl, block_id))
{
while (conn->fec_ctl->fec_rpr_syb_num > window_size) {
min_block_id = xqc_get_min_rpr_blk_num(conn);
if (min_block_id == -1) {
xqc_log(conn->log, XQC_LOG_ERROR, "|quic_fec|window_size and rpr_symbol_list length is not match");
return -XQC_EFEC_TOLERABLE_ERROR;
}
// flush the smallest old block
xqc_fec_ctl_init_recv_params(conn->fec_ctl, min_block_id);
}
}
// insert into src symbol_list according to block id and symbol idx
ret = xqc_insert_rpr_symbol_by_seq(conn, symbol_list, tmp_rpr_symbol, &conn->fec_ctl->fec_rpr_syb_num, &rpr_symbol);
if (ret < 0) {
xqc_log(conn->log, XQC_LOG_DEBUG, "|quic_fec|current symbol is already exists|block_id:%d|symbol_idx:%d", block_id, tmp_rpr_symbol->symbol_idx);
return ret;
}
// link src symbols to the rpr symbols using pkt mask
if (conn->conn_settings.fec_params.fec_decoder_scheme == XQC_PACKET_MASK_CODE) {
src_list = &conn->fec_ctl->fec_recv_src_syb_list;
xqc_update_rpr_symbol_mask_on_rpr(src_list, rpr_symbol);
tmp_rpr_symbol->recv_mask = rpr_symbol->recv_mask;
}
rpr_symbol->recv_time = xqc_monotonic_timestamp();
return XQC_OK;
}
xqc_int_t
xqc_get_symbol_flag(xqc_connection_t *conn, uint64_t block_id)
{
xqc_list_head_t *pos, *next;
xqc_int_t symbol_flag, max_src_symbol_num;
symbol_flag = 0;
max_src_symbol_num = conn->remote_settings.fec_max_symbols_num;
xqc_list_for_each_safe(pos, next, &conn->fec_ctl->fec_recv_src_syb_list) {
xqc_fec_src_syb_t *src_symbol = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
if (src_symbol->block_id > block_id) {
break;
}
if (src_symbol->block_id == block_id) {
symbol_flag |= (1 << src_symbol->symbol_idx);
}
}
xqc_list_for_each_safe(pos, next, &conn->fec_ctl->fec_recv_rpr_syb_list) {
xqc_fec_rpr_syb_t *rpr_symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
if (rpr_symbol->block_id > block_id) {
break;
}
if (rpr_symbol->block_id == block_id) {
symbol_flag |= (1 << (rpr_symbol->symbol_idx + max_src_symbol_num));
}
}
return symbol_flag;
}
xqc_int_t
xqc_get_symbols_buff(unsigned char **output, xqc_fec_ctl_t *fec_ctl, uint64_t block_id, size_t *size)
{
xqc_list_head_t *pos, *next, *fec_recv_src_syb_list, *fec_recv_rpr_syb_list;
xqc_int_t i = 0;
fec_recv_src_syb_list = &fec_ctl->fec_recv_src_syb_list;
fec_recv_rpr_syb_list = &fec_ctl->fec_recv_rpr_syb_list;
*size = 0;
// check 一下当前block idx能否成功被flush
xqc_list_for_each_safe(pos, next, fec_recv_src_syb_list) {
xqc_fec_src_syb_t *src_syb = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
if (src_syb->block_id == block_id) {
xqc_memcpy(output[i++], src_syb->payload, xqc_min(XQC_MAX_SYMBOL_SIZE, src_syb->payload_size));
}
}
xqc_list_for_each_safe(pos, next, fec_recv_rpr_syb_list) {
xqc_fec_rpr_syb_t *rpr_syb = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
if (rpr_syb->block_id == block_id) {
if (rpr_syb->payload_size > XQC_MAX_SYMBOL_SIZE) {
return -XQC_EPARAM;
}
xqc_memcpy(output[i++], rpr_syb->payload, xqc_min(XQC_MAX_SYMBOL_SIZE, rpr_syb->payload_size));
if (rpr_syb->payload_size > *size) {
*size = rpr_syb->payload_size;
}
}
}
return i;
}
xqc_int_t
xqc_cnt_src_symbols_num(xqc_fec_ctl_t *fec_ctl, uint64_t block_id)
{
xqc_int_t ret = 0;
xqc_list_head_t *pos, *next;
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_recv_src_syb_list) {
xqc_fec_src_syb_t *src_symbol = xqc_list_entry(pos, xqc_fec_src_syb_t, fec_list);
if (src_symbol->block_id > block_id) {
break;
}
if (src_symbol->block_id == block_id) {
ret++;
}
}
return ret;
}
xqc_int_t
xqc_cnt_rpr_symbols_num(xqc_fec_ctl_t *fec_ctl, uint64_t block_id)
{
xqc_int_t ret = 0;
xqc_list_head_t *pos, *next;
xqc_list_for_each_safe(pos, next, &fec_ctl->fec_recv_rpr_syb_list) {
xqc_fec_rpr_syb_t *rpr_symbol = xqc_list_entry(pos, xqc_fec_rpr_syb_t, fec_list);
if (rpr_symbol->block_id > block_id) {
break;
}
if (rpr_symbol->block_id == block_id) {
ret++;
}
}
return ret;
}
void
xqc_on_fec_negotiate_success(xqc_connection_t *conn, xqc_transport_params_t params)
{
uint8_t i;
if (conn->conn_settings.enable_encode_fec) {
if (conn->conn_settings.fec_params.fec_encoder_scheme == XQC_PACKET_MASK_CODE) {
xqc_set_fec_blk_size(conn, params);
for (i = XQC_NORMAL_SIZE_REQ; i < XQC_BLOCK_MODE_LEN; i++) {
if (conn->conn_settings.fec_params.fec_code_rate == 0) {
conn->fec_ctl->fec_send_required_repair_num[i] = 1;
} else {
conn->fec_ctl->fec_send_required_repair_num[i] = xqc_min(xqc_max(1, conn->fec_ctl->fec_send_block_mode_size[i] * conn->conn_settings.fec_params.fec_code_rate), XQC_REPAIR_LEN);
}
}
}
if (conn->conn_settings.fec_callback.xqc_fec_init != NULL) {
conn->conn_settings.fec_callback.xqc_fec_init(conn);
}
}
}
xqc_int_t
xqc_get_fec_rpr_num(float fec_code_rate, xqc_int_t src_syb_num)
{
return xqc_min(XQC_REPAIR_LEN, xqc_max(1, xqc_min(src_syb_num, XQC_FEC_MAX_SYMBOL_NUM_PBLOCK) * fec_code_rate + 1));
}
void
xqc_fec_on_stream_size_changed(xqc_stream_t *quic_stream)
{
float fec_code_rate;
xqc_connection_t *conn;
uint32_t src_syb_num, rpr_syb_num;
conn = quic_stream->stream_conn;
src_syb_num = xqc_min(quic_stream->stream_fec_ctl.stream_fec_syb_num, XQC_FEC_MAX_SYMBOL_NUM_PBLOCK);
// fec_code_rate should be pre-set in bitrate_allocator
fec_code_rate = conn->conn_settings.fec_params.fec_code_rate;
rpr_syb_num = xqc_get_fec_rpr_num(fec_code_rate, src_syb_num);
// if fec block size or repair number is different, init fec parameters
if (conn->fec_ctl->fec_send_required_repair_num[0] != rpr_syb_num
|| conn->conn_settings.fec_params.fec_max_symbol_num_per_block != src_syb_num)
{
conn->fec_ctl->fec_send_required_repair_num[0] = rpr_syb_num;
conn->conn_settings.fec_params.fec_max_symbol_num_per_block = src_syb_num;
conn->conn_settings.fec_callback.xqc_fec_init_one(conn, 0);
}
}