|
|
|
|
@ -9,6 +9,7 @@
|
|
|
|
|
#include "../src/stream/stream.h" |
|
|
|
|
#include "../src/stream/stream_member.h" |
|
|
|
|
#include "../src/stream/stream_priv.h" |
|
|
|
|
#include "../src/radio/radio.h" |
|
|
|
|
#include "../src/chat/chat_core.h" |
|
|
|
|
#include "../src/chat/merkle_tree.h" |
|
|
|
|
#include "../src/routing_layer/topo_node_sqlite.h" |
|
|
|
|
@ -1011,7 +1012,7 @@ static void test_profile_atomicity(void) {
|
|
|
|
|
uint64_t before_version, after_version; |
|
|
|
|
profile(0, GROUP1, before, before_signature, &before_version); root_hash(0, GROUP1, before_hash); |
|
|
|
|
memset(huge, 'p', MS_USERINFO_MAX); huge[MS_USERINFO_MAX] = 0; |
|
|
|
|
struct chat_profile_option options[] = {{"stream_source", "0000000000000001:8:2:1189"}, {"stream_subscriptions", huge}}; |
|
|
|
|
struct chat_profile_option options[] = {{"stream_source", "0000000000000001:8:2:1189:0"}, {"stream_subscriptions", huge}}; |
|
|
|
|
CHECK(chat_core_set_my_profile_options(nodes[0], "710001", options, 2) < 0); |
|
|
|
|
profile(0, GROUP1, after, after_signature, &after_version); root_hash(0, GROUP1, after_hash); |
|
|
|
|
CHECK(!strcmp(before, after) && before_version == after_version && !memcmp(before_signature, after_signature, 64)); |
|
|
|
|
@ -1385,21 +1386,22 @@ static void test_supernode_relay(void) {
|
|
|
|
|
/* Канонический список исключает неоднозначные IDs, дубли и обрезание значений. */ |
|
|
|
|
static void test_member_format(void) { |
|
|
|
|
struct stream_member_state state = {.source = {.group_id = GROUP1, .source_node_id = 1, .source_epoch = 2, |
|
|
|
|
.n = 8, .r = 2, .block_size = STREAM_RS_DEFAULT_SIZE}, .published = 1, .count = 2, |
|
|
|
|
.n = 8, .r = 2, .block_size = STREAM_RS_DEFAULT_SIZE}, .published = 1, .audio_enabled = 1, .count = 2, |
|
|
|
|
.subscriptions = {{1, 2}, {UINT64_MAX, UINT64_MAX, 1}}}; |
|
|
|
|
char source[MS_USERINFO_MAX + 1], subscriptions[MS_USERINFO_MAX + 1], json[2 * MS_USERINFO_MAX + 64]; |
|
|
|
|
CHECK(stream_member_format(&state, source, subscriptions) == STREAM_OK); |
|
|
|
|
snprintf(json, sizeof(json), "{\"stream_source\":\"%s\",\"stream_subscriptions\":\"%s\"}", source, subscriptions); |
|
|
|
|
struct stream_member_state parsed; |
|
|
|
|
CHECK(stream_member_parse(GROUP1, 1, json, &parsed) == STREAM_OK); |
|
|
|
|
CHECK(parsed.published && parsed.source.source_epoch == 2 && parsed.count == 2); |
|
|
|
|
CHECK(parsed.published && parsed.audio_enabled && parsed.source.source_epoch == 2 && parsed.count == 2); |
|
|
|
|
CHECK(parsed.subscriptions[1].source_node_id == UINT64_MAX && parsed.subscriptions[1].source_epoch == UINT64_MAX); |
|
|
|
|
CHECK(!parsed.subscriptions[0].relay_only && parsed.subscriptions[1].relay_only); |
|
|
|
|
const char *bad[] = { |
|
|
|
|
"{\"stream_subscriptions\":\"1:2\"}", |
|
|
|
|
"{\"stream_subscriptions\":\"0000000000000001:0000000000000002,\"}", |
|
|
|
|
"{\"stream_subscriptions\":\"0000000000000001:0000000000000002,0000000000000001:0000000000000002\"}", |
|
|
|
|
"{\"stream_source\":\"0000000000000002:256:2:1189\"}", |
|
|
|
|
"{\"stream_source\":\"0000000000000002:256:2:1189:1\"}", |
|
|
|
|
"{\"stream_source\":\"0000000000000002:8:2:1189:2\",\"stream_subscriptions\":\"0000000000000001:0000000000000002\"}", |
|
|
|
|
"{\"stream_source\":\"0000000000000002:8:2:1189\"}", |
|
|
|
|
"{\"stream_source\":\"\",\"stream_source\":\"\"}" |
|
|
|
|
}; |
|
|
|
|
@ -1408,6 +1410,62 @@ static void test_member_format(void) {
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] member format: full 64-bit IDs, canonical lists, duplicate/overflow rejection"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void wait_source_audio(uint64_t epoch, int enabled) { |
|
|
|
|
uint64_t deadline = get_time_tb() + 100000; |
|
|
|
|
for (;;) { |
|
|
|
|
struct stream_member_state state; |
|
|
|
|
if (stream_member_load(nodes[1], GROUP1, nodes[0]->node_id, &state, NULL) == STREAM_OK |
|
|
|
|
&& state.published && state.source.source_epoch == epoch && state.audio_enabled == enabled) return; |
|
|
|
|
CHECK(get_time_tb() < deadline); test_clock_poll(ua, 100); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void test_viewer_radio_mute(void) { |
|
|
|
|
struct STREAM_HANDLE *publisher = NULL, *raw = NULL, *relay = NULL, *first = NULL, *second = NULL; |
|
|
|
|
struct stream_info info; |
|
|
|
|
uint64_t source = nodes[0]->node_id; |
|
|
|
|
CHECK(stream_publish_open(nodes[0], GROUP1, NULL, &publisher) == STREAM_OK); |
|
|
|
|
CHECK(stream_publish_set_audio_enabled(publisher, 1) == STREAM_OK); |
|
|
|
|
CHECK(stream_get_info(publisher, &info) == STREAM_OK); |
|
|
|
|
uint64_t epoch = info.descriptor.source_epoch; |
|
|
|
|
wait_source_audio(epoch, 1); |
|
|
|
|
CHECK(!radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
CHECK(stream_relay_open(nodes[1], &info.descriptor, &relay) == STREAM_OK); |
|
|
|
|
CHECK(stream_subscribe_open(nodes[1], GROUP1, source, &raw) == STREAM_OK); |
|
|
|
|
CHECK(!radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
CHECK(stream_media_open(nodes[1], GROUP1, source, epoch, STREAM_MEDIA_TRACK_VIDEO, &first) == STREAM_OK); |
|
|
|
|
CHECK(stream_media_open(nodes[1], GROUP1, source, epoch, STREAM_MEDIA_TRACK_VIDEO | STREAM_MEDIA_TRACK_AUDIO, &second) == STREAM_OK); |
|
|
|
|
CHECK(radio_member_silenced(nodes[1], GROUP1, source) && !radio_member_muted(nodes[1], GROUP1, source)); |
|
|
|
|
CHECK(!radio_member_silenced(nodes[1], GROUP2, source)); |
|
|
|
|
CHECK(!radio_member_silenced(nodes[1], GROUP1, nodes[2]->node_id)); |
|
|
|
|
stream_close(first); first = NULL; |
|
|
|
|
CHECK(radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
CHECK(radio_set_member_muted(nodes[1], GROUP1, source, 1) == 0); |
|
|
|
|
CHECK(stream_publish_set_audio_enabled(publisher, 0) == STREAM_OK); wait_source_audio(epoch, 0); |
|
|
|
|
CHECK(!stream_radio_muted(nodes[1], GROUP1, source) && radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
CHECK(radio_set_member_muted(nodes[1], GROUP1, source, 0) == 0); |
|
|
|
|
CHECK(!radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
CHECK(stream_publish_set_audio_enabled(publisher, 1) == STREAM_OK); wait_source_audio(epoch, 1); |
|
|
|
|
CHECK(radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
stream_close(second); second = NULL; |
|
|
|
|
CHECK(!radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
CHECK(stream_media_open(nodes[1], GROUP1, source, epoch, STREAM_MEDIA_TRACK_VIDEO, &first) == STREAM_OK); |
|
|
|
|
CHECK(radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
stream_close(publisher); publisher = NULL; |
|
|
|
|
CHECK(stream_publish_open(nodes[0], GROUP1, NULL, &publisher) == STREAM_OK); |
|
|
|
|
CHECK(stream_publish_set_audio_enabled(publisher, 1) == STREAM_OK); |
|
|
|
|
CHECK(stream_get_info(publisher, &info) == STREAM_OK && info.descriptor.source_epoch != epoch); |
|
|
|
|
wait_source_audio(info.descriptor.source_epoch, 1); |
|
|
|
|
CHECK(!stream_handle_active(first) && !radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
stream_close(first); stream_close(raw); stream_close(relay); first = NULL; |
|
|
|
|
CHECK(stream_media_open(nodes[1], GROUP1, source, info.descriptor.source_epoch, STREAM_MEDIA_TRACK_VIDEO, &first) == STREAM_OK); |
|
|
|
|
CHECK(radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
stream_cancel_group(nodes[1], GROUP1); |
|
|
|
|
CHECK(!radio_member_silenced(nodes[1], GROUP1, source)); |
|
|
|
|
stream_close(first); stream_close(publisher); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] viewer radio mute: source audio, manual mute, group isolation, owners, relay, epoch, close"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Пауза player не останавливает live ingest/selector; FIN сохраняет источник до дочитывания локального owner. */ |
|
|
|
|
static void test_delayed_media_reader(void) { |
|
|
|
|
struct STREAM_HANDLE *publisher = NULL, *remote = NULL, *preview = NULL; |
|
|
|
|
@ -1766,6 +1824,7 @@ int main(int argc, char **argv) {
|
|
|
|
|
for (unsigned i = 0; i < 4; i++) stream_set_event_callback(nodes[i], NULL, NULL); |
|
|
|
|
test_partial_bootstrap_and_end(); |
|
|
|
|
test_delayed_media_reader(); |
|
|
|
|
test_viewer_radio_mute(); |
|
|
|
|
for (unsigned i = 0; i < 3; i++) topo_group_peer_close(connections[i]); |
|
|
|
|
for (unsigned i = 0; i < 4; i++) utun_instance_destroy(nodes[i]); |
|
|
|
|
uasync_destroy(ua, 1); test_clock_finish(); |
|
|
|
|
|