Organize host and robot streaming releases
This commit is contained in:
333
host/OmniSocketGo_add_camera/src/gps_buffer.c
Normal file
333
host/OmniSocketGo_add_camera/src/gps_buffer.c
Normal file
@@ -0,0 +1,333 @@
|
||||
#include "gps_buffer.h"
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <math.h>
|
||||
#include <errno.h> // 确保包含 errno
|
||||
|
||||
// 全局共享变量
|
||||
static gps_video_sample_t g_current_gps_data = {0.0, 0.0};
|
||||
static volatile int g_running = 0;
|
||||
static pthread_t g_gps_thread;
|
||||
static pthread_mutex_t g_gps_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static double normalize_coordinate(double coordinate) {
|
||||
return round(coordinate * 1000000.0) / 1000000.0;
|
||||
}
|
||||
|
||||
static void store_gps(double latitude, double longitude) {
|
||||
pthread_mutex_lock(&g_gps_mutex);
|
||||
g_current_gps_data.latitude = normalize_coordinate(latitude);
|
||||
g_current_gps_data.longitude = normalize_coordinate(longitude);
|
||||
pthread_mutex_unlock(&g_gps_mutex);
|
||||
}
|
||||
|
||||
static void clear_gps(void) {
|
||||
pthread_mutex_lock(&g_gps_mutex);
|
||||
g_current_gps_data.latitude = 0.0;
|
||||
g_current_gps_data.longitude = 0.0;
|
||||
pthread_mutex_unlock(&g_gps_mutex);
|
||||
}
|
||||
|
||||
static gps_video_sample_t load_gps(void) {
|
||||
gps_video_sample_t sample;
|
||||
|
||||
pthread_mutex_lock(&g_gps_mutex);
|
||||
sample = g_current_gps_data;
|
||||
pthread_mutex_unlock(&g_gps_mutex);
|
||||
return sample;
|
||||
}
|
||||
|
||||
static void gps_sleep_before_retry(void) {
|
||||
int retry_ms = 1000;
|
||||
int step_ms = 100;
|
||||
int elapsed_ms = 0;
|
||||
|
||||
while (g_running && elapsed_ms < retry_ms) {
|
||||
usleep((useconds_t) step_ms * 1000U);
|
||||
elapsed_ms += step_ms;
|
||||
}
|
||||
}
|
||||
|
||||
// 将经纬度规范化为 double,保留 6 位小数。
|
||||
static int normalize_gps(double latitude, double longitude, gps_video_sample_t* sample) {
|
||||
if (!isfinite(latitude) || !isfinite(longitude)) {
|
||||
return -1;
|
||||
}
|
||||
// 过滤掉 0,0 这种无效坐标
|
||||
if (fabs(latitude) < 1e-6 && fabs(longitude) < 1e-6) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sample == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sample->latitude = normalize_coordinate(latitude);
|
||||
sample->longitude = normalize_coordinate(longitude);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 以下是借鉴 gps_parse.c 实现的底层解析函数
|
||||
// =================================================================
|
||||
|
||||
// 1. 辅助函数:在 JSON 字符串中查找键对应的值的起始位置
|
||||
static const char* find_json_value(const char* json, const char* key) {
|
||||
char pattern[64];
|
||||
int written;
|
||||
const char* position;
|
||||
|
||||
if (json == NULL || key == NULL) return NULL;
|
||||
|
||||
// 构建搜索模式: "key":
|
||||
written = snprintf(pattern, sizeof(pattern), "\"%s\":", key);
|
||||
if (written < 0 || (size_t)written >= sizeof(pattern)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
position = strstr(json, pattern);
|
||||
if (position == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 跳过 "key":
|
||||
position += written;
|
||||
|
||||
// 跳过可能存在的空格
|
||||
while (*position == ' ' || *position == '\t') {
|
||||
position++;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
// 2. 解析函数:从 JSON 字符串中提取 Double 类型的值
|
||||
static int json_extract_double(const char* json, const char* key, double* value) {
|
||||
const char* position;
|
||||
char* endptr = NULL;
|
||||
double parsed;
|
||||
|
||||
position = find_json_value(json, key);
|
||||
if (position == NULL) {
|
||||
return 0; // 键不存在
|
||||
}
|
||||
|
||||
// 确保当前位置是数字或负号
|
||||
if (*position != '-' && !(*position >= '0' && *position <= '9')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 重置 errno 以检测错误
|
||||
errno = 0;
|
||||
parsed = strtod(position, &endptr);
|
||||
|
||||
// 检查转换是否成功
|
||||
if (errno != 0 || endptr == position || !isfinite(parsed)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*value = parsed;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 3. 解析函数:从 JSON 字符串中提取 Int 类型的值
|
||||
static int json_extract_int(const char* json, const char* key, int* value) {
|
||||
double dval;
|
||||
if (json_extract_double(json, key, &dval)) {
|
||||
*value = (int)dval;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 4. 检查是否为 TPV (定位数据) 包
|
||||
static int is_tpv_class(const char* json) {
|
||||
char class_buf[32] = {0};
|
||||
const char* pos = find_json_value(json, "class");
|
||||
if (pos == NULL || *pos != '"') return 0;
|
||||
|
||||
// 简单提取 class 的值 (TPV/SKY/DEVICES)
|
||||
sscanf(pos, "\"%31[^\"]\"", class_buf);
|
||||
return (strcmp(class_buf, "TPV") == 0);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 后台线程函数:负责连接 gpsd 并更新全局变量
|
||||
// =================================================================
|
||||
void* gps_update_thread(void* arg) {
|
||||
const char* host = (const char*)arg;
|
||||
const char* gpsd_host = (host != NULL && host[0] != '\0') ? host : "127.0.0.1";
|
||||
|
||||
while (g_running) {
|
||||
int sockfd = -1;
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *res = NULL;
|
||||
struct addrinfo *rp = NULL;
|
||||
int s;
|
||||
char buffer[4096];
|
||||
size_t offset = 0;
|
||||
|
||||
// 1. 解析地址并连接 gpsd (默认端口 2947)
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC; // 兼容 IPv4/IPv6
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
s = getaddrinfo(gpsd_host, "2947", &hints, &res);
|
||||
if (s != 0) {
|
||||
fprintf(stderr, "GPS线程: 解析 gpsd 地址失败 %s:2947: %s\n", gpsd_host, gai_strerror(s));
|
||||
gps_sleep_before_retry();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 尝试连接每一个解析出来的地址
|
||||
for (rp = res; rp != NULL; rp = rp->ai_next) {
|
||||
sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (sockfd == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) {
|
||||
break;
|
||||
}
|
||||
close(sockfd);
|
||||
sockfd = -1;
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
|
||||
if (sockfd < 0) {
|
||||
fprintf(stderr, "GPS线程: 无法连接到 %s:2947,1 秒后重试\n", gpsd_host);
|
||||
gps_sleep_before_retry();
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("GPS线程: 已连接到 gpsd %s\n", gpsd_host);
|
||||
|
||||
// 2. 发送 WATCH 命令,开启 JSON 流
|
||||
{
|
||||
const char* watch_cmd = "?WATCH={\"enable\":true,\"json\":true};\n";
|
||||
|
||||
if (send(sockfd, watch_cmd, strlen(watch_cmd), 0) < 0) {
|
||||
perror("GPS线程: 发送 WATCH 命令失败");
|
||||
close(sockfd);
|
||||
gps_sleep_before_retry();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 主循环:读取并解析数据流
|
||||
// 注意:gpsd 数据是以 \n 结尾的,不能直接用固定长度 recv
|
||||
while (g_running) {
|
||||
ssize_t len = recv(sockfd, buffer + offset, sizeof(buffer) - 1 - offset, 0);
|
||||
|
||||
if (len <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
offset += (size_t) len;
|
||||
buffer[offset] = '\0'; // 确保字符串结束
|
||||
|
||||
// 查找换行符 \n,因为一条完整的 JSON 消息以 \n 结尾
|
||||
char* start = buffer;
|
||||
char* end;
|
||||
|
||||
while ((end = memchr(start, '\n', (buffer + offset) - start)) != NULL) {
|
||||
*end = '\0'; // 临时截断,形成独立字符串
|
||||
|
||||
// --- 核心解析逻辑 ---
|
||||
// 1. 检查是否为 TPV 数据包
|
||||
if (is_tpv_class(start)) {
|
||||
double lat = 0.0;
|
||||
double lon = 0.0;
|
||||
int mode = 0;
|
||||
int has_fix = 0;
|
||||
|
||||
// 2. 提取定位模式 (mode: 1=无定位, 2=2D, 3=3D)
|
||||
if (json_extract_int(start, "mode", &mode)) {
|
||||
has_fix = (mode >= 2);
|
||||
}
|
||||
|
||||
// 3. 如果有定位,提取经纬度
|
||||
if (has_fix) {
|
||||
int got_lat = json_extract_double(start, "lat", &lat);
|
||||
int got_lon = json_extract_double(start, "lon", &lon);
|
||||
|
||||
if (got_lat && got_lon) {
|
||||
gps_video_sample_t sample;
|
||||
|
||||
// 4. 更新全局共享变量,使用 double 直接携带经纬度。
|
||||
if (normalize_gps(lat, lon, &sample) == 0) {
|
||||
store_gps(sample.latitude, sample.longitude);
|
||||
}
|
||||
// 调试:取消注释可查看实时经纬度
|
||||
// printf("更新GPS: lat=%.6f, lon=%.6f\n", lat, lon);
|
||||
}
|
||||
}
|
||||
// 如果无定位,这里不操作,保持上一次的有效值
|
||||
}
|
||||
// --- 解析结束 ---
|
||||
|
||||
// 移动指针到下一条消息
|
||||
start = end + 1;
|
||||
}
|
||||
|
||||
// 处理完所有完整消息后,将剩余未处理的数据移到缓冲区头部
|
||||
if (start < buffer + offset) {
|
||||
size_t remaining = (size_t) ((buffer + offset) - start);
|
||||
memmove(buffer, start, remaining);
|
||||
offset = remaining;
|
||||
} else {
|
||||
offset = 0; // 缓冲区已清空
|
||||
}
|
||||
}
|
||||
|
||||
close(sockfd);
|
||||
if (g_running) {
|
||||
fprintf(stderr, "GPS线程: 连接断开,1 秒后重连...\n");
|
||||
gps_sleep_before_retry();
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 接口函数实现
|
||||
// =================================================================
|
||||
gps_video_sample_t get_latest_gps_for_video(void) {
|
||||
return load_gps();
|
||||
}
|
||||
|
||||
int gps_buffer_init(const char* host) {
|
||||
if (g_running) return 0;
|
||||
|
||||
g_running = 1;
|
||||
clear_gps();
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
// 创建后台线程
|
||||
if (pthread_create(&g_gps_thread, &attr, gps_update_thread, (void*)host) != 0) {
|
||||
g_running = 0;
|
||||
pthread_attr_destroy(&attr); // 清理属性
|
||||
perror("无法创建 GPS 线程");
|
||||
return -1;
|
||||
}
|
||||
pthread_attr_destroy(&attr); // 清理属性
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gps_buffer_cleanup(void) {
|
||||
g_running = 0;
|
||||
// 等待线程结束
|
||||
|
||||
usleep(10000); // 等待 100ms 让后台线程有机会处理退出标志
|
||||
}
|
||||
|
||||
|
||||
//gcc main.c video_pipeline_run.c gps_buffer.c -lpthread -lm -o my_app 请确保在编译命令中链接 pthread 和 m (math) 库
|
||||
77
host/OmniSocketGo_add_camera/src/interactive.c
Normal file
77
host/OmniSocketGo_add_camera/src/interactive.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "interactive.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
static void interactive_skip_spaces(const char **cursor) {
|
||||
while (**cursor != '\0' && isspace((unsigned char) **cursor)) {
|
||||
(*cursor)++;
|
||||
}
|
||||
}
|
||||
|
||||
int interactive_parse_command(const char *line, interactive_command_t *command, char *err, size_t err_len) {
|
||||
const char *cursor = line;
|
||||
char action[16];
|
||||
size_t action_len = 0;
|
||||
size_t to_len = 0;
|
||||
size_t value_len;
|
||||
|
||||
if (line == NULL || command == NULL) {
|
||||
snprintf(err, err_len, "interactive: invalid command");
|
||||
return -1;
|
||||
}
|
||||
memset(command, 0, sizeof(*command));
|
||||
interactive_skip_spaces(&cursor);
|
||||
while (*cursor != '\0' && !isspace((unsigned char) *cursor) && action_len + 1 < sizeof(action)) {
|
||||
action[action_len++] = *cursor++;
|
||||
}
|
||||
action[action_len] = '\0';
|
||||
if (action_len == 0) {
|
||||
snprintf(err, err_len, "interactive: empty command");
|
||||
return -1;
|
||||
}
|
||||
if (strcmp(action, "help") == 0) {
|
||||
command->type = INTERACTIVE_CMD_HELP;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(action, "quit") == 0) {
|
||||
command->type = INTERACTIVE_CMD_QUIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
interactive_skip_spaces(&cursor);
|
||||
while (*cursor != '\0' && !isspace((unsigned char) *cursor) && to_len + 1 < sizeof(command->to)) {
|
||||
command->to[to_len++] = *cursor++;
|
||||
}
|
||||
command->to[to_len] = '\0';
|
||||
interactive_skip_spaces(&cursor);
|
||||
if (command->to[0] == '\0' || *cursor == '\0') {
|
||||
snprintf(err, err_len, "interactive: missing target or value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
value_len = strlen(cursor);
|
||||
if (value_len >= sizeof(command->value)) {
|
||||
snprintf(err, err_len, "interactive: value too long");
|
||||
return -1;
|
||||
}
|
||||
snprintf(command->value, sizeof(command->value), "%s", cursor);
|
||||
|
||||
if (strcmp(action, "text") == 0) {
|
||||
command->type = INTERACTIVE_CMD_TEXT;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(action, "file") == 0) {
|
||||
command->type = INTERACTIVE_CMD_FILE;
|
||||
return 0;
|
||||
}
|
||||
snprintf(err, err_len, "interactive: unknown command %s", action);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void interactive_print_help(FILE *out, const char *transport_name) {
|
||||
fprintf(out, "interactive mode commands (%s):\n", transport_name);
|
||||
fprintf(out, " help show this help\n");
|
||||
fprintf(out, " text <peer> <message> send one text message\n");
|
||||
fprintf(out, " file <peer> <path> send one file\n");
|
||||
fprintf(out, " quit exit this process\n");
|
||||
}
|
||||
166
host/OmniSocketGo_add_camera/src/kcp_packet_debug.c
Normal file
166
host/OmniSocketGo_add_camera/src/kcp_packet_debug.c
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "kcp_packet_debug.h"
|
||||
|
||||
kcp_packet_debug_logger_t *kcp_packet_debug_open_jsonl(const char *path) {
|
||||
kcp_packet_debug_logger_t *logger;
|
||||
FILE *file;
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
file = fopen(path, "ab");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
logger = (kcp_packet_debug_logger_t *) calloc(1, sizeof(*logger));
|
||||
if (logger == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
omni_file_logger_init_path(&logger->file_logger, file, path, 0);
|
||||
logger->enabled = 1;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void kcp_packet_debug_close(kcp_packet_debug_logger_t *logger) {
|
||||
if (logger == NULL) {
|
||||
return;
|
||||
}
|
||||
if (logger->file_logger.file != NULL) {
|
||||
fclose(logger->file_logger.file);
|
||||
}
|
||||
omni_file_logger_destroy(&logger->file_logger);
|
||||
free(logger);
|
||||
}
|
||||
|
||||
void kcp_packet_debug_record_clear(kcp_packet_debug_record_t *record) {
|
||||
if (record == NULL) {
|
||||
return;
|
||||
}
|
||||
free(record->segments);
|
||||
memset(record, 0, sizeof(*record));
|
||||
}
|
||||
|
||||
int kcp_packet_debug_log(kcp_packet_debug_logger_t *logger, const kcp_packet_debug_record_t *record) {
|
||||
char *event = NULL;
|
||||
char *node_role = NULL;
|
||||
char *node_id = NULL;
|
||||
char *local_addr = NULL;
|
||||
char *remote_addr = NULL;
|
||||
char *segments_json = NULL;
|
||||
char *tx_id_text = NULL;
|
||||
char *conv_text = NULL;
|
||||
char *line = NULL;
|
||||
size_t i;
|
||||
size_t cap = 128U;
|
||||
size_t len = 0U;
|
||||
|
||||
if (logger == NULL || record == NULL || !logger->enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
event = omni_json_escape(record->event);
|
||||
node_role = omni_json_escape(record->node_role);
|
||||
node_id = omni_json_escape(record->node_id);
|
||||
local_addr = omni_json_escape(record->local_addr);
|
||||
remote_addr = omni_json_escape(record->remote_addr);
|
||||
if (event == NULL || node_role == NULL || node_id == NULL || local_addr == NULL || remote_addr == NULL) {
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
segments_json = (char *) malloc(cap);
|
||||
if (segments_json == NULL) {
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
return -1;
|
||||
}
|
||||
segments_json[len++] = '[';
|
||||
for (i = 0; i < record->segment_count; ++i) {
|
||||
int written;
|
||||
while (len + 96U > cap) {
|
||||
char *next = (char *) realloc(segments_json, cap * 2U);
|
||||
if (next == NULL) {
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(segments_json);
|
||||
return -1;
|
||||
}
|
||||
segments_json = next;
|
||||
cap *= 2U;
|
||||
}
|
||||
written = snprintf(
|
||||
segments_json + len,
|
||||
cap - len,
|
||||
"%s{\"cmd\":%u,\"sn\":%u,\"una\":%u,\"frg\":%u,\"wnd\":%u,\"len\":%u}",
|
||||
i == 0 ? "" : ",",
|
||||
record->segments[i].cmd,
|
||||
record->segments[i].sn,
|
||||
record->segments[i].una,
|
||||
record->segments[i].frg,
|
||||
record->segments[i].wnd,
|
||||
record->segments[i].len
|
||||
);
|
||||
len += (size_t) written;
|
||||
}
|
||||
segments_json[len++] = ']';
|
||||
segments_json[len] = '\0';
|
||||
|
||||
tx_id_text = record->has_udp_tx_id ? omni_strdup_printf("%u", record->udp_tx_id) : omni_strdup("null");
|
||||
conv_text = record->has_kcp_conv ? omni_strdup_printf("%u", record->kcp_conv) : omni_strdup("null");
|
||||
if (tx_id_text == NULL || conv_text == NULL) {
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(segments_json);
|
||||
free(tx_id_text);
|
||||
free(conv_text);
|
||||
return -1;
|
||||
}
|
||||
|
||||
line = omni_strdup_printf(
|
||||
"{\"event\":\"%s\",\"node_role\":\"%s\",\"node_id\":\"%s\",\"local_addr\":\"%s\",\"remote_addr\":\"%s\",\"packet_bytes\":%d,\"udp_tx_id\":%s,\"kcp_conv\":%s,\"segments\":%s,\"ts_unix_nano\":%" PRId64 "}",
|
||||
event,
|
||||
node_role,
|
||||
node_id,
|
||||
local_addr,
|
||||
remote_addr,
|
||||
record->packet_bytes,
|
||||
tx_id_text,
|
||||
conv_text,
|
||||
segments_json,
|
||||
record->ts_unix_nano
|
||||
);
|
||||
|
||||
free(event);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(segments_json);
|
||||
free(tx_id_text);
|
||||
free(conv_text);
|
||||
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_file_logger_write_line(&logger->file_logger, line) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
return 0;
|
||||
}
|
||||
286
host/OmniSocketGo_add_camera/src/kcp_session_stats.c
Normal file
286
host/OmniSocketGo_add_camera/src/kcp_session_stats.c
Normal file
@@ -0,0 +1,286 @@
|
||||
#include "kcp_session_stats.h"
|
||||
|
||||
static int kcp_session_stats_append(char **line, size_t *len, const char *suffix) {
|
||||
size_t suffix_len;
|
||||
char *next;
|
||||
|
||||
if (line == NULL || len == NULL || suffix == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
suffix_len = strlen(suffix);
|
||||
next = (char *) realloc(*line, *len + suffix_len + 1U);
|
||||
if (next == NULL) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(next + *len, suffix, suffix_len + 1U);
|
||||
*line = next;
|
||||
*len += suffix_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_session_stats_appendf(char **line, size_t *len, const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_list copy;
|
||||
int needed;
|
||||
char *buffer;
|
||||
|
||||
if (line == NULL || len == NULL || fmt == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
va_copy(copy, args);
|
||||
needed = vsnprintf(NULL, 0, fmt, copy);
|
||||
va_end(copy);
|
||||
if (needed < 0) {
|
||||
va_end(args);
|
||||
return -1;
|
||||
}
|
||||
|
||||
buffer = (char *) malloc((size_t) needed + 1U);
|
||||
if (buffer == NULL) {
|
||||
va_end(args);
|
||||
return -1;
|
||||
}
|
||||
vsnprintf(buffer, (size_t) needed + 1U, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (kcp_session_stats_append(line, len, buffer) != 0) {
|
||||
free(buffer);
|
||||
return -1;
|
||||
}
|
||||
free(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
kcp_session_stats_logger_t *kcp_session_stats_open_jsonl(const char *path) {
|
||||
kcp_session_stats_logger_t *logger;
|
||||
FILE *file;
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
file = fopen(path, "ab");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
logger = (kcp_session_stats_logger_t *) calloc(1, sizeof(*logger));
|
||||
if (logger == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
omni_file_logger_init_path(&logger->file_logger, file, path, 0);
|
||||
logger->enabled = 1;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void kcp_session_stats_close(kcp_session_stats_logger_t *logger) {
|
||||
if (logger == NULL) {
|
||||
return;
|
||||
}
|
||||
if (logger->file_logger.file != NULL) {
|
||||
fclose(logger->file_logger.file);
|
||||
}
|
||||
omni_file_logger_destroy(&logger->file_logger);
|
||||
free(logger);
|
||||
}
|
||||
|
||||
int kcp_session_stats_log(kcp_session_stats_logger_t *logger, const kcp_session_stats_record_t *record) {
|
||||
char *record_type = NULL;
|
||||
char *node_role = NULL;
|
||||
char *node_id = NULL;
|
||||
char *local_addr = NULL;
|
||||
char *remote_addr = NULL;
|
||||
char *sample_reason = NULL;
|
||||
char *line = NULL;
|
||||
size_t line_len = 0;
|
||||
|
||||
if (logger == NULL || record == NULL || !logger->enabled) {
|
||||
return 0;
|
||||
}
|
||||
record_type = omni_json_escape(record->record_type);
|
||||
node_role = omni_json_escape(record->node_role);
|
||||
node_id = omni_json_escape(record->node_id);
|
||||
local_addr = omni_json_escape(record->local_addr);
|
||||
remote_addr = omni_json_escape(record->remote_addr);
|
||||
sample_reason = omni_json_escape(record->sample_reason);
|
||||
if (record_type == NULL || node_role == NULL || node_id == NULL || local_addr == NULL || remote_addr == NULL || sample_reason == NULL) {
|
||||
free(record_type);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(sample_reason);
|
||||
return -1;
|
||||
}
|
||||
line = omni_strdup("");
|
||||
if (line == NULL) {
|
||||
free(record_type);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(sample_reason);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (kcp_session_stats_appendf(&line, &line_len, "{\"record_type\":\"%s\",\"node_role\":\"%s\",\"node_id\":\"%s\",\"ts_unix_nano\":%" PRId64 ",\"sample_reason\":\"%s\"",
|
||||
record_type,
|
||||
node_role,
|
||||
node_id,
|
||||
record->ts_unix_nano,
|
||||
sample_reason) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->local_addr[0] != '\0' &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"local_addr\":\"%s\"", local_addr) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->remote_addr[0] != '\0' &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"remote_addr\":\"%s\"", remote_addr) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_conv &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"conv\":%u", record->conv) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_rto_ms &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"rto_ms\":%u", record->rto_ms) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_srtt_ms &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"srtt_ms\":%d", record->srtt_ms) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_min_srtt_ms &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"min_srtt_ms\":%d", record->min_srtt_ms) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_srttvar_ms &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"srttvar_ms\":%d", record->srttvar_ms) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_last_feedback_age_ms &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"last_feedback_age_ms\":%u", record->last_feedback_age_ms) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_snd_wnd &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"snd_wnd\":%u", record->snd_wnd) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_rmt_wnd &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"rmt_wnd\":%u", record->rmt_wnd) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_inflight &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"inflight\":%u", record->inflight) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_window_limit &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"window_limit\":%u", record->window_limit) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_window_pressure_pct &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"window_pressure_pct\":%.3f", record->window_pressure_pct) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_bytes_sent &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"bytes_sent\":%" PRIu64, record->bytes_sent) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_bytes_received &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"bytes_received\":%" PRIu64, record->bytes_received) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_in_pkts &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"in_pkts\":%" PRIu64, record->in_pkts) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_out_pkts &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"out_pkts\":%" PRIu64, record->out_pkts) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_in_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"in_segs\":%" PRIu64, record->in_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_out_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"out_segs\":%" PRIu64, record->out_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_retrans_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"retrans_segs\":%" PRIu64, record->retrans_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_fast_retrans_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"fast_retrans_segs\":%" PRIu64, record->fast_retrans_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_early_retrans_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"early_retrans_segs\":%" PRIu64, record->early_retrans_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_lost_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"lost_segs\":%" PRIu64, record->lost_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_repeat_segs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"repeat_segs\":%" PRIu64, record->repeat_segs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_in_errs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"in_errs\":%" PRIu64, record->in_errs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_kcp_in_errs &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"kcp_in_errs\":%" PRIu64, record->kcp_in_errs) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_ring_buffer_snd_queue &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"ring_buffer_snd_queue\":%" PRIu64, record->ring_buffer_snd_queue) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_ring_buffer_rcv_queue &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"ring_buffer_rcv_queue\":%" PRIu64, record->ring_buffer_rcv_queue) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_ring_buffer_snd_buffer &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"ring_buffer_snd_buffer\":%" PRIu64, record->ring_buffer_snd_buffer) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (record->has_curr_estab &&
|
||||
kcp_session_stats_appendf(&line, &line_len, ",\"curr_estab\":%" PRIu64, record->curr_estab) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (kcp_session_stats_append(&line, &line_len, "}") != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
free(record_type);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(sample_reason);
|
||||
|
||||
if (omni_file_logger_write_line(&logger->file_logger, line) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
free(record_type);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(local_addr);
|
||||
free(remote_addr);
|
||||
free(sample_reason);
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
130
host/OmniSocketGo_add_camera/src/latencylog.c
Normal file
130
host/OmniSocketGo_add_camera/src/latencylog.c
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "latencylog.h"
|
||||
|
||||
static void latencylog_fill_event(latency_event_t *event, const char *node_role, const char *node_id, const char *event_name, int64_t ts_unix_nano, const message_t *msg) {
|
||||
memset(event, 0, sizeof(*event));
|
||||
event->ts_unix_nano = ts_unix_nano;
|
||||
snprintf(event->node_role, sizeof(event->node_role), "%s", node_role == NULL ? "" : node_role);
|
||||
snprintf(event->node_id, sizeof(event->node_id), "%s", node_id == NULL ? "" : node_id);
|
||||
snprintf(event->event, sizeof(event->event), "%s", event_name == NULL ? "" : event_name);
|
||||
event->message_type = msg->type;
|
||||
event->message_id = msg->id;
|
||||
snprintf(event->from, sizeof(event->from), "%s", msg->from);
|
||||
snprintf(event->to, sizeof(event->to), "%s", msg->to);
|
||||
snprintf(event->file_name, sizeof(event->file_name), "%s", msg->file_name);
|
||||
event->body_size = (int) msg->body_len;
|
||||
}
|
||||
|
||||
latency_logger_t *latencylog_open_jsonl(const char *path) {
|
||||
latency_logger_t *logger;
|
||||
FILE *file;
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
file = fopen(path, "ab");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
logger = (latency_logger_t *) calloc(1, sizeof(*logger));
|
||||
if (logger == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
omni_file_logger_init_path(&logger->file_logger, file, path, 0);
|
||||
logger->enabled = 1;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void latencylog_close(latency_logger_t *logger) {
|
||||
if (logger == NULL) {
|
||||
return;
|
||||
}
|
||||
if (logger->file_logger.file != NULL) {
|
||||
fclose(logger->file_logger.file);
|
||||
}
|
||||
omni_file_logger_destroy(&logger->file_logger);
|
||||
free(logger);
|
||||
}
|
||||
|
||||
int latencylog_log_event(latency_logger_t *logger, const latency_event_t *event) {
|
||||
char *node_role = NULL;
|
||||
char *node_id = NULL;
|
||||
char *event_name = NULL;
|
||||
char *from = NULL;
|
||||
char *to = NULL;
|
||||
char *file_name = NULL;
|
||||
char *line = NULL;
|
||||
|
||||
if (logger == NULL || event == NULL || !logger->enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
node_role = omni_json_escape(event->node_role);
|
||||
node_id = omni_json_escape(event->node_id);
|
||||
event_name = omni_json_escape(event->event);
|
||||
from = omni_json_escape(event->from);
|
||||
to = omni_json_escape(event->to);
|
||||
file_name = omni_json_escape(event->file_name);
|
||||
if (node_role == NULL || node_id == NULL || event_name == NULL || from == NULL || to == NULL || file_name == NULL) {
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(event_name);
|
||||
free(from);
|
||||
free(to);
|
||||
free(file_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
line = omni_strdup_printf(
|
||||
"{\"ts_unix_nano\":%" PRId64 ",\"node_role\":\"%s\",\"node_id\":\"%s\",\"event\":\"%s\",\"message_type\":\"%s\",\"message_id\":%" PRIu64 ",\"from\":\"%s\",\"to\":\"%s\",\"file_name\":\"%s\",\"body_size\":%d}",
|
||||
event->ts_unix_nano,
|
||||
node_role,
|
||||
node_id,
|
||||
event_name,
|
||||
protocol_message_type_name(event->message_type),
|
||||
event->message_id,
|
||||
from,
|
||||
to,
|
||||
file_name,
|
||||
event->body_size
|
||||
);
|
||||
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(event_name);
|
||||
free(from);
|
||||
free(to);
|
||||
free(file_name);
|
||||
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_file_logger_write_line(&logger->file_logger, line) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int latencylog_is_business_message(const message_t *msg) {
|
||||
if (msg == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return msg->type == MSG_TYPE_TEXT || msg->type == MSG_TYPE_FILE || msg->type == MSG_TYPE_BINARY;
|
||||
}
|
||||
|
||||
void latencylog_log_message_event(latency_logger_t *logger, const char *node_role, const char *node_id, const char *event_name, const message_t *msg) {
|
||||
latencylog_log_message_event_at(logger, node_role, node_id, event_name, omni_now_unix_nano(), msg);
|
||||
}
|
||||
|
||||
void latencylog_log_message_event_at(latency_logger_t *logger, const char *node_role, const char *node_id, const char *event_name, int64_t ts_unix_nano, const message_t *msg) {
|
||||
latency_event_t event;
|
||||
if (!latencylog_is_business_message(msg)) {
|
||||
return;
|
||||
}
|
||||
latencylog_fill_event(&event, node_role, node_id, event_name, ts_unix_nano, msg);
|
||||
(void) latencylog_log_event(logger, &event);
|
||||
}
|
||||
103
host/OmniSocketGo_add_camera/src/linux_timestamping.c
Normal file
103
host/OmniSocketGo_add_camera/src/linux_timestamping.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "linux_timestamping.h"
|
||||
#include "latencylog.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/errqueue.h>
|
||||
#include <linux/net_tstamp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
|
||||
static int64_t linux_timespec_to_ns(const struct timespec *ts) {
|
||||
if (ts == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return (int64_t) ts->tv_sec * 1000000000LL + ts->tv_nsec;
|
||||
}
|
||||
|
||||
int linux_timestamping_enable_udp_socket(int fd, int enable_rx) {
|
||||
int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SCHED | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_OPT_TSONLY;
|
||||
if (enable_rx) {
|
||||
flags |= SOF_TIMESTAMPING_RX_SOFTWARE;
|
||||
}
|
||||
return setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));
|
||||
}
|
||||
|
||||
int64_t linux_timestamping_parse_rx_timestamp(const struct msghdr *msg) {
|
||||
struct cmsghdr *cmsg;
|
||||
const struct scm_timestamping *timestamps;
|
||||
if (msg == NULL) {
|
||||
return 0;
|
||||
}
|
||||
for (cmsg = CMSG_FIRSTHDR((struct msghdr *) msg); cmsg != NULL; cmsg = CMSG_NXTHDR((struct msghdr *) msg, cmsg)) {
|
||||
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) {
|
||||
timestamps = (const struct scm_timestamping *) CMSG_DATA(cmsg);
|
||||
if (timestamps->ts[0].tv_sec != 0 || timestamps->ts[0].tv_nsec != 0) {
|
||||
return linux_timespec_to_ns(×tamps->ts[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int linux_timestamping_parse_tx_timestamp(const struct msghdr *msg, omni_tx_timestamp_event_t *out_event) {
|
||||
struct cmsghdr *cmsg;
|
||||
const struct scm_timestamping *timestamps = NULL;
|
||||
const struct sock_extended_err *sock_err = NULL;
|
||||
int64_t timestamp_ns = 0;
|
||||
|
||||
if (msg == NULL || out_event == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(out_event, 0, sizeof(*out_event));
|
||||
|
||||
for (cmsg = CMSG_FIRSTHDR((struct msghdr *) msg); cmsg != NULL; cmsg = CMSG_NXTHDR((struct msghdr *) msg, cmsg)) {
|
||||
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) {
|
||||
timestamps = (const struct scm_timestamping *) CMSG_DATA(cmsg);
|
||||
} else if ((cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) ||
|
||||
(cmsg->cmsg_level == SOL_IPV6 && cmsg->cmsg_type == IPV6_RECVERR)) {
|
||||
sock_err = (const struct sock_extended_err *) CMSG_DATA(cmsg);
|
||||
}
|
||||
}
|
||||
if (timestamps == NULL || sock_err == NULL) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
if (timestamps->ts[0].tv_sec != 0 || timestamps->ts[0].tv_nsec != 0) {
|
||||
timestamp_ns = linux_timespec_to_ns(×tamps->ts[0]);
|
||||
snprintf(out_event->event_name, sizeof(out_event->event_name), "%s", EVENT_A_TX_SOFTWARE);
|
||||
} else if (timestamps->ts[1].tv_sec != 0 || timestamps->ts[1].tv_nsec != 0) {
|
||||
timestamp_ns = linux_timespec_to_ns(×tamps->ts[1]);
|
||||
snprintf(out_event->event_name, sizeof(out_event->event_name), "%s", EVENT_A_TX_SCHED);
|
||||
} else {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
out_event->ts_unix_nano = timestamp_ns;
|
||||
out_event->ee_info = sock_err->ee_info;
|
||||
out_event->ee_data = sock_err->ee_data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int linux_timestamping_enable_udp_socket(int fd, int enable_rx) {
|
||||
(void) fd;
|
||||
(void) enable_rx;
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t linux_timestamping_parse_rx_timestamp(const struct msghdr *msg) {
|
||||
(void) msg;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int linux_timestamping_parse_tx_timestamp(const struct msghdr *msg, omni_tx_timestamp_event_t *out_event) {
|
||||
(void) msg;
|
||||
(void) out_event;
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
795
host/OmniSocketGo_add_camera/src/omni_common.c
Normal file
795
host/OmniSocketGo_add_camera/src/omni_common.c
Normal file
@@ -0,0 +1,795 @@
|
||||
#include "omni_common.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int64_t omni_now_unix_nano(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
return (int64_t) ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
||||
}
|
||||
|
||||
uint32_t omni_now_millis32(void) {
|
||||
struct timespec ts;
|
||||
uint64_t ms;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
ms = (uint64_t) ts.tv_sec * 1000ULL + (uint64_t) (ts.tv_nsec / 1000000L);
|
||||
return (uint32_t) (ms & 0xffffffffu);
|
||||
}
|
||||
|
||||
int omni_set_nonblocking(int fd, int enabled) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (enabled) {
|
||||
flags |= O_NONBLOCK;
|
||||
} else {
|
||||
flags &= ~O_NONBLOCK;
|
||||
}
|
||||
return fcntl(fd, F_SETFL, flags);
|
||||
}
|
||||
|
||||
int omni_parse_sockaddr(const char *raw, int passive, struct sockaddr_storage *addr, socklen_t *addr_len, int *family_out) {
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result = NULL;
|
||||
char host_copy[OMNI_MAX_ADDR_TEXT];
|
||||
char port_copy[32];
|
||||
const char *host = NULL;
|
||||
const char *service = NULL;
|
||||
const char *last_colon;
|
||||
size_t host_len;
|
||||
|
||||
if (raw == NULL || addr == NULL || addr_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_flags = passive ? AI_PASSIVE : 0;
|
||||
|
||||
last_colon = strrchr(raw, ':');
|
||||
if (last_colon == NULL) {
|
||||
host = passive ? NULL : raw;
|
||||
service = passive ? raw : "0";
|
||||
} else {
|
||||
host_len = (size_t) (last_colon - raw);
|
||||
if (host_len >= sizeof(host_copy)) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
memcpy(host_copy, raw, host_len);
|
||||
host_copy[host_len] = '\0';
|
||||
snprintf(port_copy, sizeof(port_copy), "%s", last_colon + 1);
|
||||
host = host_len == 0 ? NULL : host_copy;
|
||||
service = port_copy;
|
||||
}
|
||||
|
||||
if (getaddrinfo(host, service, &hints, &result) != 0 || result == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memcpy(addr, result->ai_addr, result->ai_addrlen);
|
||||
*addr_len = (socklen_t) result->ai_addrlen;
|
||||
if (family_out != NULL) {
|
||||
*family_out = result->ai_family;
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omni_clone_sockaddr(const struct sockaddr *src, socklen_t src_len, struct sockaddr_storage *dst, socklen_t *dst_len) {
|
||||
if (src == NULL || dst == NULL || dst_len == NULL || src_len > sizeof(*dst)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(dst, 0, sizeof(*dst));
|
||||
memcpy(dst, src, src_len);
|
||||
*dst_len = src_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *omni_sockaddr_to_string(const struct sockaddr *addr, socklen_t addr_len, char *buffer, size_t buffer_len) {
|
||||
char host[NI_MAXHOST];
|
||||
char service[NI_MAXSERV];
|
||||
|
||||
if (buffer == NULL || buffer_len == 0) {
|
||||
return "";
|
||||
}
|
||||
if (addr == NULL) {
|
||||
snprintf(buffer, buffer_len, "<nil>");
|
||||
return buffer;
|
||||
}
|
||||
if (getnameinfo(addr, addr_len, host, sizeof(host), service, sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
|
||||
snprintf(buffer, buffer_len, "<addr>");
|
||||
return buffer;
|
||||
}
|
||||
if (addr->sa_family == AF_INET6) {
|
||||
snprintf(buffer, buffer_len, "[%s]:%s", host, service);
|
||||
} else {
|
||||
snprintf(buffer, buffer_len, "%s:%s", host, service);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int omni_bind_device(int fd, const char *device) {
|
||||
#ifdef __linux__
|
||||
if (device == NULL || device[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
return setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, (socklen_t) strlen(device));
|
||||
#else
|
||||
(void) fd;
|
||||
(void) device;
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int omni_mkdir_single(const char *path) {
|
||||
if (mkdir(path, 0755) == 0 || errno == EEXIST) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int omni_ensure_dir(const char *path) {
|
||||
char tmp[PATH_MAX];
|
||||
size_t i;
|
||||
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
if (strlen(path) >= sizeof(tmp)) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
snprintf(tmp, sizeof(tmp), "%s", path);
|
||||
for (i = 1; tmp[i] != '\0'; ++i) {
|
||||
if (tmp[i] == '/') {
|
||||
tmp[i] = '\0';
|
||||
if (tmp[0] != '\0' && omni_mkdir_single(tmp) != 0) {
|
||||
return -1;
|
||||
}
|
||||
tmp[i] = '/';
|
||||
}
|
||||
}
|
||||
return omni_mkdir_single(tmp);
|
||||
}
|
||||
|
||||
int omni_ensure_parent_dir(const char *path) {
|
||||
char tmp[PATH_MAX];
|
||||
char *slash;
|
||||
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
if (strlen(path) >= sizeof(tmp)) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
snprintf(tmp, sizeof(tmp), "%s", path);
|
||||
slash = strrchr(tmp, '/');
|
||||
if (slash == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (slash == tmp) {
|
||||
return omni_mkdir_single("/");
|
||||
}
|
||||
*slash = '\0';
|
||||
return omni_ensure_dir(tmp);
|
||||
}
|
||||
|
||||
int omni_read_file(const char *path, uint8_t **out, size_t *out_len) {
|
||||
FILE *file;
|
||||
long size;
|
||||
uint8_t *buffer;
|
||||
if (out == NULL || out_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*out = NULL;
|
||||
*out_len = 0;
|
||||
file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
size = ftell(file);
|
||||
if (size < 0) {
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
if (fseek(file, 0, SEEK_SET) != 0) {
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
buffer = (uint8_t *) malloc((size_t) size);
|
||||
if (size > 0 && buffer == NULL) {
|
||||
fclose(file);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
if ((size_t) size > 0 && fread(buffer, 1, (size_t) size, file) != (size_t) size) {
|
||||
free(buffer);
|
||||
fclose(file);
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
fclose(file);
|
||||
*out = buffer;
|
||||
*out_len = (size_t) size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omni_write_full_fd(int fd, const uint8_t *data, size_t len) {
|
||||
ssize_t written;
|
||||
while (len > 0) {
|
||||
written = write(fd, data, len);
|
||||
if (written < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (written == 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
data += written;
|
||||
len -= (size_t) written;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omni_write_file_internal(const char *path, const uint8_t *data, size_t len, const char *mode) {
|
||||
FILE *file;
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return -1;
|
||||
}
|
||||
file = fopen(path, mode);
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (len > 0 && fwrite(data, 1, len, file) != len) {
|
||||
fclose(file);
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
if (fclose(file) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int omni_append_file(const char *path, const uint8_t *data, size_t len) {
|
||||
return omni_write_file_internal(path, data, len, "ab");
|
||||
}
|
||||
|
||||
int omni_write_file(const char *path, const uint8_t *data, size_t len) {
|
||||
return omni_write_file_internal(path, data, len, "wb");
|
||||
}
|
||||
|
||||
int omni_random_u32(uint32_t *out) {
|
||||
uint8_t *cursor;
|
||||
size_t remaining;
|
||||
int fd;
|
||||
|
||||
if (out == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cursor = (uint8_t *) out;
|
||||
remaining = sizeof(*out);
|
||||
while (remaining > 0) {
|
||||
ssize_t n = read(fd, cursor, remaining);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
if (n == 0) {
|
||||
close(fd);
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
cursor += n;
|
||||
remaining -= (size_t) n;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if (*out == 0) {
|
||||
*out = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *omni_strdup(const char *src) {
|
||||
size_t len;
|
||||
char *dst;
|
||||
if (src == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
len = strlen(src);
|
||||
dst = (char *) malloc(len + 1U);
|
||||
if (dst == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(dst, src, len + 1U);
|
||||
return dst;
|
||||
}
|
||||
|
||||
char *omni_strdup_printf(const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_list copy;
|
||||
int needed;
|
||||
char *buffer;
|
||||
va_start(args, fmt);
|
||||
va_copy(copy, args);
|
||||
needed = vsnprintf(NULL, 0, fmt, copy);
|
||||
va_end(copy);
|
||||
if (needed < 0) {
|
||||
va_end(args);
|
||||
return NULL;
|
||||
}
|
||||
buffer = (char *) malloc((size_t) needed + 1U);
|
||||
if (buffer == NULL) {
|
||||
va_end(args);
|
||||
return NULL;
|
||||
}
|
||||
vsnprintf(buffer, (size_t) needed + 1U, fmt, args);
|
||||
va_end(args);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
char *omni_json_escape_bytes(const uint8_t *src, size_t len) {
|
||||
size_t i;
|
||||
size_t out_len = 0;
|
||||
char *out;
|
||||
char *cursor;
|
||||
|
||||
if (src == NULL) {
|
||||
if (len == 0) {
|
||||
return omni_strdup("");
|
||||
}
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
switch (src[i]) {
|
||||
case '\\':
|
||||
case '"':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
out_len += 2;
|
||||
break;
|
||||
default:
|
||||
out_len += src[i] < 0x20 ? 6U : 1U;
|
||||
break;
|
||||
}
|
||||
}
|
||||
out = (char *) malloc(out_len + 1U);
|
||||
if (out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
cursor = out;
|
||||
for (i = 0; i < len; ++i) {
|
||||
switch (src[i]) {
|
||||
case '\\':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = '\\';
|
||||
break;
|
||||
case '"':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = '"';
|
||||
break;
|
||||
case '\b':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 'b';
|
||||
break;
|
||||
case '\f':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 'f';
|
||||
break;
|
||||
case '\n':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 'n';
|
||||
break;
|
||||
case '\r':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 'r';
|
||||
break;
|
||||
case '\t':
|
||||
*cursor++ = '\\';
|
||||
*cursor++ = 't';
|
||||
break;
|
||||
default:
|
||||
if (src[i] < 0x20) {
|
||||
snprintf(cursor, 7, "\\u%04x", src[i]);
|
||||
cursor += 6;
|
||||
} else {
|
||||
*cursor++ = (char) src[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
*cursor = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
char *omni_json_escape(const char *src) {
|
||||
if (src == NULL) {
|
||||
return omni_strdup("");
|
||||
}
|
||||
return omni_json_escape_bytes((const uint8_t *) src, strlen(src));
|
||||
}
|
||||
|
||||
int omni_utf8_valid(const uint8_t *data, size_t len) {
|
||||
size_t i = 0;
|
||||
uint8_t c;
|
||||
while (i < len) {
|
||||
c = data[i];
|
||||
if (c <= 0x7f) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if ((c & 0xe0) == 0xc0) {
|
||||
if (i + 1 >= len || (data[i + 1] & 0xc0) != 0x80 || c < 0xc2) {
|
||||
return 0;
|
||||
}
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if ((c & 0xf0) == 0xe0) {
|
||||
if (i + 2 >= len || (data[i + 1] & 0xc0) != 0x80 || (data[i + 2] & 0xc0) != 0x80) {
|
||||
return 0;
|
||||
}
|
||||
if (c == 0xe0 && data[i + 1] < 0xa0) {
|
||||
return 0;
|
||||
}
|
||||
if (c == 0xed && data[i + 1] >= 0xa0) {
|
||||
return 0;
|
||||
}
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
if ((c & 0xf8) == 0xf0) {
|
||||
if (i + 3 >= len || (data[i + 1] & 0xc0) != 0x80 || (data[i + 2] & 0xc0) != 0x80 || (data[i + 3] & 0xc0) != 0x80) {
|
||||
return 0;
|
||||
}
|
||||
if (c == 0xf0 && data[i + 1] < 0x90) {
|
||||
return 0;
|
||||
}
|
||||
if (c > 0xf4 || (c == 0xf4 && data[i + 1] >= 0x90)) {
|
||||
return 0;
|
||||
}
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void omni_trim_newline(char *line) {
|
||||
size_t len;
|
||||
if (line == NULL) {
|
||||
return;
|
||||
}
|
||||
len = strlen(line);
|
||||
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
|
||||
line[--len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
int omni_parse_duration_ms(const char *raw, int default_ms, int *out_ms) {
|
||||
char *endptr;
|
||||
long value;
|
||||
if (out_ms == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (raw == NULL || raw[0] == '\0') {
|
||||
*out_ms = default_ms;
|
||||
return 0;
|
||||
}
|
||||
value = strtol(raw, &endptr, 10);
|
||||
if (endptr == raw || value <= 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (*endptr == '\0' || strcmp(endptr, "ms") == 0) {
|
||||
*out_ms = (int) value;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(endptr, "s") == 0) {
|
||||
*out_ms = (int) (value * 1000L);
|
||||
return 0;
|
||||
}
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
double omni_duration_ms_to_ns(double ms) {
|
||||
return ms * 1000000.0;
|
||||
}
|
||||
|
||||
const char *omni_path_base_name(const char *path) {
|
||||
const char *slash;
|
||||
|
||||
if (path == NULL) {
|
||||
return "";
|
||||
}
|
||||
slash = strrchr(path, '/');
|
||||
return slash == NULL ? path : slash + 1;
|
||||
}
|
||||
|
||||
static uint64_t omni_now_monotonic_ms64(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t) ts.tv_sec * 1000ULL + (uint64_t) (ts.tv_nsec / 1000000L);
|
||||
}
|
||||
|
||||
static int omni_positive_int_env(const char *name, int default_value) {
|
||||
const char *raw = getenv(name);
|
||||
long parsed;
|
||||
char *endptr = NULL;
|
||||
|
||||
if (raw == NULL || raw[0] == '\0') {
|
||||
return default_value;
|
||||
}
|
||||
parsed = strtol(raw, &endptr, 10);
|
||||
if (endptr == raw || *endptr != '\0' || parsed <= 0) {
|
||||
return default_value;
|
||||
}
|
||||
return (int) parsed;
|
||||
}
|
||||
|
||||
static size_t omni_positive_size_env(const char *name, size_t default_value) {
|
||||
const char *raw = getenv(name);
|
||||
unsigned long long parsed;
|
||||
char *endptr = NULL;
|
||||
|
||||
if (raw == NULL || raw[0] == '\0') {
|
||||
return default_value;
|
||||
}
|
||||
parsed = strtoull(raw, &endptr, 10);
|
||||
if (endptr == raw || *endptr != '\0' || parsed == 0ULL) {
|
||||
return default_value;
|
||||
}
|
||||
return (size_t) parsed;
|
||||
}
|
||||
|
||||
static int omni_file_logger_flush_locked(omni_file_logger_t *logger, uint64_t now_ms) {
|
||||
if (logger == NULL || logger->file == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (fflush(logger->file) != 0) {
|
||||
return -1;
|
||||
}
|
||||
logger->buffered_bytes = 0U;
|
||||
logger->last_flush_monotonic_ms = now_ms;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omni_build_rotated_path(char *buffer, size_t buffer_len, const char *path, int suffix) {
|
||||
size_t path_len;
|
||||
int written;
|
||||
|
||||
if (buffer == NULL || buffer_len == 0U || path == NULL || path[0] == '\0') {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
path_len = strlen(path);
|
||||
if (path_len + 16U >= buffer_len) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
memcpy(buffer, path, path_len);
|
||||
written = snprintf(buffer + path_len, buffer_len - path_len, ".%d", suffix);
|
||||
if (written < 0 || (size_t) written >= buffer_len - path_len) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omni_file_logger_reopen_append_locked(omni_file_logger_t *logger) {
|
||||
struct stat st;
|
||||
FILE *file;
|
||||
|
||||
if (logger == NULL || logger->path[0] == '\0') {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
file = fopen(logger->path, "ab");
|
||||
if (file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
logger->file = file;
|
||||
logger->current_bytes = 0U;
|
||||
if (stat(logger->path, &st) == 0) {
|
||||
logger->current_bytes = (size_t) st.st_size;
|
||||
}
|
||||
logger->buffered_bytes = 0U;
|
||||
logger->last_flush_monotonic_ms = omni_now_monotonic_ms64();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omni_file_logger_recover_after_rotate_locked(omni_file_logger_t *logger, const char *rotated_current_path) {
|
||||
int reopen_errno;
|
||||
|
||||
if (omni_file_logger_reopen_append_locked(logger) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
reopen_errno = errno;
|
||||
if (rotated_current_path != NULL && rotated_current_path[0] != '\0') {
|
||||
if (rename(rotated_current_path, logger->path) == 0) {
|
||||
if (omni_file_logger_reopen_append_locked(logger) == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errno = reopen_errno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int omni_file_logger_rotate_locked(omni_file_logger_t *logger) {
|
||||
int index;
|
||||
int saved_errno = 0;
|
||||
int should_recover = 0;
|
||||
char rotated_current_path[PATH_MAX];
|
||||
char from_path[PATH_MAX];
|
||||
char to_path[PATH_MAX];
|
||||
|
||||
if (logger == NULL || logger->path[0] == '\0' || logger->max_bytes == 0U || logger->max_files <= 0) {
|
||||
return 0;
|
||||
}
|
||||
rotated_current_path[0] = '\0';
|
||||
if (logger->file != NULL) {
|
||||
if (omni_file_logger_flush_locked(logger, omni_now_monotonic_ms64()) != 0) {
|
||||
return -1;
|
||||
}
|
||||
should_recover = 1;
|
||||
if (fclose(logger->file) != 0) {
|
||||
logger->file = NULL;
|
||||
saved_errno = errno;
|
||||
goto recover;
|
||||
}
|
||||
logger->file = NULL;
|
||||
}
|
||||
|
||||
if (omni_build_rotated_path(from_path, sizeof(from_path), logger->path, logger->max_files) != 0) {
|
||||
saved_errno = errno;
|
||||
goto recover;
|
||||
}
|
||||
unlink(from_path);
|
||||
for (index = logger->max_files - 1; index >= 1; --index) {
|
||||
if (omni_build_rotated_path(from_path, sizeof(from_path), logger->path, index) != 0 ||
|
||||
omni_build_rotated_path(to_path, sizeof(to_path), logger->path, index + 1) != 0) {
|
||||
saved_errno = errno;
|
||||
goto recover;
|
||||
}
|
||||
if (rename(from_path, to_path) != 0 && errno != ENOENT) {
|
||||
saved_errno = errno;
|
||||
goto recover;
|
||||
}
|
||||
}
|
||||
if (omni_build_rotated_path(to_path, sizeof(to_path), logger->path, 1) != 0) {
|
||||
saved_errno = errno;
|
||||
goto recover;
|
||||
}
|
||||
if (rename(logger->path, to_path) != 0 && errno != ENOENT) {
|
||||
saved_errno = errno;
|
||||
goto recover;
|
||||
}
|
||||
snprintf(rotated_current_path, sizeof(rotated_current_path), "%s", to_path);
|
||||
|
||||
if (omni_file_logger_reopen_append_locked(logger) != 0) {
|
||||
saved_errno = errno;
|
||||
goto recover;
|
||||
}
|
||||
return 0;
|
||||
|
||||
recover:
|
||||
if (should_recover) {
|
||||
int recover_errno = saved_errno != 0 ? saved_errno : errno;
|
||||
if (omni_file_logger_recover_after_rotate_locked(logger, rotated_current_path) == 0) {
|
||||
errno = recover_errno;
|
||||
} else if (saved_errno != 0) {
|
||||
errno = saved_errno;
|
||||
}
|
||||
} else if (saved_errno != 0) {
|
||||
errno = saved_errno;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void omni_file_logger_init(omni_file_logger_t *logger, FILE *file) {
|
||||
memset(logger, 0, sizeof(*logger));
|
||||
logger->file = file;
|
||||
pthread_mutex_init(&logger->mutex, NULL);
|
||||
logger->flush_bytes = 1U;
|
||||
logger->flush_interval_ms = 0;
|
||||
logger->immediate_flush = 1;
|
||||
logger->last_flush_monotonic_ms = omni_now_monotonic_ms64();
|
||||
}
|
||||
|
||||
void omni_file_logger_init_path(omni_file_logger_t *logger, FILE *file, const char *path, int immediate_flush) {
|
||||
struct stat st;
|
||||
|
||||
omni_file_logger_init(logger, file);
|
||||
if (path != NULL && path[0] != '\0') {
|
||||
snprintf(logger->path, sizeof(logger->path), "%s", path);
|
||||
if (stat(path, &st) == 0) {
|
||||
logger->current_bytes = (size_t) st.st_size;
|
||||
}
|
||||
}
|
||||
logger->flush_bytes = omni_positive_size_env("BLITZ_JSONL_FLUSH_BYTES", 262144U);
|
||||
logger->flush_interval_ms = omni_positive_int_env("BLITZ_JSONL_FLUSH_INTERVAL_MS", 1000);
|
||||
logger->max_bytes = omni_positive_size_env("BLITZ_JSONL_ROTATE_BYTES", 134217728U);
|
||||
logger->max_files = omni_positive_int_env("BLITZ_JSONL_ROTATE_FILES", 8);
|
||||
logger->immediate_flush = immediate_flush != 0;
|
||||
}
|
||||
|
||||
void omni_file_logger_destroy(omni_file_logger_t *logger) {
|
||||
pthread_mutex_destroy(&logger->mutex);
|
||||
}
|
||||
|
||||
int omni_file_logger_write_line(omni_file_logger_t *logger, const char *line) {
|
||||
int rc = 0;
|
||||
size_t line_len;
|
||||
uint64_t now_ms;
|
||||
if (logger == NULL || logger->file == NULL || line == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
line_len = strlen(line) + 1U;
|
||||
now_ms = omni_now_monotonic_ms64();
|
||||
pthread_mutex_lock(&logger->mutex);
|
||||
if (fputs(line, logger->file) == EOF || fputc('\n', logger->file) == EOF) {
|
||||
rc = -1;
|
||||
} else {
|
||||
logger->current_bytes += line_len;
|
||||
logger->buffered_bytes += line_len;
|
||||
if (logger->immediate_flush ||
|
||||
logger->buffered_bytes >= logger->flush_bytes ||
|
||||
(logger->flush_interval_ms > 0 && now_ms - logger->last_flush_monotonic_ms >= (uint64_t) logger->flush_interval_ms)) {
|
||||
if (omni_file_logger_flush_locked(logger, now_ms) != 0) {
|
||||
rc = -1;
|
||||
}
|
||||
}
|
||||
if (rc == 0 && logger->max_bytes > 0U && logger->current_bytes >= logger->max_bytes) {
|
||||
if (omni_file_logger_rotate_locked(logger) != 0) {
|
||||
rc = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&logger->mutex);
|
||||
return rc;
|
||||
}
|
||||
675
host/OmniSocketGo_add_camera/src/peer_kcp_client.c
Normal file
675
host/OmniSocketGo_add_camera/src/peer_kcp_client.c
Normal file
@@ -0,0 +1,675 @@
|
||||
#include "peer_kcp_client.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define KCP_CLIENT_REGISTER_TIMEOUT_MS 3000
|
||||
#define KCP_CLIENT_CTRL_REGISTER_OK "{\"type\":\"server_register_ok\"}"
|
||||
#define KCP_CLIENT_CTRL_PEER_REPLACED "{\"type\":\"server_peer_replaced\",\"reason\":\"new_instance_wins\"}"
|
||||
#define KCP_CLIENT_CTRL_HEARTBEAT "{\"type\":\"server_heartbeat\"}"
|
||||
#define KCP_CLIENT_CTRL_HEARTBEAT_ACK "{\"type\":\"server_heartbeat_ack\"}"
|
||||
|
||||
struct kcp_client {
|
||||
char id[OMNI_MAX_PEER_ID];
|
||||
char server_addr[OMNI_MAX_ADDR_TEXT];
|
||||
kcp_conn_t *conn;
|
||||
latency_logger_t *logger;
|
||||
pthread_mutex_t state_mu;
|
||||
uint64_t next_message_id;
|
||||
int registered;
|
||||
uint32_t last_server_activity_ms;
|
||||
char last_server_error[256];
|
||||
};
|
||||
|
||||
static int kcp_client_next_message_id(kcp_client_t *client, uint64_t *out_id) {
|
||||
if (client == NULL || out_id == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&client->state_mu);
|
||||
*out_id = ++client->next_message_id;
|
||||
pthread_mutex_unlock(&client->state_mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kcp_client_set_registered(kcp_client_t *client, int registered) {
|
||||
if (client == NULL) {
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&client->state_mu);
|
||||
client->registered = registered != 0;
|
||||
pthread_mutex_unlock(&client->state_mu);
|
||||
}
|
||||
|
||||
static void kcp_client_touch_server_activity(kcp_client_t *client) {
|
||||
if (client == NULL) {
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&client->state_mu);
|
||||
client->last_server_activity_ms = omni_now_millis32();
|
||||
pthread_mutex_unlock(&client->state_mu);
|
||||
}
|
||||
|
||||
static void kcp_client_set_last_server_error(kcp_client_t *client, const char *message) {
|
||||
if (client == NULL) {
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&client->state_mu);
|
||||
snprintf(client->last_server_error, sizeof(client->last_server_error), "%s", message == NULL ? "" : message);
|
||||
pthread_mutex_unlock(&client->state_mu);
|
||||
}
|
||||
|
||||
static void kcp_client_clear_last_server_error(kcp_client_t *client) {
|
||||
kcp_client_set_last_server_error(client, "");
|
||||
}
|
||||
|
||||
static int kcp_client_server_error_invalidates_registration(const char *message) {
|
||||
if (message == NULL || message[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
return strstr(message, "not registered") != NULL
|
||||
|| strstr(message, "first message must be register") != NULL
|
||||
|| strstr(message, "peer replaced") != NULL
|
||||
|| strstr(message, "timed out waiting for server_register_ok") != NULL;
|
||||
}
|
||||
|
||||
static int kcp_client_is_registered(kcp_client_t *client) {
|
||||
int registered;
|
||||
|
||||
if (client == NULL) {
|
||||
return 0;
|
||||
}
|
||||
pthread_mutex_lock(&client->state_mu);
|
||||
registered = client->registered;
|
||||
pthread_mutex_unlock(&client->state_mu);
|
||||
return registered;
|
||||
}
|
||||
|
||||
static int kcp_client_text_body_equals(const message_t *msg, const char *payload) {
|
||||
size_t expected_len;
|
||||
|
||||
if (msg == NULL || payload == NULL || msg->body == NULL) {
|
||||
return 0;
|
||||
}
|
||||
expected_len = strlen(payload);
|
||||
return msg->body_len == expected_len && memcmp(msg->body, payload, expected_len) == 0;
|
||||
}
|
||||
|
||||
static void kcp_client_copy_server_error_body(const message_t *msg, char *buffer, size_t buffer_len) {
|
||||
size_t copy_len;
|
||||
|
||||
if (buffer == NULL || buffer_len == 0) {
|
||||
return;
|
||||
}
|
||||
buffer[0] = '\0';
|
||||
if (msg == NULL || msg->body == NULL || msg->body_len == 0) {
|
||||
return;
|
||||
}
|
||||
copy_len = msg->body_len < (buffer_len - 1U) ? msg->body_len : (buffer_len - 1U);
|
||||
memcpy(buffer, msg->body, copy_len);
|
||||
buffer[copy_len] = '\0';
|
||||
}
|
||||
|
||||
static int kcp_client_registration_errno_from_message(const char *message) {
|
||||
if (message == NULL || message[0] == '\0') {
|
||||
return ECONNREFUSED;
|
||||
}
|
||||
if (strstr(message, "duplicate peer id") != NULL) {
|
||||
return EEXIST;
|
||||
}
|
||||
if (strstr(message, "first message must be register") != NULL) {
|
||||
return EPROTO;
|
||||
}
|
||||
return ECONNREFUSED;
|
||||
}
|
||||
|
||||
static int kcp_client_send_text_internal(kcp_client_t *client, const char *to, const char *text, int log_business_event) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
|
||||
if (client == NULL || to == NULL || text == NULL || client->conn == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protocol_message_init(&msg);
|
||||
if (kcp_client_next_message_id(client, &id) != 0) {
|
||||
return -1;
|
||||
}
|
||||
msg.type = MSG_TYPE_TEXT;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
msg.body = (uint8_t *) omni_strdup(text);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
msg.body_len = strlen((const char *) msg.body);
|
||||
if (log_business_event) {
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
}
|
||||
if (kcp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_client_send_business_preflight(kcp_client_t *client) {
|
||||
if (client == NULL || client->conn == NULL) {
|
||||
errno = ENOTCONN;
|
||||
return -1;
|
||||
}
|
||||
if (!kcp_client_is_registered(client)) {
|
||||
errno = ENOTCONN;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_client_handle_reserved_server_message(kcp_client_t *client, const message_t *msg) {
|
||||
if (client == NULL || msg == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (msg->type != MSG_TYPE_TEXT || strcmp(msg->from, SERVER_PEER_ID) != 0) {
|
||||
return 0;
|
||||
}
|
||||
kcp_client_touch_server_activity(client);
|
||||
if (kcp_client_text_body_equals(msg, KCP_CLIENT_CTRL_REGISTER_OK)) {
|
||||
kcp_client_set_registered(client, 1);
|
||||
kcp_client_clear_last_server_error(client);
|
||||
return 1;
|
||||
}
|
||||
if (kcp_client_text_body_equals(msg, KCP_CLIENT_CTRL_HEARTBEAT)) {
|
||||
if (kcp_client_send_text_internal(client, SERVER_PEER_ID, KCP_CLIENT_CTRL_HEARTBEAT_ACK, 0) != 0) {
|
||||
kcp_client_set_registered(client, 0);
|
||||
kcp_client_set_last_server_error(client, "failed to acknowledge server heartbeat");
|
||||
(void) kcp_conn_close(client->conn);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (kcp_client_text_body_equals(msg, KCP_CLIENT_CTRL_HEARTBEAT_ACK)) {
|
||||
return 1;
|
||||
}
|
||||
if (kcp_client_text_body_equals(msg, KCP_CLIENT_CTRL_PEER_REPLACED)) {
|
||||
kcp_client_set_registered(client, 0);
|
||||
kcp_client_set_last_server_error(client, "server peer replaced this session");
|
||||
(void) kcp_conn_close(client->conn);
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kcp_client_remaining_timeout_ms(int original_timeout_ms, uint32_t start_ms) {
|
||||
uint32_t elapsed_ms;
|
||||
|
||||
if (original_timeout_ms < 0) {
|
||||
return -1;
|
||||
}
|
||||
elapsed_ms = omni_now_millis32() - start_ms;
|
||||
if (elapsed_ms >= (uint32_t) original_timeout_ms) {
|
||||
return 0;
|
||||
}
|
||||
return original_timeout_ms - (int) elapsed_ms;
|
||||
}
|
||||
|
||||
static int kcp_client_wait_for_register_ok(kcp_client_t *client) {
|
||||
uint32_t start_ms;
|
||||
|
||||
if (client == NULL || client->conn == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
start_ms = omni_now_millis32();
|
||||
for (;;) {
|
||||
message_t msg;
|
||||
int rc;
|
||||
int remaining_timeout_ms = kcp_client_remaining_timeout_ms(KCP_CLIENT_REGISTER_TIMEOUT_MS, start_ms);
|
||||
|
||||
if (remaining_timeout_ms <= 0) {
|
||||
kcp_client_set_registered(client, 0);
|
||||
kcp_client_set_last_server_error(client, "timed out waiting for server_register_ok");
|
||||
(void) kcp_conn_close(client->conn);
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protocol_message_init(&msg);
|
||||
rc = kcp_conn_receive_timed(client->conn, &msg, remaining_timeout_ms);
|
||||
if (rc == 1) {
|
||||
protocol_message_clear(&msg);
|
||||
kcp_client_set_registered(client, 0);
|
||||
kcp_client_set_last_server_error(client, "timed out waiting for server_register_ok");
|
||||
(void) kcp_conn_close(client->conn);
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
if (rc != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
kcp_client_set_registered(client, 0);
|
||||
return -1;
|
||||
}
|
||||
if (msg.type == MSG_TYPE_ERROR && strcmp(msg.from, SERVER_PEER_ID) == 0) {
|
||||
char error_text[256];
|
||||
|
||||
kcp_client_copy_server_error_body(&msg, error_text, sizeof(error_text));
|
||||
kcp_client_touch_server_activity(client);
|
||||
kcp_client_set_registered(client, 0);
|
||||
kcp_client_set_last_server_error(client, error_text);
|
||||
protocol_message_clear(&msg);
|
||||
(void) kcp_conn_close(client->conn);
|
||||
errno = kcp_client_registration_errno_from_message(error_text);
|
||||
return -1;
|
||||
}
|
||||
rc = kcp_client_handle_reserved_server_message(client, &msg);
|
||||
protocol_message_clear(&msg);
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (rc > 0 && kcp_client_is_registered(client)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
kcp_client_set_registered(client, 0);
|
||||
kcp_client_set_last_server_error(client, "unexpected message while waiting for server_register_ok");
|
||||
(void) kcp_conn_close(client->conn);
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int kcp_client_receive_business_timed(kcp_client_t *client, message_t *out_msg, int timeout_ms) {
|
||||
uint32_t start_ms;
|
||||
|
||||
if (client == NULL || out_msg == NULL || client->conn == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
start_ms = omni_now_millis32();
|
||||
protocol_message_init(out_msg);
|
||||
for (;;) {
|
||||
int rc;
|
||||
int reserved_rc;
|
||||
int effective_timeout_ms = timeout_ms < 0 ? -1 : kcp_client_remaining_timeout_ms(timeout_ms, start_ms);
|
||||
|
||||
if (timeout_ms >= 0 && effective_timeout_ms <= 0) {
|
||||
return 1;
|
||||
}
|
||||
protocol_message_clear(out_msg);
|
||||
rc = kcp_conn_receive_timed(client->conn, out_msg, effective_timeout_ms);
|
||||
if (rc != 0) {
|
||||
if (rc != 1) {
|
||||
kcp_client_set_registered(client, 0);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (strcmp(out_msg->from, SERVER_PEER_ID) == 0) {
|
||||
kcp_client_touch_server_activity(client);
|
||||
}
|
||||
reserved_rc = kcp_client_handle_reserved_server_message(client, out_msg);
|
||||
if (reserved_rc < 0) {
|
||||
protocol_message_clear(out_msg);
|
||||
return -1;
|
||||
}
|
||||
if (reserved_rc > 0) {
|
||||
protocol_message_clear(out_msg);
|
||||
continue;
|
||||
}
|
||||
if (out_msg->type == MSG_TYPE_ERROR && strcmp(out_msg->from, SERVER_PEER_ID) == 0) {
|
||||
char error_text[256];
|
||||
|
||||
kcp_client_copy_server_error_body(out_msg, error_text, sizeof(error_text));
|
||||
kcp_client_set_last_server_error(client, error_text);
|
||||
if (kcp_client_server_error_invalidates_registration(error_text)) {
|
||||
kcp_client_set_registered(client, 0);
|
||||
}
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_APP_RECV, out_msg);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int kcp_client_persist_message_to_disk(const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len) {
|
||||
char path[512];
|
||||
|
||||
if (omni_ensure_dir(inbox_dir) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (msg->type == MSG_TYPE_TEXT) {
|
||||
char *body = omni_json_escape_bytes(msg->body, msg->body_len);
|
||||
char *from = omni_json_escape(msg->from);
|
||||
char *to = omni_json_escape(msg->to);
|
||||
char *line;
|
||||
|
||||
if (body == NULL || from == NULL || to == NULL) {
|
||||
free(body);
|
||||
free(from);
|
||||
free(to);
|
||||
return -1;
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/messages.log", inbox_dir);
|
||||
line = omni_strdup_printf(
|
||||
"{\"message_type\":\"%s\",\"message_id\":%" PRIu64 ",\"from\":\"%s\",\"to\":\"%s\",\"body\":\"%s\"}\n",
|
||||
protocol_message_type_name(msg->type),
|
||||
msg->id,
|
||||
from,
|
||||
to,
|
||||
body
|
||||
);
|
||||
free(body);
|
||||
free(from);
|
||||
free(to);
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_append_file(path, (const uint8_t *) line, strlen(line)) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
} else if (msg->type == MSG_TYPE_FILE) {
|
||||
const char *file_name = omni_path_base_name(msg->file_name);
|
||||
if (file_name[0] == '\0') {
|
||||
file_name = "unnamed";
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/%s-%" PRIu64 "-%s", inbox_dir, msg->from, msg->id, file_name);
|
||||
if (omni_write_file(path, msg->body, msg->body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
} else if (msg->type == MSG_TYPE_BINARY) {
|
||||
snprintf(path, sizeof(path), "%s/%s-%" PRIu64 ".bin", inbox_dir, msg->from, msg->id);
|
||||
if (omni_write_file(path, msg->body, msg->body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (out_path != NULL && out_path_len > 0) {
|
||||
snprintf(out_path, out_path_len, "%s", path);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kcp_client_fill_recv_meta(kcp_client_recv_meta_t *meta, const message_t *msg) {
|
||||
if (meta == NULL || msg == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(meta, 0, sizeof(*meta));
|
||||
meta->type = msg->type;
|
||||
meta->id = msg->id;
|
||||
meta->body_len = msg->body_len;
|
||||
snprintf(meta->from, sizeof(meta->from), "%s", msg->from);
|
||||
snprintf(meta->to, sizeof(meta->to), "%s", msg->to);
|
||||
snprintf(meta->file_name, sizeof(meta->file_name), "%s", msg->file_name);
|
||||
}
|
||||
|
||||
kcp_client_t *kcp_client_dial_with_options(const char *server_addr, const char *dial_addr, const char *peer_id, const char *bind_ip, const char *bind_device, const kcp_conn_options_t *options, latency_logger_t *logger, kcp_packet_debug_logger_t *packet_logger, kcp_session_stats_logger_t *stats_logger, int stats_interval_ms) {
|
||||
kcp_client_t *client;
|
||||
const char *actual_dial_addr = (dial_addr != NULL && dial_addr[0] != '\0') ? dial_addr : server_addr;
|
||||
message_t register_msg;
|
||||
int saved_errno = 0;
|
||||
|
||||
client = (kcp_client_t *) calloc(1, sizeof(*client));
|
||||
if (client == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
snprintf(client->id, sizeof(client->id), "%s", peer_id);
|
||||
snprintf(client->server_addr, sizeof(client->server_addr), "%s", server_addr == NULL ? "" : server_addr);
|
||||
pthread_mutex_init(&client->state_mu, NULL);
|
||||
client->last_server_activity_ms = omni_now_millis32();
|
||||
client->logger = logger;
|
||||
client->conn = kcp_conn_dial_with_options(actual_dial_addr, bind_ip, bind_device, options, packet_logger, logger, OMNI_NODE_ROLE_PEER, peer_id, stats_logger, stats_interval_ms);
|
||||
if (client->conn == NULL) {
|
||||
saved_errno = errno;
|
||||
kcp_client_free(client);
|
||||
errno = saved_errno;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
protocol_message_init(®ister_msg);
|
||||
register_msg.type = MSG_TYPE_REGISTER;
|
||||
register_msg.id = 0;
|
||||
snprintf(register_msg.from, sizeof(register_msg.from), "%s", peer_id);
|
||||
snprintf(register_msg.to, sizeof(register_msg.to), "%s", SERVER_PEER_ID);
|
||||
if (kcp_conn_send(client->conn, ®ister_msg) != 0) {
|
||||
saved_errno = errno;
|
||||
kcp_client_free(client);
|
||||
errno = saved_errno;
|
||||
return NULL;
|
||||
}
|
||||
if (kcp_client_wait_for_register_ok(client) != 0) {
|
||||
saved_errno = errno;
|
||||
kcp_client_free(client);
|
||||
errno = saved_errno;
|
||||
return NULL;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
kcp_client_t *kcp_client_dial(const char *server_addr, const char *dial_addr, const char *peer_id, const char *bind_ip, const char *bind_device, latency_logger_t *logger, kcp_packet_debug_logger_t *packet_logger, kcp_session_stats_logger_t *stats_logger, int stats_interval_ms) {
|
||||
return kcp_client_dial_with_options(server_addr, dial_addr, peer_id, bind_ip, bind_device, NULL, logger, packet_logger, stats_logger, stats_interval_ms);
|
||||
}
|
||||
|
||||
const char *kcp_client_id(const kcp_client_t *client) {
|
||||
return client == NULL ? "" : client->id;
|
||||
}
|
||||
|
||||
int kcp_client_send_text(kcp_client_t *client, const char *to, const char *text) {
|
||||
if (client == NULL || to == NULL || text == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (kcp_client_send_business_preflight(client) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return kcp_client_send_text_internal(client, to, text, 1);
|
||||
}
|
||||
|
||||
int kcp_client_send_binary(kcp_client_t *client, const char *to, const void *data, size_t data_len) {
|
||||
return kcp_client_send_binary_with_id(client, to, data, data_len, NULL);
|
||||
}
|
||||
|
||||
int kcp_client_send_binary_with_id(
|
||||
kcp_client_t *client,
|
||||
const char *to,
|
||||
const void *data,
|
||||
size_t data_len,
|
||||
uint64_t *out_id
|
||||
) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
|
||||
if (client == NULL || to == NULL || (data == NULL && data_len > 0)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (kcp_client_send_business_preflight(client) != 0) {
|
||||
return -1;
|
||||
}
|
||||
protocol_message_init(&msg);
|
||||
if (kcp_client_next_message_id(client, &id) != 0) {
|
||||
return -1;
|
||||
}
|
||||
msg.type = MSG_TYPE_BINARY;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
if (data_len > 0) {
|
||||
msg.body = (uint8_t *) malloc(data_len);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(msg.body, data, data_len);
|
||||
}
|
||||
msg.body_len = data_len;
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
if (kcp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
if (out_id != NULL) {
|
||||
*out_id = id;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_client_send_file_path(kcp_client_t *client, const char *to, const char *path) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
uint8_t *body = NULL;
|
||||
size_t body_len = 0;
|
||||
const char *base_name = strrchr(path, '/');
|
||||
|
||||
if (client == NULL || to == NULL || path == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (kcp_client_send_business_preflight(client) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_read_file(path, &body, &body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
protocol_message_init(&msg);
|
||||
if (kcp_client_next_message_id(client, &id) != 0) {
|
||||
free(body);
|
||||
return -1;
|
||||
}
|
||||
msg.type = MSG_TYPE_FILE;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
snprintf(msg.file_name, sizeof(msg.file_name), "%s", base_name == NULL ? path : base_name + 1);
|
||||
msg.body = body;
|
||||
msg.body_len = body_len;
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
if (kcp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_client_receive_timed(kcp_client_t *client, message_t *out_msg, int timeout_ms) {
|
||||
if (client == NULL || out_msg == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return kcp_client_receive_business_timed(client, out_msg, timeout_ms);
|
||||
}
|
||||
|
||||
int kcp_client_receive(kcp_client_t *client, message_t *out_msg) {
|
||||
if (kcp_client_receive_timed(client, out_msg, -1) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_client_receive_binary_into(kcp_client_t *client, void *buffer, size_t buffer_len, kcp_client_recv_meta_t *out_meta, int timeout_ms) {
|
||||
message_t msg;
|
||||
int rc;
|
||||
|
||||
if (client == NULL || (buffer == NULL && buffer_len > 0) || out_meta == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protocol_message_init(&msg);
|
||||
rc = kcp_client_receive_timed(client, &msg, timeout_ms);
|
||||
if (rc != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
kcp_client_fill_recv_meta(out_meta, &msg);
|
||||
if (msg.body_len > buffer_len) {
|
||||
protocol_message_clear(&msg);
|
||||
errno = EMSGSIZE;
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (msg.body_len > 0) {
|
||||
memcpy(buffer, msg.body, msg.body_len);
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kcp_client_persist_message(kcp_client_t *client, const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len) {
|
||||
if (!latencylog_is_business_message(msg)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_PERSIST_BEGIN, msg);
|
||||
if (kcp_client_persist_message_to_disk(msg, inbox_dir, out_path, out_path_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_PERSIST_END, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void kcp_client_state_snapshot(kcp_client_t *client, kcp_client_state_t *out_state) {
|
||||
kcp_runtime_stats_t runtime_stats;
|
||||
|
||||
if (out_state == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(out_state, 0, sizeof(*out_state));
|
||||
if (client == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(&runtime_stats, 0, sizeof(runtime_stats));
|
||||
if (client->conn != NULL) {
|
||||
kcp_conn_runtime_stats_snapshot(client->conn, &runtime_stats);
|
||||
out_state->connected = runtime_stats.connected;
|
||||
}
|
||||
pthread_mutex_lock(&client->state_mu);
|
||||
out_state->registered = client->registered;
|
||||
out_state->server_idle_ms = client->last_server_activity_ms == 0
|
||||
? 0
|
||||
: (omni_now_millis32() - client->last_server_activity_ms);
|
||||
snprintf(out_state->last_server_error, sizeof(out_state->last_server_error), "%s", client->last_server_error);
|
||||
pthread_mutex_unlock(&client->state_mu);
|
||||
}
|
||||
|
||||
void kcp_client_runtime_stats_snapshot(kcp_client_t *client, kcp_runtime_stats_t *out_stats) {
|
||||
if (out_stats == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(out_stats, 0, sizeof(*out_stats));
|
||||
if (client == NULL || client->conn == NULL) {
|
||||
return;
|
||||
}
|
||||
kcp_conn_runtime_stats_snapshot(client->conn, out_stats);
|
||||
}
|
||||
|
||||
int kcp_client_close(kcp_client_t *client) {
|
||||
if (client == NULL) {
|
||||
return 0;
|
||||
}
|
||||
kcp_client_set_registered(client, 0);
|
||||
return kcp_conn_close(client->conn);
|
||||
}
|
||||
|
||||
void kcp_client_free(kcp_client_t *client) {
|
||||
if (client == NULL) {
|
||||
return;
|
||||
}
|
||||
kcp_conn_free(client->conn);
|
||||
pthread_mutex_destroy(&client->state_mu);
|
||||
free(client);
|
||||
}
|
||||
297
host/OmniSocketGo_add_camera/src/peer_udp_client.c
Normal file
297
host/OmniSocketGo_add_camera/src/peer_udp_client.c
Normal file
@@ -0,0 +1,297 @@
|
||||
#include "peer_udp_client.h"
|
||||
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
|
||||
struct udp_client {
|
||||
char id[OMNI_MAX_PEER_ID];
|
||||
udp_conn_t *conn;
|
||||
latency_logger_t *logger;
|
||||
pthread_mutex_t id_mu;
|
||||
uint64_t next_message_id;
|
||||
};
|
||||
|
||||
static int client_next_message_id(udp_client_t *client, uint64_t *out_id) {
|
||||
pthread_mutex_lock(&client->id_mu);
|
||||
*out_id = ++client->next_message_id;
|
||||
pthread_mutex_unlock(&client->id_mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void udp_client_fill_recv_meta(udp_client_recv_meta_t *meta, const message_t *msg) {
|
||||
if (meta == NULL || msg == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(meta, 0, sizeof(*meta));
|
||||
meta->type = msg->type;
|
||||
meta->id = msg->id;
|
||||
meta->body_len = msg->body_len;
|
||||
snprintf(meta->from, sizeof(meta->from), "%s", msg->from);
|
||||
snprintf(meta->to, sizeof(meta->to), "%s", msg->to);
|
||||
snprintf(meta->file_name, sizeof(meta->file_name), "%s", msg->file_name);
|
||||
}
|
||||
|
||||
static int client_persist_message_to_disk(const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len) {
|
||||
char path[512];
|
||||
if (omni_ensure_dir(inbox_dir) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (msg->type == MSG_TYPE_TEXT) {
|
||||
char *body = omni_json_escape_bytes(msg->body, msg->body_len);
|
||||
char *from = omni_json_escape(msg->from);
|
||||
char *to = omni_json_escape(msg->to);
|
||||
char *line;
|
||||
if (body == NULL || from == NULL || to == NULL) {
|
||||
free(body);
|
||||
free(from);
|
||||
free(to);
|
||||
return -1;
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/messages.log", inbox_dir);
|
||||
line = omni_strdup_printf("{\"message_type\":\"%s\",\"message_id\":%" PRIu64 ",\"from\":\"%s\",\"to\":\"%s\",\"body\":\"%s\"}\n", protocol_message_type_name(msg->type), msg->id, from, to, body);
|
||||
free(body);
|
||||
free(from);
|
||||
free(to);
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_append_file(path, (const uint8_t *) line, strlen(line)) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
} else if (msg->type == MSG_TYPE_FILE) {
|
||||
const char *file_name = omni_path_base_name(msg->file_name);
|
||||
if (file_name[0] == '\0') {
|
||||
file_name = "unnamed";
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/%s-%" PRIu64 "-%s", inbox_dir, msg->from, msg->id, file_name);
|
||||
if (omni_write_file(path, msg->body, msg->body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
} else if (msg->type == MSG_TYPE_BINARY) {
|
||||
snprintf(path, sizeof(path), "%s/%s-%" PRIu64 ".bin", inbox_dir, msg->from, msg->id);
|
||||
if (omni_write_file(path, msg->body, msg->body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (out_path != NULL && out_path_len > 0) {
|
||||
snprintf(out_path, out_path_len, "%s", path);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
udp_client_t *udp_client_dial_with_options(const char *server_addr, const char *peer_id, const char *bind_ip, const char *bind_device, latency_logger_t *logger, tx_timestamp_debug_logger_t *debug_logger, int enable_timestamping) {
|
||||
udp_client_t *client;
|
||||
message_t register_msg;
|
||||
client = (udp_client_t *) calloc(1, sizeof(*client));
|
||||
if (client == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
snprintf(client->id, sizeof(client->id), "%s", peer_id);
|
||||
pthread_mutex_init(&client->id_mu, NULL);
|
||||
client->logger = logger;
|
||||
client->conn = udp_conn_dial(server_addr, bind_ip, bind_device, enable_timestamping, logger, OMNI_NODE_ROLE_PEER, peer_id, debug_logger);
|
||||
if (client->conn == NULL) {
|
||||
udp_client_free(client);
|
||||
return NULL;
|
||||
}
|
||||
protocol_message_init(®ister_msg);
|
||||
register_msg.type = MSG_TYPE_REGISTER;
|
||||
register_msg.id = 0;
|
||||
snprintf(register_msg.from, sizeof(register_msg.from), "%s", peer_id);
|
||||
snprintf(register_msg.to, sizeof(register_msg.to), "%s", SERVER_PEER_ID);
|
||||
if (udp_conn_send(client->conn, ®ister_msg) != 0) {
|
||||
udp_client_free(client);
|
||||
return NULL;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
udp_client_t *udp_client_dial(const char *server_addr, const char *peer_id, const char *bind_ip, latency_logger_t *logger, tx_timestamp_debug_logger_t *debug_logger, int enable_timestamping) {
|
||||
return udp_client_dial_with_options(server_addr, peer_id, bind_ip, NULL, logger, debug_logger, enable_timestamping);
|
||||
}
|
||||
|
||||
const char *udp_client_id(const udp_client_t *client) {
|
||||
return client == NULL ? "" : client->id;
|
||||
}
|
||||
|
||||
int udp_client_send_text(udp_client_t *client, const char *to, const char *text) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
protocol_message_init(&msg);
|
||||
client_next_message_id(client, &id);
|
||||
msg.type = MSG_TYPE_TEXT;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
msg.body = (uint8_t *) omni_strdup(text);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
msg.body_len = strlen((const char *) msg.body);
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
if (udp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_send_binary(udp_client_t *client, const char *to, const void *data, size_t data_len) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
|
||||
if (client == NULL || to == NULL || (data == NULL && data_len > 0)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protocol_message_init(&msg);
|
||||
client_next_message_id(client, &id);
|
||||
msg.type = MSG_TYPE_BINARY;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
if (data_len > 0) {
|
||||
msg.body = (uint8_t *) malloc(data_len);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(msg.body, data, data_len);
|
||||
}
|
||||
msg.body_len = data_len;
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
if (udp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_send_file_path(udp_client_t *client, const char *to, const char *path) {
|
||||
message_t msg;
|
||||
uint64_t id;
|
||||
uint8_t *body = NULL;
|
||||
size_t body_len = 0;
|
||||
const char *base_name = strrchr(path, '/');
|
||||
if (omni_read_file(path, &body, &body_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
protocol_message_init(&msg);
|
||||
client_next_message_id(client, &id);
|
||||
msg.type = MSG_TYPE_FILE;
|
||||
msg.id = id;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", client->id);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to);
|
||||
snprintf(msg.file_name, sizeof(msg.file_name), "%s", base_name == NULL ? path : base_name + 1);
|
||||
msg.body = body;
|
||||
msg.body_len = body_len;
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_A_APP_PREP_BEGIN, &msg);
|
||||
if (udp_conn_send(client->conn, &msg) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_receive_timed(udp_client_t *client, message_t *out_msg, int timeout_ms) {
|
||||
if (client == NULL || out_msg == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (timeout_ms >= 0) {
|
||||
struct pollfd pfd;
|
||||
int rc;
|
||||
|
||||
memset(&pfd, 0, sizeof(pfd));
|
||||
pfd.fd = udp_conn_fd(client->conn);
|
||||
pfd.events = POLLIN | POLLERR | POLLHUP;
|
||||
do {
|
||||
rc = poll(&pfd, 1, timeout_ms);
|
||||
} while (rc < 0 && errno == EINTR);
|
||||
if (rc == 0) {
|
||||
return 1;
|
||||
}
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
if ((pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0 && (pfd.revents & POLLIN) == 0) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (udp_conn_receive(client->conn, out_msg, NULL, NULL) != 0) {
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_APP_RECV, out_msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_receive(udp_client_t *client, message_t *out_msg) {
|
||||
return udp_client_receive_timed(client, out_msg, -1);
|
||||
}
|
||||
|
||||
int udp_client_receive_into(udp_client_t *client, void *buffer, size_t buffer_len, udp_client_recv_meta_t *out_meta, int timeout_ms) {
|
||||
message_t msg;
|
||||
int rc;
|
||||
|
||||
if (client == NULL || (buffer == NULL && buffer_len > 0) || out_meta == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protocol_message_init(&msg);
|
||||
rc = udp_client_receive_timed(client, &msg, timeout_ms);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
udp_client_fill_recv_meta(out_meta, &msg);
|
||||
if (msg.body_len > buffer_len) {
|
||||
protocol_message_clear(&msg);
|
||||
errno = EMSGSIZE;
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (msg.body_len > 0) {
|
||||
memcpy(buffer, msg.body, msg.body_len);
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_persist_message(udp_client_t *client, const message_t *msg, const char *inbox_dir, char *out_path, size_t out_path_len) {
|
||||
if (!latencylog_is_business_message(msg)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_PERSIST_BEGIN, msg);
|
||||
if (client_persist_message_to_disk(msg, inbox_dir, out_path, out_path_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(client->logger, OMNI_NODE_ROLE_PEER, client->id, EVENT_B_PERSIST_END, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_client_close(udp_client_t *client) {
|
||||
return client == NULL ? 0 : udp_conn_close(client->conn);
|
||||
}
|
||||
|
||||
void udp_client_free(udp_client_t *client) {
|
||||
if (client == NULL) {
|
||||
return;
|
||||
}
|
||||
udp_conn_free(client->conn);
|
||||
pthread_mutex_destroy(&client->id_mu);
|
||||
free(client);
|
||||
}
|
||||
415
host/OmniSocketGo_add_camera/src/protocol.c
Normal file
415
host/OmniSocketGo_add_camera/src/protocol.c
Normal file
@@ -0,0 +1,415 @@
|
||||
#include "protocol.h"
|
||||
|
||||
#include "cJSON.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
static const char *protocol_message_type_table[] = {
|
||||
"text",
|
||||
"file",
|
||||
"register",
|
||||
"error",
|
||||
"binary"
|
||||
};
|
||||
|
||||
const char *protocol_message_type_name(message_type_t type) {
|
||||
if ((int) type < 0 || (size_t) type >= OMNI_ARRAY_LEN(protocol_message_type_table)) {
|
||||
return "invalid";
|
||||
}
|
||||
return protocol_message_type_table[type];
|
||||
}
|
||||
|
||||
int protocol_message_type_from_name(const char *raw, message_type_t *out) {
|
||||
size_t i;
|
||||
if (raw == NULL || out == NULL) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < OMNI_ARRAY_LEN(protocol_message_type_table); ++i) {
|
||||
if (strcmp(raw, protocol_message_type_table[i]) == 0) {
|
||||
*out = (message_type_t) i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void protocol_message_init(message_t *msg) {
|
||||
if (msg == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(msg, 0, sizeof(*msg));
|
||||
msg->type = MSG_TYPE_INVALID;
|
||||
}
|
||||
|
||||
void protocol_message_clear(message_t *msg) {
|
||||
if (msg == NULL) {
|
||||
return;
|
||||
}
|
||||
free(msg->body);
|
||||
protocol_message_init(msg);
|
||||
}
|
||||
|
||||
int protocol_message_copy(message_t *dst, const message_t *src) {
|
||||
if (dst == NULL || src == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(dst);
|
||||
memcpy(dst, src, sizeof(*dst));
|
||||
dst->body = NULL;
|
||||
if (src->body_len > 0) {
|
||||
dst->body = (uint8_t *) malloc(src->body_len);
|
||||
if (dst->body == NULL) {
|
||||
protocol_message_init(dst);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(dst->body, src->body, src->body_len);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int protocol_set_err(char *err, size_t err_len, const char *fmt, ...) {
|
||||
va_list args;
|
||||
if (err != NULL && err_len > 0) {
|
||||
va_start(args, fmt);
|
||||
vsnprintf(err, err_len, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int protocol_validate_message(const message_t *msg, char *err, size_t err_len) {
|
||||
if (msg == NULL) {
|
||||
return protocol_set_err(err, err_len, "protocol: nil message");
|
||||
}
|
||||
if (msg->from[0] == '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: missing from");
|
||||
}
|
||||
if (msg->to[0] == '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: missing to");
|
||||
}
|
||||
switch (msg->type) {
|
||||
case MSG_TYPE_TEXT:
|
||||
if (msg->file_name[0] != '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: unexpected file name");
|
||||
}
|
||||
if (!omni_utf8_valid(msg->body, msg->body_len)) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid text body");
|
||||
}
|
||||
break;
|
||||
case MSG_TYPE_FILE:
|
||||
if (msg->file_name[0] == '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: missing file name");
|
||||
}
|
||||
break;
|
||||
case MSG_TYPE_BINARY:
|
||||
if (msg->file_name[0] != '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: unexpected file name");
|
||||
}
|
||||
break;
|
||||
case MSG_TYPE_REGISTER:
|
||||
if (strcmp(msg->to, SERVER_PEER_ID) != 0) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid register target");
|
||||
}
|
||||
if (msg->file_name[0] != '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: unexpected file name");
|
||||
}
|
||||
if (msg->body_len != 0) {
|
||||
return protocol_set_err(err, err_len, "protocol: unexpected body");
|
||||
}
|
||||
break;
|
||||
case MSG_TYPE_ERROR:
|
||||
if (strcmp(msg->from, SERVER_PEER_ID) != 0) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid error source");
|
||||
}
|
||||
if (msg->file_name[0] != '\0') {
|
||||
return protocol_set_err(err, err_len, "protocol: unexpected file name");
|
||||
}
|
||||
if (!omni_utf8_valid(msg->body, msg->body_len)) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid text body");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return protocol_set_err(err, err_len, "protocol: invalid message type");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int protocol_build_header_json(const message_t *msg, char **out_json, size_t *out_len) {
|
||||
cJSON *root;
|
||||
char *json;
|
||||
|
||||
root = cJSON_CreateObject();
|
||||
if (root == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
cJSON_AddStringToObject(root, "type", protocol_message_type_name(msg->type));
|
||||
cJSON_AddNumberToObject(root, "id", (double) msg->id);
|
||||
cJSON_AddStringToObject(root, "from", msg->from);
|
||||
cJSON_AddStringToObject(root, "to", msg->to);
|
||||
if (msg->file_name[0] != '\0') {
|
||||
cJSON_AddStringToObject(root, "file_name", msg->file_name);
|
||||
}
|
||||
cJSON_AddNumberToObject(root, "content_length", (double) msg->body_len);
|
||||
json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
if (json == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
*out_len = strlen(json);
|
||||
*out_json = json;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_encode_message_datagram(const message_t *msg, uint8_t **out, size_t *out_len) {
|
||||
uint8_t *buffer;
|
||||
char *header_json;
|
||||
size_t header_len;
|
||||
uint32_t net_header_len;
|
||||
char err[128];
|
||||
|
||||
if (out == NULL || out_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*out = NULL;
|
||||
*out_len = 0;
|
||||
if (protocol_validate_message(msg, err, sizeof(err)) != 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (protocol_build_header_json(msg, &header_json, &header_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (4U + header_len + msg->body_len > OMNI_MAX_FRAME_SIZE) {
|
||||
cJSON_free(header_json);
|
||||
errno = EMSGSIZE;
|
||||
return -1;
|
||||
}
|
||||
buffer = (uint8_t *) malloc(4U + header_len + msg->body_len);
|
||||
if (buffer == NULL) {
|
||||
cJSON_free(header_json);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
net_header_len = htonl((uint32_t) header_len);
|
||||
memcpy(buffer, &net_header_len, 4);
|
||||
memcpy(buffer + 4, header_json, header_len);
|
||||
if (msg->body_len > 0) {
|
||||
memcpy(buffer + 4 + header_len, msg->body, msg->body_len);
|
||||
}
|
||||
cJSON_free(header_json);
|
||||
*out = buffer;
|
||||
*out_len = 4U + header_len + msg->body_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int protocol_copy_string_field(char *dst, size_t dst_len, const cJSON *object, const char *field, int required, char *err, size_t err_len) {
|
||||
const cJSON *item = cJSON_GetObjectItemCaseSensitive(object, field);
|
||||
if (item == NULL) {
|
||||
if (required) {
|
||||
return protocol_set_err(err, err_len, "protocol: missing %s", field);
|
||||
}
|
||||
dst[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
if (!cJSON_IsString(item) || item->valuestring == NULL) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid %s", field);
|
||||
}
|
||||
snprintf(dst, dst_len, "%s", item->valuestring);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int protocol_copy_u64_field(uint64_t *dst, const cJSON *object, const char *field, int required, char *err, size_t err_len) {
|
||||
const cJSON *item = cJSON_GetObjectItemCaseSensitive(object, field);
|
||||
if (item == NULL) {
|
||||
if (required) {
|
||||
return protocol_set_err(err, err_len, "protocol: missing %s", field);
|
||||
}
|
||||
*dst = 0;
|
||||
return 0;
|
||||
}
|
||||
if (!cJSON_IsNumber(item)) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid %s", field);
|
||||
}
|
||||
*dst = (uint64_t) item->valuedouble;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_decode_message_datagram(const uint8_t *data, size_t data_len, message_t *out_msg, char *err, size_t err_len) {
|
||||
uint32_t net_header_len;
|
||||
uint32_t header_len;
|
||||
char *header_text = NULL;
|
||||
cJSON *header = NULL;
|
||||
const cJSON *type_item;
|
||||
uint64_t content_length = 0;
|
||||
|
||||
if (data == NULL || out_msg == NULL || data_len < 4U) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid datagram");
|
||||
}
|
||||
if (data_len > OMNI_MAX_FRAME_SIZE) {
|
||||
return protocol_set_err(err, err_len, "protocol: frame too large");
|
||||
}
|
||||
|
||||
protocol_message_clear(out_msg);
|
||||
|
||||
memcpy(&net_header_len, data, 4);
|
||||
header_len = ntohl(net_header_len);
|
||||
if (header_len == 0 || (size_t) header_len > data_len - 4U) {
|
||||
return protocol_set_err(err, err_len, "protocol: invalid header length");
|
||||
}
|
||||
header_text = (char *) malloc((size_t) header_len + 1U);
|
||||
if (header_text == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(header_text, data + 4, header_len);
|
||||
header_text[header_len] = '\0';
|
||||
header = cJSON_Parse(header_text);
|
||||
free(header_text);
|
||||
if (header == NULL || !cJSON_IsObject(header)) {
|
||||
if (header != NULL) {
|
||||
cJSON_Delete(header);
|
||||
}
|
||||
return protocol_set_err(err, err_len, "protocol: invalid header json");
|
||||
}
|
||||
type_item = cJSON_GetObjectItemCaseSensitive(header, "type");
|
||||
if (type_item == NULL || !cJSON_IsString(type_item) || protocol_message_type_from_name(type_item->valuestring, &out_msg->type) != 0) {
|
||||
cJSON_Delete(header);
|
||||
return protocol_set_err(err, err_len, "protocol: invalid message type");
|
||||
}
|
||||
if (protocol_copy_u64_field(&out_msg->id, header, "id", 1, err, err_len) != 0 ||
|
||||
protocol_copy_string_field(out_msg->from, sizeof(out_msg->from), header, "from", 1, err, err_len) != 0 ||
|
||||
protocol_copy_string_field(out_msg->to, sizeof(out_msg->to), header, "to", 1, err, err_len) != 0 ||
|
||||
protocol_copy_string_field(out_msg->file_name, sizeof(out_msg->file_name), header, "file_name", 0, err, err_len) != 0 ||
|
||||
protocol_copy_u64_field(&content_length, header, "content_length", 1, err, err_len) != 0) {
|
||||
cJSON_Delete(header);
|
||||
protocol_message_clear(out_msg);
|
||||
return -1;
|
||||
}
|
||||
cJSON_Delete(header);
|
||||
if ((size_t) content_length != data_len - 4U - (size_t) header_len) {
|
||||
protocol_message_clear(out_msg);
|
||||
return protocol_set_err(err, err_len, "protocol: invalid content length");
|
||||
}
|
||||
out_msg->body_len = (size_t) content_length;
|
||||
if (out_msg->body_len > 0) {
|
||||
out_msg->body = (uint8_t *) malloc(out_msg->body_len);
|
||||
if (out_msg->body == NULL) {
|
||||
protocol_message_clear(out_msg);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(out_msg->body, data + 4U + header_len, out_msg->body_len);
|
||||
}
|
||||
if (protocol_validate_message(out_msg, err, err_len) != 0) {
|
||||
protocol_message_clear(out_msg);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_encode_message_stream(const message_t *msg, uint8_t **out, size_t *out_len) {
|
||||
uint8_t *payload;
|
||||
uint8_t *buffer;
|
||||
size_t payload_len;
|
||||
uint32_t net_len;
|
||||
|
||||
if (protocol_encode_message_datagram(msg, &payload, &payload_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
buffer = (uint8_t *) malloc(payload_len + 4U);
|
||||
if (buffer == NULL) {
|
||||
free(payload);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
net_len = htonl((uint32_t) payload_len);
|
||||
memcpy(buffer, &net_len, 4);
|
||||
memcpy(buffer + 4, payload, payload_len);
|
||||
free(payload);
|
||||
*out = buffer;
|
||||
*out_len = payload_len + 4U;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_decode_message_stream_payload(const uint8_t *payload, size_t payload_len, message_t *out_msg, char *err, size_t err_len) {
|
||||
return protocol_decode_message_datagram(payload, payload_len, out_msg, err, err_len);
|
||||
}
|
||||
|
||||
void protocol_frame_decoder_init(protocol_frame_decoder_t *decoder) {
|
||||
memset(decoder, 0, sizeof(*decoder));
|
||||
}
|
||||
|
||||
void protocol_frame_decoder_reset(protocol_frame_decoder_t *decoder) {
|
||||
decoder->len = 0;
|
||||
}
|
||||
|
||||
void protocol_frame_decoder_destroy(protocol_frame_decoder_t *decoder) {
|
||||
free(decoder->buffer);
|
||||
memset(decoder, 0, sizeof(*decoder));
|
||||
}
|
||||
|
||||
int protocol_frame_decoder_feed(protocol_frame_decoder_t *decoder, const uint8_t *data, size_t data_len) {
|
||||
uint8_t *next_buffer;
|
||||
size_t next_cap;
|
||||
if (decoder->len + data_len > OMNI_MAX_FRAME_SIZE * 2U) {
|
||||
errno = EMSGSIZE;
|
||||
return -1;
|
||||
}
|
||||
if (decoder->len + data_len > decoder->cap) {
|
||||
next_cap = decoder->cap == 0 ? 4096U : decoder->cap;
|
||||
while (next_cap < decoder->len + data_len) {
|
||||
next_cap *= 2U;
|
||||
}
|
||||
next_buffer = (uint8_t *) realloc(decoder->buffer, next_cap);
|
||||
if (next_buffer == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
decoder->buffer = next_buffer;
|
||||
decoder->cap = next_cap;
|
||||
}
|
||||
memcpy(decoder->buffer + decoder->len, data, data_len);
|
||||
decoder->len += data_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_frame_decoder_next(protocol_frame_decoder_t *decoder, uint8_t **payload, size_t *payload_len) {
|
||||
uint32_t net_len;
|
||||
uint32_t frame_len;
|
||||
uint8_t *frame;
|
||||
|
||||
if (payload == NULL || payload_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
*payload = NULL;
|
||||
*payload_len = 0;
|
||||
if (decoder->len < 4U) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(&net_len, decoder->buffer, 4);
|
||||
frame_len = ntohl(net_len);
|
||||
if (frame_len == 0 || frame_len > OMNI_MAX_FRAME_SIZE) {
|
||||
errno = EMSGSIZE;
|
||||
return -1;
|
||||
}
|
||||
if (decoder->len < 4U + frame_len) {
|
||||
return 0;
|
||||
}
|
||||
frame = (uint8_t *) malloc(frame_len);
|
||||
if (frame == NULL) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(frame, decoder->buffer + 4, frame_len);
|
||||
memmove(decoder->buffer, decoder->buffer + 4U + frame_len, decoder->len - 4U - frame_len);
|
||||
decoder->len -= 4U + frame_len;
|
||||
*payload = frame;
|
||||
*payload_len = frame_len;
|
||||
return 1;
|
||||
}
|
||||
1136
host/OmniSocketGo_add_camera/src/server_kcp_hub.c
Normal file
1136
host/OmniSocketGo_add_camera/src/server_kcp_hub.c
Normal file
File diff suppressed because it is too large
Load Diff
181
host/OmniSocketGo_add_camera/src/server_udp_hub.c
Normal file
181
host/OmniSocketGo_add_camera/src/server_udp_hub.c
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "server_udp_hub.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct udp_peer_entry {
|
||||
struct udp_peer_entry *next;
|
||||
char peer_id[OMNI_MAX_PEER_ID];
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len;
|
||||
} udp_peer_entry_t;
|
||||
|
||||
struct udp_hub {
|
||||
udp_conn_t *conn;
|
||||
pthread_rwlock_t lock;
|
||||
udp_peer_entry_t *peers;
|
||||
};
|
||||
|
||||
static int udp_addr_equal(const struct sockaddr_storage *a, socklen_t a_len, const struct sockaddr_storage *b, socklen_t b_len) {
|
||||
return a_len == b_len && memcmp(a, b, a_len) == 0;
|
||||
}
|
||||
|
||||
static udp_peer_entry_t *udp_hub_find_by_id(udp_hub_t *hub, const char *peer_id) {
|
||||
udp_peer_entry_t *entry;
|
||||
for (entry = hub->peers; entry != NULL; entry = entry->next) {
|
||||
if (strcmp(entry->peer_id, peer_id) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static udp_peer_entry_t *udp_hub_find_by_addr(udp_hub_t *hub, const struct sockaddr_storage *addr, socklen_t addr_len) {
|
||||
udp_peer_entry_t *entry;
|
||||
for (entry = hub->peers; entry != NULL; entry = entry->next) {
|
||||
if (udp_addr_equal(&entry->addr, entry->addr_len, addr, addr_len)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int udp_hub_send_error(udp_hub_t *hub, const struct sockaddr_storage *addr, socklen_t addr_len, const char *to, const char *message) {
|
||||
message_t msg;
|
||||
protocol_message_init(&msg);
|
||||
msg.type = MSG_TYPE_ERROR;
|
||||
msg.id = 0;
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", SERVER_PEER_ID);
|
||||
snprintf(msg.to, sizeof(msg.to), "%s", to == NULL || to[0] == '\0' ? "unknown" : to);
|
||||
msg.body = (uint8_t *) omni_strdup(message);
|
||||
msg.body_len = msg.body == NULL ? 0 : strlen((const char *) msg.body);
|
||||
if (msg.body == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (udp_conn_send_to(hub->conn, &msg, (const struct sockaddr *) addr, addr_len) != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
udp_hub_t *udp_hub_open(const char *listen_addr, latency_logger_t *logger, tx_timestamp_debug_logger_t *debug_logger, int enable_timestamping) {
|
||||
udp_hub_t *hub = (udp_hub_t *) calloc(1, sizeof(*hub));
|
||||
if (hub == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
hub->conn = udp_conn_bind(listen_addr, NULL, enable_timestamping, logger, OMNI_NODE_ROLE_SERVER, "hub", debug_logger);
|
||||
if (hub->conn == NULL) {
|
||||
free(hub);
|
||||
return NULL;
|
||||
}
|
||||
pthread_rwlock_init(&hub->lock, NULL);
|
||||
return hub;
|
||||
}
|
||||
|
||||
int udp_hub_serve(udp_hub_t *hub) {
|
||||
message_t msg;
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len;
|
||||
udp_peer_entry_t *sender;
|
||||
udp_peer_entry_t *target;
|
||||
udp_peer_entry_t *entry;
|
||||
|
||||
if (hub == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protocol_message_init(&msg);
|
||||
for (;;) {
|
||||
protocol_message_clear(&msg);
|
||||
if (udp_conn_receive(hub->conn, &msg, &addr, &addr_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (msg.type == MSG_TYPE_REGISTER) {
|
||||
pthread_rwlock_wrlock(&hub->lock);
|
||||
entry = udp_hub_find_by_id(hub, msg.from);
|
||||
if (entry == NULL) {
|
||||
entry = (udp_peer_entry_t *) calloc(1, sizeof(*entry));
|
||||
if (entry == NULL) {
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
snprintf(entry->peer_id, sizeof(entry->peer_id), "%s", msg.from);
|
||||
entry->next = hub->peers;
|
||||
hub->peers = entry;
|
||||
}
|
||||
memcpy(&entry->addr, &addr, sizeof(addr));
|
||||
entry->addr_len = addr_len;
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
continue;
|
||||
}
|
||||
if (msg.type != MSG_TYPE_TEXT && msg.type != MSG_TYPE_FILE && msg.type != MSG_TYPE_BINARY) {
|
||||
if (msg.type == MSG_TYPE_ERROR) {
|
||||
udp_hub_send_error(hub, &addr, addr_len, msg.from, "peers cannot send error messages");
|
||||
} else {
|
||||
char *error_text = omni_strdup_printf("unsupported message type: %s", protocol_message_type_name(msg.type));
|
||||
if (error_text != NULL) {
|
||||
udp_hub_send_error(hub, &addr, addr_len, msg.from, error_text);
|
||||
free(error_text);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
pthread_rwlock_rdlock(&hub->lock);
|
||||
sender = udp_hub_find_by_addr(hub, &addr, addr_len);
|
||||
if (sender == NULL) {
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
udp_hub_send_error(hub, &addr, addr_len, msg.from, "not registered; send register first");
|
||||
continue;
|
||||
}
|
||||
snprintf(msg.from, sizeof(msg.from), "%s", sender->peer_id);
|
||||
target = udp_hub_find_by_id(hub, msg.to);
|
||||
if (target == NULL) {
|
||||
char *error_text;
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
error_text = omni_strdup_printf("unknown target: %s", msg.to);
|
||||
if (error_text != NULL) {
|
||||
udp_hub_send_error(hub, &addr, addr_len, sender->peer_id, error_text);
|
||||
free(error_text);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (udp_conn_send_to(hub->conn, &msg, (const struct sockaddr *) &target->addr, target->addr_len) != 0) {
|
||||
char *error_text;
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
error_text = omni_strdup_printf("failed to forward to %s", msg.to);
|
||||
if (error_text != NULL) {
|
||||
udp_hub_send_error(hub, &addr, addr_len, sender->peer_id, error_text);
|
||||
free(error_text);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
pthread_rwlock_unlock(&hub->lock);
|
||||
}
|
||||
}
|
||||
|
||||
int udp_hub_close(udp_hub_t *hub) {
|
||||
if (hub == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return udp_conn_close(hub->conn);
|
||||
}
|
||||
|
||||
void udp_hub_free(udp_hub_t *hub) {
|
||||
udp_peer_entry_t *entry;
|
||||
udp_peer_entry_t *next;
|
||||
if (hub == NULL) {
|
||||
return;
|
||||
}
|
||||
udp_conn_free(hub->conn);
|
||||
for (entry = hub->peers; entry != NULL; entry = next) {
|
||||
next = entry->next;
|
||||
free(entry);
|
||||
}
|
||||
pthread_rwlock_destroy(&hub->lock);
|
||||
free(hub);
|
||||
}
|
||||
613
host/OmniSocketGo_add_camera/src/server_udp_relay.c
Normal file
613
host/OmniSocketGo_add_camera/src/server_udp_relay.c
Normal file
@@ -0,0 +1,613 @@
|
||||
#include "server_udp_relay.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define UDP_RELAY_BUF_SIZE (64U * 1024U)
|
||||
#define UDP_RELAY_ROUTE_TIMEOUT_MS 30000U
|
||||
#define UDP_RELAY_DEFAULT_PACKET_LOG_SAMPLE_EVERY 200U
|
||||
|
||||
struct udp_relay {
|
||||
int downstream_fd;
|
||||
int upstream_fd;
|
||||
struct sockaddr_storage upstream_addr;
|
||||
socklen_t upstream_addr_len;
|
||||
char downstream_local_addr[OMNI_MAX_ADDR_TEXT];
|
||||
char upstream_local_addr[OMNI_MAX_ADDR_TEXT];
|
||||
struct sockaddr_storage client_addr;
|
||||
socklen_t client_addr_len;
|
||||
int has_client;
|
||||
uint32_t client_last_seen_ms;
|
||||
struct udp_relay_route *routes;
|
||||
pthread_mutex_t lock;
|
||||
pthread_mutex_t log_mu;
|
||||
unsigned int packet_log_sample_every;
|
||||
atomic_ullong packet_log_counter;
|
||||
pthread_mutex_t state_mu;
|
||||
pthread_cond_t state_cond;
|
||||
pthread_t downstream_thread;
|
||||
int downstream_thread_started;
|
||||
pthread_t upstream_thread;
|
||||
int upstream_thread_started;
|
||||
int worker_done;
|
||||
int worker_rc;
|
||||
int worker_errno;
|
||||
int closed;
|
||||
};
|
||||
|
||||
typedef struct udp_relay_route {
|
||||
struct udp_relay_route *next;
|
||||
uint32_t conv;
|
||||
struct sockaddr_storage client_addr;
|
||||
socklen_t client_addr_len;
|
||||
uint32_t last_seen_ms;
|
||||
} udp_relay_route_t;
|
||||
|
||||
static uint32_t udp_relay_now_ms(void) {
|
||||
return omni_now_millis32();
|
||||
}
|
||||
|
||||
static uint32_t udp_relay_elapsed_ms(uint32_t now_ms, uint32_t then_ms) {
|
||||
return now_ms - then_ms;
|
||||
}
|
||||
|
||||
static unsigned int udp_relay_packet_log_sample_every(void) {
|
||||
const char *raw = getenv("OMNI_RELAY_PACKET_LOG_SAMPLE_EVERY");
|
||||
unsigned long parsed;
|
||||
char *endptr = NULL;
|
||||
|
||||
if (raw == NULL || raw[0] == '\0') {
|
||||
return UDP_RELAY_DEFAULT_PACKET_LOG_SAMPLE_EVERY;
|
||||
}
|
||||
parsed = strtoul(raw, &endptr, 10);
|
||||
if (endptr == raw || *endptr != '\0') {
|
||||
return UDP_RELAY_DEFAULT_PACKET_LOG_SAMPLE_EVERY;
|
||||
}
|
||||
return (unsigned int) parsed;
|
||||
}
|
||||
|
||||
static int udp_relay_event_should_always_log(const char *event_name) {
|
||||
return event_name != NULL && strstr(event_name, "_drop_") != NULL;
|
||||
}
|
||||
|
||||
static int udp_relay_should_log_packet(udp_relay_t *relay, const char *event_name) {
|
||||
unsigned long long seq;
|
||||
|
||||
if (relay == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (udp_relay_event_should_always_log(event_name)) {
|
||||
return 1;
|
||||
}
|
||||
if (relay->packet_log_sample_every == 0U) {
|
||||
return 0;
|
||||
}
|
||||
if (relay->packet_log_sample_every == 1U) {
|
||||
return 1;
|
||||
}
|
||||
seq = atomic_fetch_add_explicit(&relay->packet_log_counter, 1U, memory_order_relaxed) + 1U;
|
||||
return (seq % (unsigned long long) relay->packet_log_sample_every) == 0U;
|
||||
}
|
||||
|
||||
static void udp_relay_parse_kcp_summary(const uint8_t *packet, size_t len, int *has_conv, uint32_t *conv, size_t *segment_count) {
|
||||
size_t offset = 0;
|
||||
size_t count = 0;
|
||||
|
||||
if (has_conv != NULL) {
|
||||
*has_conv = 0;
|
||||
}
|
||||
if (conv != NULL) {
|
||||
*conv = 0;
|
||||
}
|
||||
if (segment_count != NULL) {
|
||||
*segment_count = 0;
|
||||
}
|
||||
if (packet == NULL || len < 4U) {
|
||||
return;
|
||||
}
|
||||
if (has_conv != NULL) {
|
||||
*has_conv = 1;
|
||||
}
|
||||
if (conv != NULL) {
|
||||
*conv = (uint32_t) ((unsigned char) packet[0] |
|
||||
((unsigned char) packet[1] << 8) |
|
||||
((unsigned char) packet[2] << 16) |
|
||||
((unsigned char) packet[3] << 24));
|
||||
}
|
||||
while (offset + 24U <= len) {
|
||||
uint32_t seg_len = (uint32_t) ((unsigned char) packet[offset + 20] |
|
||||
((unsigned char) packet[offset + 21] << 8) |
|
||||
((unsigned char) packet[offset + 22] << 16) |
|
||||
((unsigned char) packet[offset + 23] << 24));
|
||||
if (offset + 24U + seg_len > len) {
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
offset += 24U + seg_len;
|
||||
}
|
||||
if (segment_count != NULL) {
|
||||
*segment_count = count;
|
||||
}
|
||||
}
|
||||
|
||||
static void udp_relay_print_packet(udp_relay_t *relay, const char *event_name, const char *local_addr, const struct sockaddr_storage *remote_addr, socklen_t remote_addr_len, const uint8_t *packet, size_t packet_len) {
|
||||
char remote_addr_text[OMNI_MAX_ADDR_TEXT];
|
||||
int64_t ts_unix_nano;
|
||||
int has_conv = 0;
|
||||
uint32_t conv = 0;
|
||||
size_t segment_count = 0;
|
||||
|
||||
if (relay == NULL) {
|
||||
return;
|
||||
}
|
||||
if (!udp_relay_should_log_packet(relay, event_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (remote_addr != NULL && remote_addr_len > 0) {
|
||||
omni_sockaddr_to_string((const struct sockaddr *) remote_addr, remote_addr_len, remote_addr_text, sizeof(remote_addr_text));
|
||||
} else {
|
||||
remote_addr_text[0] = '\0';
|
||||
}
|
||||
ts_unix_nano = omni_now_unix_nano();
|
||||
udp_relay_parse_kcp_summary(packet, packet_len, &has_conv, &conv, &segment_count);
|
||||
|
||||
pthread_mutex_lock(&relay->log_mu);
|
||||
if (has_conv) {
|
||||
fprintf(stderr, "[relay] ts=%" PRId64 " event=%s local=%s remote=%s bytes=%zu conv=%" PRIu32 " segs=%zu\n",
|
||||
ts_unix_nano,
|
||||
event_name == NULL ? "" : event_name,
|
||||
local_addr == NULL ? "" : local_addr,
|
||||
remote_addr_text,
|
||||
packet_len,
|
||||
conv,
|
||||
segment_count);
|
||||
} else {
|
||||
fprintf(stderr, "[relay] ts=%" PRId64 " event=%s local=%s remote=%s bytes=%zu\n",
|
||||
ts_unix_nano,
|
||||
event_name == NULL ? "" : event_name,
|
||||
local_addr == NULL ? "" : local_addr,
|
||||
remote_addr_text,
|
||||
packet_len);
|
||||
}
|
||||
fflush(stderr);
|
||||
pthread_mutex_unlock(&relay->log_mu);
|
||||
}
|
||||
|
||||
static int udp_relay_is_closed(udp_relay_t *relay) {
|
||||
int closed;
|
||||
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
closed = relay->closed;
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
return closed;
|
||||
}
|
||||
|
||||
static void udp_relay_note_result(udp_relay_t *relay, int rc, int errnum) {
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
if (!relay->worker_done) {
|
||||
relay->worker_done = 1;
|
||||
relay->worker_rc = rc;
|
||||
relay->worker_errno = errnum;
|
||||
pthread_cond_signal(&relay->state_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
}
|
||||
|
||||
static void udp_relay_record_client(udp_relay_t *relay, const struct sockaddr_storage *addr, socklen_t addr_len) {
|
||||
pthread_mutex_lock(&relay->lock);
|
||||
memcpy(&relay->client_addr, addr, sizeof(*addr));
|
||||
relay->client_addr_len = addr_len;
|
||||
relay->has_client = 1;
|
||||
relay->client_last_seen_ms = udp_relay_now_ms();
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
}
|
||||
|
||||
static void udp_relay_prune_routes_locked(udp_relay_t *relay, uint32_t now_ms) {
|
||||
udp_relay_route_t *prev = NULL;
|
||||
udp_relay_route_t *route;
|
||||
|
||||
if (relay == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
route = relay->routes;
|
||||
while (route != NULL) {
|
||||
udp_relay_route_t *next = route->next;
|
||||
|
||||
if (udp_relay_elapsed_ms(now_ms, route->last_seen_ms) >= UDP_RELAY_ROUTE_TIMEOUT_MS) {
|
||||
if (prev == NULL) {
|
||||
relay->routes = next;
|
||||
} else {
|
||||
prev->next = next;
|
||||
}
|
||||
free(route);
|
||||
route = next;
|
||||
continue;
|
||||
}
|
||||
|
||||
prev = route;
|
||||
route = next;
|
||||
}
|
||||
|
||||
if (relay->has_client && udp_relay_elapsed_ms(now_ms, relay->client_last_seen_ms) >= UDP_RELAY_ROUTE_TIMEOUT_MS) {
|
||||
relay->has_client = 0;
|
||||
relay->client_addr_len = 0;
|
||||
memset(&relay->client_addr, 0, sizeof(relay->client_addr));
|
||||
}
|
||||
}
|
||||
|
||||
static int udp_relay_record_route(udp_relay_t *relay, uint32_t conv, const struct sockaddr_storage *addr, socklen_t addr_len) {
|
||||
udp_relay_route_t *route;
|
||||
uint32_t now_ms;
|
||||
|
||||
if (relay == NULL || addr == NULL || addr_len == 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
now_ms = udp_relay_now_ms();
|
||||
pthread_mutex_lock(&relay->lock);
|
||||
udp_relay_prune_routes_locked(relay, now_ms);
|
||||
for (route = relay->routes; route != NULL; route = route->next) {
|
||||
if (route->conv == conv) {
|
||||
memcpy(&route->client_addr, addr, sizeof(*addr));
|
||||
route->client_addr_len = addr_len;
|
||||
route->last_seen_ms = now_ms;
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
route = (udp_relay_route_t *) calloc(1, sizeof(*route));
|
||||
if (route == NULL) {
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
return -1;
|
||||
}
|
||||
route->conv = conv;
|
||||
memcpy(&route->client_addr, addr, sizeof(*addr));
|
||||
route->client_addr_len = addr_len;
|
||||
route->last_seen_ms = now_ms;
|
||||
route->next = relay->routes;
|
||||
relay->routes = route;
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int udp_relay_copy_client(udp_relay_t *relay, struct sockaddr_storage *addr, socklen_t *addr_len) {
|
||||
int has_client;
|
||||
uint32_t now_ms;
|
||||
|
||||
now_ms = udp_relay_now_ms();
|
||||
pthread_mutex_lock(&relay->lock);
|
||||
udp_relay_prune_routes_locked(relay, now_ms);
|
||||
has_client = relay->has_client;
|
||||
if (has_client) {
|
||||
memcpy(addr, &relay->client_addr, sizeof(*addr));
|
||||
*addr_len = relay->client_addr_len;
|
||||
}
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
return has_client;
|
||||
}
|
||||
|
||||
static int udp_relay_copy_route(udp_relay_t *relay, uint32_t conv, struct sockaddr_storage *addr, socklen_t *addr_len) {
|
||||
udp_relay_route_t *route;
|
||||
uint32_t now_ms;
|
||||
|
||||
now_ms = udp_relay_now_ms();
|
||||
pthread_mutex_lock(&relay->lock);
|
||||
udp_relay_prune_routes_locked(relay, now_ms);
|
||||
for (route = relay->routes; route != NULL; route = route->next) {
|
||||
if (route->conv == conv) {
|
||||
memcpy(addr, &route->client_addr, sizeof(*addr));
|
||||
*addr_len = route->client_addr_len;
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void udp_relay_clear_routes(udp_relay_t *relay) {
|
||||
udp_relay_route_t *route;
|
||||
udp_relay_route_t *next;
|
||||
|
||||
if (relay == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&relay->lock);
|
||||
route = relay->routes;
|
||||
relay->routes = NULL;
|
||||
pthread_mutex_unlock(&relay->lock);
|
||||
|
||||
while (route != NULL) {
|
||||
next = route->next;
|
||||
free(route);
|
||||
route = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void *udp_relay_forward_downstream_to_upstream(void *arg) {
|
||||
udp_relay_t *relay = (udp_relay_t *) arg;
|
||||
uint8_t buffer[UDP_RELAY_BUF_SIZE];
|
||||
|
||||
for (;;) {
|
||||
struct sockaddr_storage source;
|
||||
socklen_t source_len = sizeof(source);
|
||||
ssize_t n = recvfrom(relay->downstream_fd, buffer, sizeof(buffer), 0, (struct sockaddr *) &source, &source_len);
|
||||
int has_conv = 0;
|
||||
uint32_t conv = 0;
|
||||
|
||||
if (n < 0) {
|
||||
int errnum = errno;
|
||||
if (errnum == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
udp_relay_note_result(relay, 0, 0);
|
||||
} else {
|
||||
udp_relay_note_result(relay, -1, errnum);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
udp_relay_record_client(relay, &source, source_len);
|
||||
udp_relay_parse_kcp_summary(buffer, (size_t) n, &has_conv, &conv, NULL);
|
||||
if (has_conv) {
|
||||
(void) udp_relay_record_route(relay, conv, &source, source_len);
|
||||
}
|
||||
udp_relay_print_packet(relay, "relay_downstream_rx", relay->downstream_local_addr, &source, source_len, buffer, (size_t) n);
|
||||
for (;;) {
|
||||
if (send(relay->upstream_fd, buffer, (size_t) n, 0) >= 0) {
|
||||
udp_relay_print_packet(relay, "relay_upstream_tx", relay->upstream_local_addr, &relay->upstream_addr, relay->upstream_addr_len, buffer, (size_t) n);
|
||||
break;
|
||||
}
|
||||
{
|
||||
int errnum = errno;
|
||||
if (errnum == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
udp_relay_note_result(relay, 0, 0);
|
||||
} else {
|
||||
udp_relay_note_result(relay, -1, errnum);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void *udp_relay_forward_upstream_to_downstream(void *arg) {
|
||||
udp_relay_t *relay = (udp_relay_t *) arg;
|
||||
uint8_t buffer[UDP_RELAY_BUF_SIZE];
|
||||
|
||||
for (;;) {
|
||||
struct sockaddr_storage client_addr;
|
||||
socklen_t client_addr_len = 0;
|
||||
ssize_t n = recv(relay->upstream_fd, buffer, sizeof(buffer), 0);
|
||||
int has_conv = 0;
|
||||
uint32_t conv = 0;
|
||||
|
||||
if (n < 0) {
|
||||
int errnum = errno;
|
||||
if (errnum == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
udp_relay_note_result(relay, 0, 0);
|
||||
} else {
|
||||
udp_relay_note_result(relay, -1, errnum);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
udp_relay_print_packet(relay, "relay_upstream_rx", relay->upstream_local_addr, &relay->upstream_addr, relay->upstream_addr_len, buffer, (size_t) n);
|
||||
udp_relay_parse_kcp_summary(buffer, (size_t) n, &has_conv, &conv, NULL);
|
||||
if (has_conv && !udp_relay_copy_route(relay, conv, &client_addr, &client_addr_len)) {
|
||||
udp_relay_print_packet(relay, "relay_upstream_drop_unknown_conv", relay->upstream_local_addr, &relay->upstream_addr, relay->upstream_addr_len, buffer, (size_t) n);
|
||||
continue;
|
||||
}
|
||||
if (!has_conv && !udp_relay_copy_client(relay, &client_addr, &client_addr_len)) {
|
||||
udp_relay_print_packet(relay, "relay_upstream_drop_no_client", relay->upstream_local_addr, &relay->upstream_addr, relay->upstream_addr_len, buffer, (size_t) n);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (sendto(relay->downstream_fd, buffer, (size_t) n, 0, (struct sockaddr *) &client_addr, client_addr_len) >= 0) {
|
||||
udp_relay_print_packet(relay, "relay_downstream_tx", relay->downstream_local_addr, &client_addr, client_addr_len, buffer, (size_t) n);
|
||||
break;
|
||||
}
|
||||
{
|
||||
int errnum = errno;
|
||||
if (errnum == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
udp_relay_note_result(relay, 0, 0);
|
||||
} else {
|
||||
udp_relay_note_result(relay, -1, errnum);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void udp_relay_join_threads(udp_relay_t *relay) {
|
||||
if (relay->downstream_thread_started) {
|
||||
pthread_join(relay->downstream_thread, NULL);
|
||||
relay->downstream_thread_started = 0;
|
||||
}
|
||||
if (relay->upstream_thread_started) {
|
||||
pthread_join(relay->upstream_thread, NULL);
|
||||
relay->upstream_thread_started = 0;
|
||||
}
|
||||
}
|
||||
|
||||
udp_relay_t *udp_relay_open(const char *listen_addr, const char *upstream_addr) {
|
||||
struct sockaddr_storage listen_ss;
|
||||
struct sockaddr_storage upstream_ss;
|
||||
struct sockaddr_storage downstream_local_ss;
|
||||
struct sockaddr_storage upstream_local_ss;
|
||||
socklen_t listen_len;
|
||||
socklen_t upstream_len;
|
||||
socklen_t downstream_local_len = sizeof(downstream_local_ss);
|
||||
socklen_t upstream_local_len = sizeof(upstream_local_ss);
|
||||
int family;
|
||||
int fd_listen = -1;
|
||||
int fd_upstream = -1;
|
||||
udp_relay_t *relay = NULL;
|
||||
|
||||
if (omni_parse_sockaddr(listen_addr, 1, &listen_ss, &listen_len, &family) != 0 ||
|
||||
omni_parse_sockaddr(upstream_addr, 0, &upstream_ss, &upstream_len, &family) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
fd_listen = socket(listen_ss.ss_family, SOCK_DGRAM, 0);
|
||||
if (fd_listen < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (bind(fd_listen, (struct sockaddr *) &listen_ss, listen_len) != 0) {
|
||||
close(fd_listen);
|
||||
return NULL;
|
||||
}
|
||||
fd_upstream = socket(upstream_ss.ss_family, SOCK_DGRAM, 0);
|
||||
if (fd_upstream < 0) {
|
||||
close(fd_listen);
|
||||
return NULL;
|
||||
}
|
||||
if (connect(fd_upstream, (struct sockaddr *) &upstream_ss, upstream_len) != 0) {
|
||||
close(fd_upstream);
|
||||
close(fd_listen);
|
||||
return NULL;
|
||||
}
|
||||
relay = (udp_relay_t *) calloc(1, sizeof(*relay));
|
||||
if (relay == NULL) {
|
||||
close(fd_upstream);
|
||||
close(fd_listen);
|
||||
return NULL;
|
||||
}
|
||||
relay->downstream_fd = fd_listen;
|
||||
relay->upstream_fd = fd_upstream;
|
||||
memcpy(&relay->upstream_addr, &upstream_ss, sizeof(upstream_ss));
|
||||
relay->upstream_addr_len = upstream_len;
|
||||
if (getsockname(fd_listen, (struct sockaddr *) &downstream_local_ss, &downstream_local_len) == 0) {
|
||||
omni_sockaddr_to_string((const struct sockaddr *) &downstream_local_ss, downstream_local_len, relay->downstream_local_addr, sizeof(relay->downstream_local_addr));
|
||||
} else {
|
||||
snprintf(relay->downstream_local_addr, sizeof(relay->downstream_local_addr), "%s", listen_addr == NULL ? "" : listen_addr);
|
||||
}
|
||||
if (getsockname(fd_upstream, (struct sockaddr *) &upstream_local_ss, &upstream_local_len) == 0) {
|
||||
omni_sockaddr_to_string((const struct sockaddr *) &upstream_local_ss, upstream_local_len, relay->upstream_local_addr, sizeof(relay->upstream_local_addr));
|
||||
} else {
|
||||
snprintf(relay->upstream_local_addr, sizeof(relay->upstream_local_addr), "%s", listen_addr == NULL ? "" : listen_addr);
|
||||
}
|
||||
pthread_mutex_init(&relay->lock, NULL);
|
||||
pthread_mutex_init(&relay->log_mu, NULL);
|
||||
relay->packet_log_sample_every = udp_relay_packet_log_sample_every();
|
||||
atomic_init(&relay->packet_log_counter, 0U);
|
||||
pthread_mutex_init(&relay->state_mu, NULL);
|
||||
pthread_cond_init(&relay->state_cond, NULL);
|
||||
return relay;
|
||||
}
|
||||
|
||||
int udp_relay_serve(udp_relay_t *relay) {
|
||||
int thread_rc;
|
||||
int rc;
|
||||
int errnum;
|
||||
|
||||
if (relay == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (udp_relay_is_closed(relay)) {
|
||||
errno = ECANCELED;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
relay->worker_done = 0;
|
||||
relay->worker_rc = 0;
|
||||
relay->worker_errno = 0;
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
|
||||
thread_rc = pthread_create(&relay->downstream_thread, NULL, udp_relay_forward_downstream_to_upstream, relay);
|
||||
if (thread_rc != 0) {
|
||||
errno = thread_rc;
|
||||
return -1;
|
||||
}
|
||||
relay->downstream_thread_started = 1;
|
||||
|
||||
thread_rc = pthread_create(&relay->upstream_thread, NULL, udp_relay_forward_upstream_to_downstream, relay);
|
||||
if (thread_rc != 0) {
|
||||
errno = thread_rc;
|
||||
udp_relay_close(relay);
|
||||
udp_relay_join_threads(relay);
|
||||
return -1;
|
||||
}
|
||||
relay->upstream_thread_started = 1;
|
||||
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
while (!relay->worker_done) {
|
||||
pthread_cond_wait(&relay->state_cond, &relay->state_mu);
|
||||
}
|
||||
rc = relay->worker_rc;
|
||||
errnum = relay->worker_errno;
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
|
||||
udp_relay_close(relay);
|
||||
udp_relay_join_threads(relay);
|
||||
|
||||
if (rc != 0 && errnum != 0) {
|
||||
errno = errnum;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int udp_relay_close(udp_relay_t *relay) {
|
||||
int downstream_fd;
|
||||
int upstream_fd;
|
||||
|
||||
if (relay == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&relay->state_mu);
|
||||
if (relay->closed) {
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
return 0;
|
||||
}
|
||||
relay->closed = 1;
|
||||
downstream_fd = relay->downstream_fd;
|
||||
upstream_fd = relay->upstream_fd;
|
||||
relay->downstream_fd = -1;
|
||||
relay->upstream_fd = -1;
|
||||
pthread_cond_broadcast(&relay->state_cond);
|
||||
pthread_mutex_unlock(&relay->state_mu);
|
||||
|
||||
if (downstream_fd >= 0) {
|
||||
close(downstream_fd);
|
||||
}
|
||||
if (upstream_fd >= 0) {
|
||||
close(upstream_fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void udp_relay_free(udp_relay_t *relay) {
|
||||
if (relay == NULL) {
|
||||
return;
|
||||
}
|
||||
udp_relay_close(relay);
|
||||
udp_relay_join_threads(relay);
|
||||
udp_relay_clear_routes(relay);
|
||||
pthread_mutex_destroy(&relay->lock);
|
||||
pthread_mutex_destroy(&relay->log_mu);
|
||||
pthread_cond_destroy(&relay->state_cond);
|
||||
pthread_mutex_destroy(&relay->state_mu);
|
||||
free(relay);
|
||||
}
|
||||
2061
host/OmniSocketGo_add_camera/src/transport_kcp.c
Normal file
2061
host/OmniSocketGo_add_camera/src/transport_kcp.c
Normal file
File diff suppressed because it is too large
Load Diff
486
host/OmniSocketGo_add_camera/src/transport_udp.c
Normal file
486
host/OmniSocketGo_add_camera/src/transport_udp.c
Normal file
@@ -0,0 +1,486 @@
|
||||
#include "transport_udp.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct udp_pending_tx {
|
||||
struct udp_pending_tx *next;
|
||||
uint32_t tx_id;
|
||||
message_t msg;
|
||||
int bytes_written;
|
||||
int saw_sched;
|
||||
int saw_software;
|
||||
} udp_pending_tx_t;
|
||||
|
||||
struct udp_conn {
|
||||
int fd;
|
||||
int connected;
|
||||
int timestamping_enabled;
|
||||
latency_logger_t *logger;
|
||||
tx_timestamp_debug_logger_t *debug_logger;
|
||||
char node_role[OMNI_MAX_NODE_ROLE];
|
||||
char node_id[OMNI_MAX_PEER_ID];
|
||||
pthread_mutex_t write_mu;
|
||||
pthread_mutex_t pending_mu;
|
||||
pthread_t errqueue_thread;
|
||||
int errqueue_thread_started;
|
||||
uint32_t next_tx_id;
|
||||
udp_pending_tx_t *pending_head;
|
||||
uint8_t *recv_buffer;
|
||||
size_t recv_buffer_cap;
|
||||
int closed;
|
||||
};
|
||||
|
||||
static int udp_open_socket_for_addr(const struct sockaddr *addr, socklen_t addr_len, int bind_device, const char *device) {
|
||||
int fd;
|
||||
int reuse = 1;
|
||||
fd = socket(addr->sa_family, SOCK_DGRAM, 0);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
if (bind_device && omni_bind_device(fd, device) != 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
(void) addr_len;
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int udp_resolve_ip_only(const char *ip, int family, struct sockaddr_storage *out, socklen_t *out_len) {
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
if (getaddrinfo(ip, "0", &hints, &result) != 0 || result == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memcpy(out, result->ai_addr, result->ai_addrlen);
|
||||
*out_len = (socklen_t) result->ai_addrlen;
|
||||
freeaddrinfo(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static udp_conn_t *udp_conn_alloc(int fd, int connected, int enable_timestamping, latency_logger_t *logger, const char *node_role, const char *node_id, tx_timestamp_debug_logger_t *debug_logger) {
|
||||
udp_conn_t *conn = (udp_conn_t *) calloc(1, sizeof(*conn));
|
||||
if (conn == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
conn->fd = fd;
|
||||
conn->connected = connected;
|
||||
conn->timestamping_enabled = enable_timestamping;
|
||||
conn->logger = logger;
|
||||
conn->debug_logger = debug_logger;
|
||||
snprintf(conn->node_role, sizeof(conn->node_role), "%s", node_role == NULL ? "" : node_role);
|
||||
snprintf(conn->node_id, sizeof(conn->node_id), "%s", node_id == NULL ? "" : node_id);
|
||||
conn->recv_buffer = (uint8_t *) malloc(OMNI_MAX_FRAME_SIZE);
|
||||
if (conn->recv_buffer == NULL) {
|
||||
free(conn);
|
||||
return NULL;
|
||||
}
|
||||
conn->recv_buffer_cap = OMNI_MAX_FRAME_SIZE;
|
||||
pthread_mutex_init(&conn->write_mu, NULL);
|
||||
pthread_mutex_init(&conn->pending_mu, NULL);
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void udp_pending_destroy(udp_pending_tx_t *pending) {
|
||||
while (pending != NULL) {
|
||||
udp_pending_tx_t *next = pending->next;
|
||||
protocol_message_clear(&pending->msg);
|
||||
free(pending);
|
||||
pending = next;
|
||||
}
|
||||
}
|
||||
|
||||
static int udp_debug_log_send_chunk(udp_conn_t *conn, const message_t *msg, int bytes_written, uint32_t tx_id) {
|
||||
tx_timestamp_debug_record_t record;
|
||||
if (conn->debug_logger == NULL) {
|
||||
return 0;
|
||||
}
|
||||
memset(&record, 0, sizeof(record));
|
||||
snprintf(record.record_type, sizeof(record.record_type), "%s", TX_TIMESTAMP_DEBUG_RECORD_SEND_CHUNK);
|
||||
snprintf(record.node_role, sizeof(record.node_role), "%s", conn->node_role);
|
||||
snprintf(record.node_id, sizeof(record.node_id), "%s", conn->node_id);
|
||||
record.message_type = msg->type;
|
||||
record.message_id = msg->id;
|
||||
snprintf(record.from, sizeof(record.from), "%s", msg->from);
|
||||
snprintf(record.to, sizeof(record.to), "%s", msg->to);
|
||||
snprintf(record.file_name, sizeof(record.file_name), "%s", msg->file_name);
|
||||
record.body_size = (int) msg->body_len;
|
||||
record.send_call_index = 0;
|
||||
record.frame_offset_start = 0;
|
||||
record.frame_offset_end = bytes_written > 0 ? bytes_written - 1 : 0;
|
||||
record.bytes_written = bytes_written;
|
||||
record.expected_tx_id = tx_id;
|
||||
return tx_timestamp_debug_log(conn->debug_logger, &record);
|
||||
}
|
||||
|
||||
static void udp_debug_log_errqueue_event(udp_conn_t *conn, const message_t *msg, const omni_tx_timestamp_event_t *event, uint32_t tx_id, int selected) {
|
||||
tx_timestamp_debug_record_t record;
|
||||
if (conn->debug_logger == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(&record, 0, sizeof(record));
|
||||
snprintf(record.record_type, sizeof(record.record_type), "%s", TX_TIMESTAMP_DEBUG_RECORD_ERRQUEUE_EVENT);
|
||||
snprintf(record.node_role, sizeof(record.node_role), "%s", conn->node_role);
|
||||
snprintf(record.node_id, sizeof(record.node_id), "%s", conn->node_id);
|
||||
record.message_type = msg->type;
|
||||
record.message_id = msg->id;
|
||||
snprintf(record.from, sizeof(record.from), "%s", msg->from);
|
||||
snprintf(record.to, sizeof(record.to), "%s", msg->to);
|
||||
snprintf(record.file_name, sizeof(record.file_name), "%s", msg->file_name);
|
||||
record.body_size = (int) msg->body_len;
|
||||
snprintf(record.phase, sizeof(record.phase), "%s", "background");
|
||||
record.read_index = 0;
|
||||
snprintf(record.event_name, sizeof(record.event_name), "%s", event->event_name);
|
||||
record.ts_unix_nano = event->ts_unix_nano;
|
||||
record.ee_info = event->ee_info;
|
||||
record.ee_data = event->ee_data;
|
||||
record.expected_tx_id = tx_id;
|
||||
record.selected_for_latency = selected;
|
||||
tx_timestamp_debug_log(conn->debug_logger, &record);
|
||||
}
|
||||
|
||||
static void *udp_errqueue_thread_main(void *arg) {
|
||||
udp_conn_t *conn = (udp_conn_t *) arg;
|
||||
uint8_t control[512];
|
||||
struct msghdr msg;
|
||||
struct iovec iov;
|
||||
uint8_t dummy;
|
||||
|
||||
while (!conn->closed) {
|
||||
ssize_t rc;
|
||||
omni_tx_timestamp_event_t event;
|
||||
udp_pending_tx_t *prev = NULL;
|
||||
udp_pending_tx_t *cur = NULL;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
memset(control, 0, sizeof(control));
|
||||
dummy = 0;
|
||||
iov.iov_base = &dummy;
|
||||
iov.iov_len = sizeof(dummy);
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = sizeof(control);
|
||||
rc = recvmsg(conn->fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
|
||||
if (rc < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
if (conn->closed) {
|
||||
return NULL;
|
||||
}
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
if (linux_timestamping_parse_tx_timestamp(&msg, &event) != 0) {
|
||||
continue;
|
||||
}
|
||||
pthread_mutex_lock(&conn->pending_mu);
|
||||
cur = NULL;
|
||||
prev = NULL;
|
||||
{
|
||||
udp_pending_tx_t **head = &conn->pending_head;
|
||||
udp_pending_tx_t *iter = *head;
|
||||
while (iter != NULL) {
|
||||
if (iter->tx_id == event.ee_data) {
|
||||
cur = iter;
|
||||
break;
|
||||
}
|
||||
prev = iter;
|
||||
iter = iter->next;
|
||||
}
|
||||
if (cur != NULL) {
|
||||
if (strcmp(event.event_name, EVENT_A_TX_SCHED) == 0 && !cur->saw_sched) {
|
||||
cur->saw_sched = 1;
|
||||
latencylog_log_message_event_at(conn->logger, conn->node_role, conn->node_id, EVENT_A_TX_SCHED, event.ts_unix_nano, &cur->msg);
|
||||
} else if (strcmp(event.event_name, EVENT_A_TX_SOFTWARE) == 0 && !cur->saw_software) {
|
||||
cur->saw_software = 1;
|
||||
latencylog_log_message_event_at(conn->logger, conn->node_role, conn->node_id, EVENT_A_TX_SOFTWARE, event.ts_unix_nano, &cur->msg);
|
||||
}
|
||||
udp_debug_log_errqueue_event(conn, &cur->msg, &event, cur->tx_id, 1);
|
||||
if (cur->saw_sched && cur->saw_software) {
|
||||
if (prev == NULL) {
|
||||
*head = cur->next;
|
||||
} else {
|
||||
prev->next = cur->next;
|
||||
}
|
||||
protocol_message_clear(&cur->msg);
|
||||
free(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&conn->pending_mu);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int udp_conn_start_errqueue(udp_conn_t *conn) {
|
||||
if (!conn->timestamping_enabled) {
|
||||
return 0;
|
||||
}
|
||||
if (pthread_create(&conn->errqueue_thread, NULL, udp_errqueue_thread_main, conn) != 0) {
|
||||
return -1;
|
||||
}
|
||||
conn->errqueue_thread_started = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
udp_conn_t *udp_conn_dial(const char *server_addr, const char *bind_ip, const char *bind_device, int enable_timestamping, latency_logger_t *logger, const char *node_role, const char *node_id, tx_timestamp_debug_logger_t *debug_logger) {
|
||||
struct sockaddr_storage remote_addr;
|
||||
struct sockaddr_storage local_addr;
|
||||
socklen_t remote_len;
|
||||
socklen_t local_len;
|
||||
int family;
|
||||
int fd;
|
||||
udp_conn_t *conn;
|
||||
|
||||
if (omni_parse_sockaddr(server_addr, 0, &remote_addr, &remote_len, &family) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
fd = udp_open_socket_for_addr((struct sockaddr *) &remote_addr, remote_len, bind_device != NULL && bind_device[0] != '\0', bind_device);
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (bind_ip != NULL && bind_ip[0] != '\0') {
|
||||
if (udp_resolve_ip_only(bind_ip, family, &local_addr, &local_len) != 0 || bind(fd, (struct sockaddr *) &local_addr, local_len) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (connect(fd, (struct sockaddr *) &remote_addr, remote_len) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
if (enable_timestamping && linux_timestamping_enable_udp_socket(fd, 1) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
conn = udp_conn_alloc(fd, 1, enable_timestamping, logger, node_role, node_id, debug_logger);
|
||||
if (conn == NULL) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
if (udp_conn_start_errqueue(conn) != 0) {
|
||||
udp_conn_free(conn);
|
||||
return NULL;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
udp_conn_t *udp_conn_bind(const char *listen_addr, const char *bind_device, int enable_timestamping, latency_logger_t *logger, const char *node_role, const char *node_id, tx_timestamp_debug_logger_t *debug_logger) {
|
||||
struct sockaddr_storage local_addr;
|
||||
socklen_t local_len;
|
||||
int family;
|
||||
int fd;
|
||||
udp_conn_t *conn;
|
||||
if (omni_parse_sockaddr(listen_addr, 1, &local_addr, &local_len, &family) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
fd = udp_open_socket_for_addr((struct sockaddr *) &local_addr, local_len, bind_device != NULL && bind_device[0] != '\0', bind_device);
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (bind(fd, (struct sockaddr *) &local_addr, local_len) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
if (enable_timestamping && linux_timestamping_enable_udp_socket(fd, 1) != 0) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
conn = udp_conn_alloc(fd, 0, enable_timestamping, logger, node_role, node_id, debug_logger);
|
||||
if (conn == NULL) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
if (udp_conn_start_errqueue(conn) != 0) {
|
||||
udp_conn_free(conn);
|
||||
return NULL;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
static int udp_conn_send_inner(udp_conn_t *conn, const message_t *msg, const struct sockaddr *addr, socklen_t addr_len) {
|
||||
uint8_t *payload = NULL;
|
||||
size_t payload_len = 0;
|
||||
ssize_t rc;
|
||||
udp_pending_tx_t *pending = NULL;
|
||||
uint32_t tx_id;
|
||||
|
||||
if (protocol_encode_message_datagram(msg, &payload, &payload_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&conn->write_mu);
|
||||
latencylog_log_message_event(conn->logger, conn->node_role, conn->node_id, EVENT_SEND_HANDOFF_BEGIN, msg);
|
||||
tx_id = conn->next_tx_id++;
|
||||
if (conn->timestamping_enabled) {
|
||||
pending = (udp_pending_tx_t *) calloc(1, sizeof(*pending));
|
||||
if (pending == NULL || protocol_message_copy(&pending->msg, msg) != 0) {
|
||||
free(payload);
|
||||
pthread_mutex_unlock(&conn->write_mu);
|
||||
free(pending);
|
||||
return -1;
|
||||
}
|
||||
pending->tx_id = tx_id;
|
||||
pending->bytes_written = (int) payload_len;
|
||||
pthread_mutex_lock(&conn->pending_mu);
|
||||
pending->next = conn->pending_head;
|
||||
conn->pending_head = pending;
|
||||
pthread_mutex_unlock(&conn->pending_mu);
|
||||
udp_debug_log_send_chunk(conn, msg, (int) payload_len, tx_id);
|
||||
}
|
||||
if (addr != NULL) {
|
||||
rc = sendto(conn->fd, payload, payload_len, 0, addr, addr_len);
|
||||
} else {
|
||||
rc = send(conn->fd, payload, payload_len, 0);
|
||||
}
|
||||
free(payload);
|
||||
if (rc < 0 || (size_t) rc != payload_len) {
|
||||
if (pending != NULL) {
|
||||
udp_pending_tx_t *prev = NULL;
|
||||
udp_pending_tx_t *cur;
|
||||
pthread_mutex_lock(&conn->pending_mu);
|
||||
for (cur = conn->pending_head; cur != NULL; cur = cur->next) {
|
||||
if (cur == pending) {
|
||||
if (prev == NULL) {
|
||||
conn->pending_head = cur->next;
|
||||
} else {
|
||||
prev->next = cur->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
prev = cur;
|
||||
}
|
||||
pthread_mutex_unlock(&conn->pending_mu);
|
||||
protocol_message_clear(&pending->msg);
|
||||
free(pending);
|
||||
}
|
||||
pthread_mutex_unlock(&conn->write_mu);
|
||||
return -1;
|
||||
}
|
||||
latencylog_log_message_event(conn->logger, conn->node_role, conn->node_id, EVENT_SEND_HANDOFF_END, msg);
|
||||
pthread_mutex_unlock(&conn->write_mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_conn_send(udp_conn_t *conn, const message_t *msg) {
|
||||
if (conn == NULL || !conn->connected) {
|
||||
errno = ENOTCONN;
|
||||
return -1;
|
||||
}
|
||||
return udp_conn_send_inner(conn, msg, NULL, 0);
|
||||
}
|
||||
|
||||
int udp_conn_send_to(udp_conn_t *conn, const message_t *msg, const struct sockaddr *addr, socklen_t addr_len) {
|
||||
if (conn == NULL || addr == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return udp_conn_send_inner(conn, msg, addr, addr_len);
|
||||
}
|
||||
|
||||
int udp_conn_receive(udp_conn_t *conn, message_t *out_msg, struct sockaddr_storage *addr, socklen_t *addr_len) {
|
||||
uint8_t control[512];
|
||||
struct sockaddr_storage source;
|
||||
struct iovec iov;
|
||||
struct msghdr msg;
|
||||
ssize_t n;
|
||||
int64_t rx_ts = 0;
|
||||
char err[128];
|
||||
|
||||
if (conn == NULL || out_msg == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
memset(&source, 0, sizeof(source));
|
||||
iov.iov_base = conn->recv_buffer;
|
||||
iov.iov_len = conn->recv_buffer_cap;
|
||||
msg.msg_name = &source;
|
||||
msg.msg_namelen = sizeof(source);
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
if (conn->timestamping_enabled) {
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = sizeof(control);
|
||||
}
|
||||
n = recvmsg(conn->fd, &msg, 0);
|
||||
if (n < 0) {
|
||||
if (conn->closed) {
|
||||
errno = ECANCELED;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (n == 0 && conn->closed) {
|
||||
errno = ECANCELED;
|
||||
return -1;
|
||||
}
|
||||
if (conn->timestamping_enabled) {
|
||||
rx_ts = linux_timestamping_parse_rx_timestamp(&msg);
|
||||
}
|
||||
if (protocol_decode_message_datagram(conn->recv_buffer, (size_t) n, out_msg, err, sizeof(err)) != 0) {
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
if (addr != NULL && addr_len != NULL) {
|
||||
omni_clone_sockaddr((struct sockaddr *) &source, msg.msg_namelen, addr, addr_len);
|
||||
}
|
||||
if (rx_ts > 0) {
|
||||
latencylog_log_message_event_at(conn->logger, conn->node_role, conn->node_id, EVENT_B_RX_SOFTWARE, rx_ts, out_msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_conn_fd(const udp_conn_t *conn) {
|
||||
return conn == NULL ? -1 : conn->fd;
|
||||
}
|
||||
|
||||
int udp_conn_local_addr(const udp_conn_t *conn, struct sockaddr_storage *addr, socklen_t *addr_len) {
|
||||
socklen_t len = sizeof(*addr);
|
||||
if (conn == NULL || addr == NULL || addr_len == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (getsockname(conn->fd, (struct sockaddr *) addr, &len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
*addr_len = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_conn_close(udp_conn_t *conn) {
|
||||
if (conn == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (!conn->closed) {
|
||||
conn->closed = 1;
|
||||
/* Wake blocking recvmsg()/poll users before tearing down the socket. */
|
||||
(void) shutdown(conn->fd, SHUT_RDWR);
|
||||
close(conn->fd);
|
||||
if (conn->errqueue_thread_started) {
|
||||
pthread_join(conn->errqueue_thread, NULL);
|
||||
conn->errqueue_thread_started = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void udp_conn_free(udp_conn_t *conn) {
|
||||
if (conn == NULL) {
|
||||
return;
|
||||
}
|
||||
udp_conn_close(conn);
|
||||
udp_pending_destroy(conn->pending_head);
|
||||
free(conn->recv_buffer);
|
||||
pthread_mutex_destroy(&conn->write_mu);
|
||||
pthread_mutex_destroy(&conn->pending_mu);
|
||||
free(conn);
|
||||
}
|
||||
108
host/OmniSocketGo_add_camera/src/tx_timestamp_debug.c
Normal file
108
host/OmniSocketGo_add_camera/src/tx_timestamp_debug.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "tx_timestamp_debug.h"
|
||||
|
||||
tx_timestamp_debug_logger_t *tx_timestamp_debug_open_jsonl(const char *path) {
|
||||
tx_timestamp_debug_logger_t *logger;
|
||||
FILE *file;
|
||||
if (path == NULL || path[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
if (omni_ensure_parent_dir(path) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
file = fopen(path, "ab");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
logger = (tx_timestamp_debug_logger_t *) calloc(1, sizeof(*logger));
|
||||
if (logger == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
omni_file_logger_init_path(&logger->file_logger, file, path, 0);
|
||||
logger->enabled = 1;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void tx_timestamp_debug_close(tx_timestamp_debug_logger_t *logger) {
|
||||
if (logger == NULL) {
|
||||
return;
|
||||
}
|
||||
if (logger->file_logger.file != NULL) {
|
||||
fclose(logger->file_logger.file);
|
||||
}
|
||||
omni_file_logger_destroy(&logger->file_logger);
|
||||
free(logger);
|
||||
}
|
||||
|
||||
int tx_timestamp_debug_log(tx_timestamp_debug_logger_t *logger, const tx_timestamp_debug_record_t *record) {
|
||||
char *line;
|
||||
char *node_role;
|
||||
char *node_id;
|
||||
char *from;
|
||||
char *to;
|
||||
char *file_name;
|
||||
char *phase;
|
||||
char *event_name;
|
||||
|
||||
if (logger == NULL || record == NULL || !logger->enabled) {
|
||||
return 0;
|
||||
}
|
||||
node_role = omni_json_escape(record->node_role);
|
||||
node_id = omni_json_escape(record->node_id);
|
||||
from = omni_json_escape(record->from);
|
||||
to = omni_json_escape(record->to);
|
||||
file_name = omni_json_escape(record->file_name);
|
||||
phase = omni_json_escape(record->phase);
|
||||
event_name = omni_json_escape(record->event_name);
|
||||
if (node_role == NULL || node_id == NULL || from == NULL || to == NULL || file_name == NULL || phase == NULL || event_name == NULL) {
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(from);
|
||||
free(to);
|
||||
free(file_name);
|
||||
free(phase);
|
||||
free(event_name);
|
||||
return -1;
|
||||
}
|
||||
line = omni_strdup_printf(
|
||||
"{\"record_type\":\"%s\",\"node_role\":\"%s\",\"node_id\":\"%s\",\"message_type\":\"%s\",\"message_id\":%" PRIu64 ",\"from\":\"%s\",\"to\":\"%s\",\"file_name\":\"%s\",\"body_size\":%d,\"phase\":\"%s\",\"send_call_index\":%d,\"frame_offset_start\":%d,\"frame_offset_end\":%d,\"bytes_written\":%d,\"expected_tx_id\":%u,\"read_index\":%d,\"event_name\":\"%s\",\"ts_unix_nano\":%" PRId64 ",\"ee_info\":%u,\"ee_data\":%u,\"matched_send_call_index\":%d,\"selected_for_latency\":%d}",
|
||||
record->record_type,
|
||||
node_role,
|
||||
node_id,
|
||||
protocol_message_type_name(record->message_type),
|
||||
record->message_id,
|
||||
from,
|
||||
to,
|
||||
file_name,
|
||||
record->body_size,
|
||||
phase,
|
||||
record->send_call_index,
|
||||
record->frame_offset_start,
|
||||
record->frame_offset_end,
|
||||
record->bytes_written,
|
||||
record->expected_tx_id,
|
||||
record->read_index,
|
||||
event_name,
|
||||
record->ts_unix_nano,
|
||||
record->ee_info,
|
||||
record->ee_data,
|
||||
record->matched_send_call_index,
|
||||
record->selected_for_latency
|
||||
);
|
||||
free(node_role);
|
||||
free(node_id);
|
||||
free(from);
|
||||
free(to);
|
||||
free(file_name);
|
||||
free(phase);
|
||||
free(event_name);
|
||||
if (line == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (omni_file_logger_write_line(&logger->file_logger, line) != 0) {
|
||||
free(line);
|
||||
return -1;
|
||||
}
|
||||
free(line);
|
||||
return 0;
|
||||
}
|
||||
1497
host/OmniSocketGo_add_camera/src/video_pipeline.c
Normal file
1497
host/OmniSocketGo_add_camera/src/video_pipeline.c
Normal file
File diff suppressed because it is too large
Load Diff
925
host/OmniSocketGo_add_camera/src/video_pipeline_gps.c
Normal file
925
host/OmniSocketGo_add_camera/src/video_pipeline_gps.c
Normal file
@@ -0,0 +1,925 @@
|
||||
#include "video_pipeline.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
#define VIDEO_CAPTURE_WIDTH_DEFAULT 1280
|
||||
#define VIDEO_CAPTURE_HEIGHT_DEFAULT 720
|
||||
#define VIDEO_OUTPUT_WIDTH_DEFAULT 640
|
||||
#define VIDEO_OUTPUT_HEIGHT_DEFAULT 360
|
||||
#define VIDEO_NUM_BUFFERS 4
|
||||
#define VIDEO_DEFAULT_CAMERA_DEVICE "/dev/video0"
|
||||
#define VIDEO_DEFAULT_PEER_ID "peer-b-video"
|
||||
#define VIDEO_DEFAULT_TARGET_PEER "peer-a-video"
|
||||
|
||||
typedef struct video_buffer {
|
||||
void *start;
|
||||
size_t length;
|
||||
} video_buffer_t;
|
||||
|
||||
typedef struct video_sender {
|
||||
kcp_client_t *client;
|
||||
char target_peer[OMNI_MAX_PEER_ID];
|
||||
uint8_t *send_buffer;
|
||||
size_t send_buffer_cap;
|
||||
} video_sender_t;
|
||||
|
||||
static int video_pipeline_stop_requested(volatile sig_atomic_t *stop_requested) {
|
||||
return stop_requested != NULL && *stop_requested != 0;
|
||||
}
|
||||
|
||||
static int env_flag_or_default(const char *name, int fallback) {
|
||||
const char *value = getenv(name);
|
||||
|
||||
if (value == NULL || value[0] == '\0') {
|
||||
return fallback;
|
||||
}
|
||||
if (
|
||||
strcmp(value, "1") == 0 || strcmp(value, "true") == 0 || strcmp(value, "TRUE") == 0
|
||||
|| strcmp(value, "yes") == 0 || strcmp(value, "on") == 0
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
if (
|
||||
strcmp(value, "0") == 0 || strcmp(value, "false") == 0 || strcmp(value, "FALSE") == 0
|
||||
|| strcmp(value, "no") == 0 || strcmp(value, "off") == 0
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
static double video_pipeline_now_ms(void) {
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0;
|
||||
}
|
||||
|
||||
static void video_pipeline_print_timing_header(void) {
|
||||
fprintf(stderr, "Frame | Capture | Decode | Scale | Encode | Send | Total | Size | Marker\n");
|
||||
fprintf(stderr, "------|---------|--------|-------|--------|------|-------|------|--------\n");
|
||||
}
|
||||
|
||||
static void video_pipeline_print_timing_failure(int frame_number, const char *stage) {
|
||||
fprintf(stderr, "Frame %d: %s failed\n", frame_number, stage);
|
||||
}
|
||||
|
||||
static void video_pipeline_print_timing_row(
|
||||
int frame_number,
|
||||
double capture_ms,
|
||||
double decode_ms,
|
||||
double scale_ms,
|
||||
double encode_ms,
|
||||
double send_ms,
|
||||
double total_ms,
|
||||
const AVPacket *encoded_pkt
|
||||
) {
|
||||
size_t size_kb = 0;
|
||||
unsigned int marker = 0;
|
||||
|
||||
if (encoded_pkt != NULL) {
|
||||
size_kb = (size_t) encoded_pkt->size / 1024;
|
||||
if (encoded_pkt->size > 1) {
|
||||
marker = encoded_pkt->data[1];
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(
|
||||
stderr,
|
||||
"%5d | %7.1f | %6.1f | %5.1f | %6.1f | %4.1f | %5.1f | %4zu KB | 0x%02x\n",
|
||||
frame_number,
|
||||
capture_ms,
|
||||
decode_ms,
|
||||
scale_ms,
|
||||
encode_ms,
|
||||
send_ms,
|
||||
total_ms,
|
||||
size_kb,
|
||||
marker
|
||||
);
|
||||
}
|
||||
|
||||
static const char *env_or_default(const char *name, const char *fallback) {
|
||||
const char *value = getenv(name);
|
||||
if (value != NULL && value[0] != '\0') {
|
||||
return value;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
static const char *env_first_nonempty(const char *first, const char *second, const char *fallback) {
|
||||
const char *value = getenv(first);
|
||||
if (value != NULL && value[0] != '\0') {
|
||||
return value;
|
||||
}
|
||||
value = getenv(second);
|
||||
if (value != NULL && value[0] != '\0') {
|
||||
return value;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
static void video_pipeline_set_error(video_pipeline_stats_t *stats, const char *message) {
|
||||
if (stats == NULL) {
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&stats->mutex);
|
||||
snprintf(stats->last_error, sizeof(stats->last_error), "%s", message == NULL ? "" : message);
|
||||
pthread_mutex_unlock(&stats->mutex);
|
||||
}
|
||||
|
||||
static void video_pipeline_set_errno_error(video_pipeline_stats_t *stats, const char *prefix) {
|
||||
char buffer[256];
|
||||
int saved_errno = errno;
|
||||
|
||||
snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"%s: %s (errno=%d)",
|
||||
prefix == NULL ? "video pipeline error" : prefix,
|
||||
saved_errno != 0 ? strerror(saved_errno) : "unknown error",
|
||||
saved_errno
|
||||
);
|
||||
video_pipeline_set_error(stats, buffer);
|
||||
}
|
||||
|
||||
static void video_pipeline_report_progress(const video_pipeline_config_t *config) {
|
||||
if (config == NULL || config->progress_callback == NULL) {
|
||||
return;
|
||||
}
|
||||
config->progress_callback(config->progress_context);
|
||||
}
|
||||
|
||||
void video_pipeline_config_init(video_pipeline_config_t *config) {
|
||||
if (config == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(config, 0, sizeof(*config));
|
||||
config->camera_device = VIDEO_DEFAULT_CAMERA_DEVICE;
|
||||
config->server_addr = "";
|
||||
config->relay_via = "";
|
||||
config->bind_ip = "";
|
||||
config->bind_device = "";
|
||||
config->peer_id = VIDEO_DEFAULT_PEER_ID;
|
||||
config->target_peer = VIDEO_DEFAULT_TARGET_PEER;
|
||||
config->capture_width = VIDEO_CAPTURE_WIDTH_DEFAULT;
|
||||
config->capture_height = VIDEO_CAPTURE_HEIGHT_DEFAULT;
|
||||
config->output_width = VIDEO_OUTPUT_WIDTH_DEFAULT;
|
||||
config->output_height = VIDEO_OUTPUT_HEIGHT_DEFAULT;
|
||||
config->max_frames = 0;
|
||||
config->enable_timing_logs = 0;
|
||||
config->stats_logger = NULL;
|
||||
config->stats_interval_ms = 1000;
|
||||
}
|
||||
|
||||
void video_pipeline_config_load_env(video_pipeline_config_t *config) {
|
||||
if (config == NULL) {
|
||||
return;
|
||||
}
|
||||
config->camera_device = env_or_default("OMNI_CAMERA_DEVICE", config->camera_device);
|
||||
config->server_addr = env_first_nonempty("OMNI_VIDEO_SERVER_ADDR", "OMNISOCKET_SERVER_ADDR", config->server_addr);
|
||||
config->relay_via = env_first_nonempty("OMNI_VIDEO_RELAY_VIA", "OMNISOCKET_RELAY_VIA", config->relay_via);
|
||||
config->bind_ip = env_first_nonempty("OMNI_VIDEO_BIND_IP", "OMNISOCKET_BIND_IP", config->bind_ip);
|
||||
config->bind_device = env_first_nonempty("OMNI_VIDEO_BIND_DEVICE", "OMNISOCKET_BIND_DEVICE", config->bind_device);
|
||||
config->peer_id = env_or_default("OMNI_VIDEO_PEER_ID", config->peer_id);
|
||||
config->target_peer = env_or_default("OMNI_VIDEO_TARGET_PEER", config->target_peer);
|
||||
if (getenv("OMNI_VIDEO_MAX_FRAMES") != NULL) {
|
||||
config->max_frames = atoi(getenv("OMNI_VIDEO_MAX_FRAMES"));
|
||||
}
|
||||
config->enable_timing_logs = env_flag_or_default("OMNI_VIDEO_DEBUG_TIMING", config->enable_timing_logs);
|
||||
config->stats_interval_ms = env_int_or_default("BLITZ_KCP_STATS_INTERVAL_MS", config->stats_interval_ms);
|
||||
}
|
||||
|
||||
int video_pipeline_stats_init(video_pipeline_stats_t *stats) {
|
||||
int rc;
|
||||
if (stats == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(stats, 0, sizeof(*stats));
|
||||
rc = pthread_mutex_init(&stats->mutex, NULL);
|
||||
if (rc != 0) {
|
||||
errno = rc;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void video_pipeline_stats_destroy(video_pipeline_stats_t *stats) {
|
||||
if (stats == NULL) {
|
||||
return;
|
||||
}
|
||||
pthread_mutex_destroy(&stats->mutex);
|
||||
}
|
||||
|
||||
void video_pipeline_stats_snapshot(video_pipeline_stats_t *stats, video_pipeline_stats_t *out_stats) {
|
||||
if (stats == NULL || out_stats == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(out_stats, 0, sizeof(*out_stats));
|
||||
pthread_mutex_lock(&stats->mutex);
|
||||
out_stats->frames_sent = stats->frames_sent;
|
||||
out_stats->bytes_sent = stats->bytes_sent;
|
||||
out_stats->send_errors = stats->send_errors;
|
||||
out_stats->last_frame_bytes = stats->last_frame_bytes;
|
||||
out_stats->connected = stats->connected;
|
||||
snprintf(out_stats->last_error, sizeof(out_stats->last_error), "%s", stats->last_error);
|
||||
out_stats->transport = stats->transport;
|
||||
pthread_mutex_unlock(&stats->mutex);
|
||||
}
|
||||
|
||||
static int open_v4l2_device(const char *device) {
|
||||
return open(device, O_RDWR | O_NONBLOCK);
|
||||
}
|
||||
|
||||
static int init_v4l2_device(int fd, int width, int height) {
|
||||
struct v4l2_format fmt;
|
||||
|
||||
memset(&fmt, 0, sizeof(fmt));
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = width;
|
||||
fmt.fmt.pix.height = height;
|
||||
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_NONE;
|
||||
return ioctl(fd, VIDIOC_S_FMT, &fmt);
|
||||
}
|
||||
|
||||
static int init_mmap(int fd, video_buffer_t **buffers, int *num_buffers) {
|
||||
struct v4l2_requestbuffers req;
|
||||
int i;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.count = VIDEO_NUM_BUFFERS;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*num_buffers = (int) req.count;
|
||||
*buffers = (video_buffer_t *) calloc(req.count, sizeof(video_buffer_t));
|
||||
if (*buffers == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < (int) req.count; i++) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = (unsigned int) i;
|
||||
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
(*buffers)[i].length = buf.length;
|
||||
(*buffers)[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
|
||||
if ((*buffers)[i].start == MAP_FAILED) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static AVCodecContext *create_mjpeg_decoder(int width, int height) {
|
||||
const AVCodec *decoder = avcodec_find_decoder(AV_CODEC_ID_MJPEG);
|
||||
AVCodecContext *ctx;
|
||||
AVDictionary *opts = NULL;
|
||||
|
||||
if (decoder == NULL) {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx = avcodec_alloc_context3(decoder);
|
||||
if (ctx == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ctx->width = width;
|
||||
ctx->height = height;
|
||||
ctx->pix_fmt = AV_PIX_FMT_YUVJ420P;
|
||||
ctx->color_range = AVCOL_RANGE_JPEG;
|
||||
ctx->thread_count = 1;
|
||||
|
||||
av_dict_set(&opts, "flags2", "+fast", 0);
|
||||
if (avcodec_open2(ctx, decoder, &opts) < 0) {
|
||||
avcodec_free_context(&ctx);
|
||||
av_dict_free(&opts);
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
av_dict_free(&opts);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static AVCodecContext *create_mjpeg_encoder(int width, int height) {
|
||||
const AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
|
||||
AVCodecContext *ctx;
|
||||
AVDictionary *opts = NULL;
|
||||
|
||||
if (encoder == NULL) {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx = avcodec_alloc_context3(encoder);
|
||||
if (ctx == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ctx->width = width;
|
||||
ctx->height = height;
|
||||
ctx->pix_fmt = AV_PIX_FMT_YUVJ420P;
|
||||
ctx->time_base = (AVRational){1, 30};
|
||||
ctx->qmin = 8;
|
||||
ctx->qmax = 31;
|
||||
ctx->flags |= AV_CODEC_FLAG_QSCALE;
|
||||
ctx->global_quality = FF_QP2LAMBDA * 5;
|
||||
|
||||
av_dict_set(&opts, "huffman", "default", 0);
|
||||
if (avcodec_open2(ctx, encoder, &opts) < 0) {
|
||||
avcodec_free_context(&ctx);
|
||||
av_dict_free(&opts);
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
av_dict_free(&opts);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static int decode_mjpeg_frame(AVCodecContext *decoder, const uint8_t *data, int size, AVFrame **frame) {
|
||||
AVPacket *pkt;
|
||||
int ret;
|
||||
|
||||
if (frame == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*frame = NULL;
|
||||
pkt = av_packet_alloc();
|
||||
if (pkt == NULL) {
|
||||
return -1;
|
||||
}
|
||||
pkt->data = (uint8_t *) data;
|
||||
pkt->size = size;
|
||||
|
||||
ret = avcodec_send_packet(decoder, pkt);
|
||||
if (ret < 0) {
|
||||
av_packet_free(&pkt);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*frame = av_frame_alloc();
|
||||
if (*frame == NULL) {
|
||||
av_packet_free(&pkt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = avcodec_receive_frame(decoder, *frame);
|
||||
av_packet_free(&pkt);
|
||||
if (ret < 0) {
|
||||
av_frame_free(frame);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ensure_scale_context(
|
||||
struct SwsContext **sws_ctx,
|
||||
int *cached_src_width,
|
||||
int *cached_src_height,
|
||||
int *cached_src_format,
|
||||
const AVFrame *src,
|
||||
int output_width,
|
||||
int output_height
|
||||
) {
|
||||
if (
|
||||
*sws_ctx != NULL
|
||||
&& *cached_src_width == src->width
|
||||
&& *cached_src_height == src->height
|
||||
&& *cached_src_format == src->format
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sws_freeContext(*sws_ctx);
|
||||
*sws_ctx = sws_getContext(
|
||||
src->width,
|
||||
src->height,
|
||||
src->format,
|
||||
output_width,
|
||||
output_height,
|
||||
AV_PIX_FMT_YUVJ420P,
|
||||
SWS_BILINEAR,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
if (*sws_ctx == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*cached_src_width = src->width;
|
||||
*cached_src_height = src->height;
|
||||
*cached_src_format = src->format;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scale_frame(AVFrame *src, AVFrame **dst, struct SwsContext *sws_ctx, int output_width, int output_height) {
|
||||
int ret;
|
||||
|
||||
if (sws_ctx == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*dst = av_frame_alloc();
|
||||
if (*dst == NULL) {
|
||||
return -1;
|
||||
}
|
||||
(*dst)->width = output_width;
|
||||
(*dst)->height = output_height;
|
||||
(*dst)->format = AV_PIX_FMT_YUVJ420P;
|
||||
if (av_frame_get_buffer(*dst, 0) < 0) {
|
||||
av_frame_free(dst);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = sws_scale(
|
||||
sws_ctx,
|
||||
(const uint8_t *const *) src->data,
|
||||
src->linesize,
|
||||
0,
|
||||
src->height,
|
||||
(*dst)->data,
|
||||
(*dst)->linesize
|
||||
);
|
||||
if (ret < 0) {
|
||||
av_frame_free(dst);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int video_sender_ensure_buffer_capacity(video_sender_t *sender, size_t min_capacity) {
|
||||
uint8_t *resized_buffer;
|
||||
size_t next_capacity;
|
||||
|
||||
if (sender == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (sender->send_buffer_cap >= min_capacity) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
next_capacity = sender->send_buffer_cap == 0 ? min_capacity : sender->send_buffer_cap;
|
||||
while (next_capacity < min_capacity) {
|
||||
next_capacity *= 2;
|
||||
}
|
||||
|
||||
resized_buffer = (uint8_t *) realloc(sender->send_buffer, next_capacity);
|
||||
if (resized_buffer == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sender->send_buffer = resized_buffer;
|
||||
sender->send_buffer_cap = next_capacity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int encode_frame(AVCodecContext *encoder, AVFrame *frame, AVPacket **pkt) {
|
||||
int ret;
|
||||
|
||||
if (pkt == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*pkt = av_packet_alloc();
|
||||
if (*pkt == NULL) {
|
||||
return -1;
|
||||
}
|
||||
ret = avcodec_send_frame(encoder, frame);
|
||||
if (ret < 0) {
|
||||
av_packet_free(pkt);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = avcodec_receive_packet(encoder, *pkt);
|
||||
if (ret < 0) {
|
||||
av_packet_free(pkt);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t get_realtime_ms(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
return (int64_t) ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
static int video_sender_init(video_sender_t *sender, const video_pipeline_config_t *config) {
|
||||
kcp_conn_options_t options;
|
||||
|
||||
if (sender == NULL || config == NULL || config->server_addr == NULL || config->server_addr[0] == '\0') {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(sender, 0, sizeof(*sender));
|
||||
snprintf(sender->target_peer, sizeof(sender->target_peer), "%s", config->target_peer);
|
||||
kcp_conn_options_set_video_defaults(&options);
|
||||
sender->client = kcp_client_dial_with_options(
|
||||
config->server_addr,
|
||||
config->relay_via,
|
||||
config->peer_id,
|
||||
config->bind_ip,
|
||||
config->bind_device,
|
||||
&options,
|
||||
NULL,
|
||||
NULL,
|
||||
config->stats_logger,
|
||||
config->stats_interval_ms
|
||||
);
|
||||
if (sender->client == NULL) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int video_sender_drain_pending_messages(video_sender_t *sender) {
|
||||
if (sender == NULL || sender->client == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
message_t msg;
|
||||
int rc;
|
||||
|
||||
protocol_message_init(&msg);
|
||||
rc = kcp_client_receive_timed(sender->client, &msg, 1);
|
||||
if (rc == 1) {
|
||||
protocol_message_clear(&msg);
|
||||
return 0;
|
||||
}
|
||||
if (rc != 0) {
|
||||
protocol_message_clear(&msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Drain unread server errors so an offline receiver cannot back up the reverse KCP stream.
|
||||
protocol_message_clear(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
static int video_sender_send_packet(video_sender_t *sender, const AVPacket *encoded_pkt, uint64_t timestamp) {
|
||||
uint8_t *payload;
|
||||
size_t payload_len;
|
||||
int rc;
|
||||
|
||||
if (sender == NULL || sender->client == NULL || encoded_pkt == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
payload_len = (size_t) encoded_pkt->size + sizeof(timestamp);
|
||||
if (video_sender_ensure_buffer_capacity(sender, payload_len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
payload = sender->send_buffer;
|
||||
|
||||
memcpy(payload, encoded_pkt->data, (size_t) encoded_pkt->size);
|
||||
memcpy(payload + encoded_pkt->size, ×tamp, sizeof(timestamp));
|
||||
rc = kcp_client_send_binary(sender->client, sender->target_peer, payload, payload_len);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
rc = video_sender_drain_pending_messages(sender);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void video_sender_close(video_sender_t *sender) {
|
||||
if (sender == NULL) {
|
||||
return;
|
||||
}
|
||||
if (sender->client != NULL) {
|
||||
kcp_client_close(sender->client);
|
||||
kcp_client_free(sender->client);
|
||||
sender->client = NULL;
|
||||
}
|
||||
free(sender->send_buffer);
|
||||
sender->send_buffer = NULL;
|
||||
sender->send_buffer_cap = 0;
|
||||
}
|
||||
|
||||
static void video_pipeline_cleanup_buffers(video_buffer_t *buffers, int num_buffers) {
|
||||
int i;
|
||||
if (buffers == NULL) {
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < num_buffers; i++) {
|
||||
if (buffers[i].start != NULL && buffers[i].start != MAP_FAILED) {
|
||||
munmap(buffers[i].start, buffers[i].length);
|
||||
}
|
||||
}
|
||||
free(buffers);
|
||||
}
|
||||
|
||||
int video_pipeline_run(const video_pipeline_config_t *config, video_pipeline_stats_t *stats, volatile sig_atomic_t *stop_requested) {
|
||||
video_pipeline_config_t defaults;
|
||||
video_sender_t sender;
|
||||
video_buffer_t *buffers = NULL;
|
||||
AVCodecContext *decoder = NULL;
|
||||
AVCodecContext *encoder = NULL;
|
||||
struct SwsContext *sws_ctx = NULL;
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
int num_buffers = 0;
|
||||
int fd = -1;
|
||||
int frame_index = 0;
|
||||
int rc = -1;
|
||||
int sws_src_width = 0;
|
||||
int sws_src_height = 0;
|
||||
int sws_src_format = -1;
|
||||
|
||||
memset(&sender, 0, sizeof(sender));
|
||||
if (stats == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
video_pipeline_config_init(&defaults);
|
||||
if (config == NULL) {
|
||||
config = &defaults;
|
||||
}
|
||||
|
||||
#ifdef QUIET_FFMPEG_LOGS
|
||||
av_log_set_level(AV_LOG_ERROR);
|
||||
#endif
|
||||
|
||||
if (config->server_addr == NULL || config->server_addr[0] == '\0') {
|
||||
errno = EINVAL;
|
||||
video_pipeline_set_error(stats, "video server address is required");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = open_v4l2_device(config->camera_device);
|
||||
if (fd < 0) {
|
||||
video_pipeline_set_errno_error(stats, "failed to open camera device");
|
||||
goto cleanup;
|
||||
}
|
||||
if (init_v4l2_device(fd, config->capture_width, config->capture_height) < 0) {
|
||||
video_pipeline_set_errno_error(stats, "failed to configure V4L2");
|
||||
goto cleanup;
|
||||
}
|
||||
if (init_mmap(fd, &buffers, &num_buffers) < 0) {
|
||||
video_pipeline_set_errno_error(stats, "failed to initialize V4L2 mmap");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
decoder = create_mjpeg_decoder(config->capture_width, config->capture_height);
|
||||
encoder = create_mjpeg_encoder(config->output_width, config->output_height);
|
||||
if (decoder == NULL || encoder == NULL) {
|
||||
video_pipeline_set_errno_error(stats, "failed to initialize codecs");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (video_sender_init(&sender, config) < 0) {
|
||||
video_pipeline_set_errno_error(stats, "failed to start video sender");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&stats->mutex);
|
||||
stats->connected = 1;
|
||||
stats->last_error[0] = '\0';
|
||||
pthread_mutex_unlock(&stats->mutex);
|
||||
|
||||
for (frame_index = 0; frame_index < num_buffers; frame_index++) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = (unsigned int) frame_index;
|
||||
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
video_pipeline_set_errno_error(stats, "failed to queue V4L2 buffer");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
|
||||
video_pipeline_set_errno_error(stats, "failed to start V4L2 streaming");
|
||||
goto cleanup;
|
||||
}
|
||||
if (config->enable_timing_logs) {
|
||||
fprintf(stderr, "\nRunning video pipeline timing benchmark...\n");
|
||||
video_pipeline_print_timing_header();
|
||||
}
|
||||
|
||||
frame_index = 0;
|
||||
while (!video_pipeline_stop_requested(stop_requested)) {
|
||||
fd_set fds;
|
||||
struct timeval timeout;
|
||||
struct v4l2_buffer buf;
|
||||
AVFrame *decoded_frame = NULL;
|
||||
AVFrame *scaled_frame = NULL;
|
||||
AVPacket *encoded_pkt = NULL;
|
||||
int select_rc;
|
||||
double total_start_ms = 0.0;
|
||||
double capture_start_ms = 0.0;
|
||||
double capture_end_ms = 0.0;
|
||||
double decode_start_ms = 0.0;
|
||||
double decode_end_ms = 0.0;
|
||||
double scale_start_ms = 0.0;
|
||||
double scale_end_ms = 0.0;
|
||||
double encode_start_ms = 0.0;
|
||||
double encode_end_ms = 0.0;
|
||||
double send_start_ms = 0.0;
|
||||
double send_end_ms = 0.0;
|
||||
int frame_number = frame_index + 1;
|
||||
|
||||
video_pipeline_report_progress(config);
|
||||
|
||||
if (config->max_frames > 0 && frame_index >= config->max_frames) {
|
||||
break;
|
||||
}
|
||||
if (config->enable_timing_logs) {
|
||||
total_start_ms = video_pipeline_now_ms();
|
||||
}
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
timeout.tv_sec = 2;
|
||||
timeout.tv_usec = 0;
|
||||
select_rc = select(fd + 1, &fds, NULL, NULL, &timeout);
|
||||
if (select_rc <= 0) {
|
||||
if (select_rc == 0) {
|
||||
errno = ETIMEDOUT;
|
||||
}
|
||||
video_pipeline_set_errno_error(stats, "failed waiting for camera frame");
|
||||
goto cleanup;
|
||||
}
|
||||
if (config->enable_timing_logs) {
|
||||
capture_start_ms = video_pipeline_now_ms();
|
||||
}
|
||||
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
|
||||
video_pipeline_set_errno_error(stats, "failed to dequeue V4L2 buffer");
|
||||
goto cleanup;
|
||||
}
|
||||
if (config->enable_timing_logs) {
|
||||
capture_end_ms = video_pipeline_now_ms();
|
||||
decode_start_ms = capture_end_ms;
|
||||
}
|
||||
|
||||
if (decode_mjpeg_frame(decoder, (const uint8_t *) buffers[buf.index].start, (int) buf.bytesused, &decoded_frame) != 0) {
|
||||
if (config->enable_timing_logs) {
|
||||
video_pipeline_print_timing_failure(frame_number, "decode");
|
||||
}
|
||||
(void) ioctl(fd, VIDIOC_QBUF, &buf);
|
||||
continue;
|
||||
}
|
||||
if (config->enable_timing_logs) {
|
||||
decode_end_ms = video_pipeline_now_ms();
|
||||
scale_start_ms = decode_end_ms;
|
||||
}
|
||||
if (
|
||||
ensure_scale_context(
|
||||
&sws_ctx,
|
||||
&sws_src_width,
|
||||
&sws_src_height,
|
||||
&sws_src_format,
|
||||
decoded_frame,
|
||||
config->output_width,
|
||||
config->output_height
|
||||
) != 0
|
||||
|| scale_frame(decoded_frame, &scaled_frame, sws_ctx, config->output_width, config->output_height) != 0
|
||||
) {
|
||||
if (config->enable_timing_logs) {
|
||||
video_pipeline_print_timing_failure(frame_number, "scale");
|
||||
}
|
||||
av_frame_free(&decoded_frame);
|
||||
(void) ioctl(fd, VIDIOC_QBUF, &buf);
|
||||
continue;
|
||||
}
|
||||
if (config->enable_timing_logs) {
|
||||
scale_end_ms = video_pipeline_now_ms();
|
||||
encode_start_ms = scale_end_ms;
|
||||
}
|
||||
if (encode_frame(encoder, scaled_frame, &encoded_pkt) != 0) {
|
||||
if (config->enable_timing_logs) {
|
||||
video_pipeline_print_timing_failure(frame_number, "encode");
|
||||
}
|
||||
av_frame_free(&decoded_frame);
|
||||
av_frame_free(&scaled_frame);
|
||||
(void) ioctl(fd, VIDIOC_QBUF, &buf);
|
||||
continue;
|
||||
}
|
||||
if (config->enable_timing_logs) {
|
||||
encode_end_ms = video_pipeline_now_ms();
|
||||
send_start_ms = encode_end_ms;
|
||||
}
|
||||
|
||||
if (video_sender_send_packet(&sender, encoded_pkt, (uint64_t) get_realtime_ms()) != 0) {
|
||||
pthread_mutex_lock(&stats->mutex);
|
||||
stats->send_errors += 1;
|
||||
pthread_mutex_unlock(&stats->mutex);
|
||||
if (config->enable_timing_logs) {
|
||||
video_pipeline_print_timing_failure(frame_number, "send");
|
||||
}
|
||||
video_pipeline_set_errno_error(stats, "failed to send video packet");
|
||||
av_frame_free(&decoded_frame);
|
||||
av_frame_free(&scaled_frame);
|
||||
av_packet_free(&encoded_pkt);
|
||||
(void) ioctl(fd, VIDIOC_QBUF, &buf);
|
||||
goto cleanup;
|
||||
}
|
||||
if (config->enable_timing_logs) {
|
||||
send_end_ms = video_pipeline_now_ms();
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&stats->mutex);
|
||||
stats->frames_sent += 1;
|
||||
stats->bytes_sent += (uint64_t) encoded_pkt->size;
|
||||
stats->last_frame_bytes = (uint64_t) encoded_pkt->size;
|
||||
kcp_client_runtime_stats_snapshot(sender.client, &stats->transport);
|
||||
pthread_mutex_unlock(&stats->mutex);
|
||||
if (config->enable_timing_logs) {
|
||||
video_pipeline_print_timing_row(
|
||||
frame_number,
|
||||
capture_end_ms - capture_start_ms,
|
||||
decode_end_ms - decode_start_ms,
|
||||
scale_end_ms - scale_start_ms,
|
||||
encode_end_ms - encode_start_ms,
|
||||
send_end_ms - send_start_ms,
|
||||
send_end_ms - total_start_ms,
|
||||
encoded_pkt
|
||||
);
|
||||
}
|
||||
|
||||
av_frame_free(&decoded_frame);
|
||||
av_frame_free(&scaled_frame);
|
||||
av_packet_free(&encoded_pkt);
|
||||
|
||||
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
video_pipeline_set_errno_error(stats, "failed to requeue V4L2 buffer");
|
||||
goto cleanup;
|
||||
}
|
||||
frame_index += 1;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
pthread_mutex_lock(&stats->mutex);
|
||||
stats->connected = 0;
|
||||
pthread_mutex_unlock(&stats->mutex);
|
||||
if (fd >= 0) {
|
||||
(void) ioctl(fd, VIDIOC_STREAMOFF, &type);
|
||||
}
|
||||
video_sender_close(&sender);
|
||||
if (encoder != NULL) {
|
||||
avcodec_free_context(&encoder);
|
||||
}
|
||||
if (decoder != NULL) {
|
||||
avcodec_free_context(&decoder);
|
||||
}
|
||||
sws_freeContext(sws_ctx);
|
||||
video_pipeline_cleanup_buffers(buffers, num_buffers);
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
Reference in New Issue
Block a user