Browse Source

Cancel local streams when signed membership is invalidated

master
evgeny 2 days ago
parent
commit
a62540800b
  1. 1
      src/chat/member_sync.c
  2. 10
      src/stream/stream.c
  3. 16
      tests/test_stream_service.c
  4. 55
      tools/chatgui/package_win.sh

1
src/chat/member_sync.c

@ -888,6 +888,7 @@ int member_sync_verify_and_purge(struct UTUN_INSTANCE* inst, const char* ch_id)
topo_node_sqlite_member_del(db, ch_id, nid);
radio_forget_member_mute(inst, strtoull(ch_id, NULL, 10), nid);
merkle_sync_recompute_path(inst, MT_DATA_MEMBERS, ch_id, nid);
_fire_props_changed(inst, nid, NULL, ch_id);
/* пассивное удаление: если узел подключён напрямую — убрать из CHAT-группы */
if (inst->topo_groups) {
uint64_t gid = strtoull(ch_id, NULL, 10);

10
src/stream/stream.c

@ -224,14 +224,20 @@ static void member_changed(uint64_t node_id, const char *tags, const char *chann
struct stream_service *service = arg;
if (service->stopping || !service->sessions) return;
for (struct stream_session *s = service->sessions; s; s = s->next) {
if (s->closing || s->source || s->descriptor.source_node_id != node_id) continue;
int own = node_id == service->inst->node_id;
if (s->closing || (!own && (s->source || s->descriptor.source_node_id != node_id))) continue;
struct TOPO_GROUP *group = stream_group(service->inst, s->descriptor.group_id);
if (!group || !channel || strcmp(group->channel_id, channel)) continue;
struct stream_member_state state; uint8_t key[32];
int result = stream_member_load(service->inst, group->group_id, node_id, &state, key);
if (result == STREAM_ERR_NOT_MEMBER || result == STREAM_ERR_SIGNATURE || result == STREAM_ERR_FORMAT) {
s->invalidated = 1; s->reason = STREAM_CLOSE_MEMBER;
} else if (result == STREAM_OK && (memcmp(key, s->public_key, 32)
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "stream membership invalidated group=%llx member=%llx own=%d result=%d",
(unsigned long long)group->group_id, (unsigned long long)node_id, own, result);
} else if (own && result == STREAM_OK && memcmp(key, service->inst->my_ed25519_pubkey, 32)) {
s->invalidated = 1; s->reason = STREAM_CLOSE_MEMBER;
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "stream own member key changed group=%llx", (unsigned long long)group->group_id);
} else if (!own && result == STREAM_OK && (memcmp(key, s->public_key, 32)
|| (state.published && (state.source.source_epoch != s->descriptor.source_epoch
|| state.source.n != s->descriptor.n || state.source.r != s->descriptor.r
|| state.source.block_size != s->descriptor.block_size)))) {

16
tests/test_stream_service.c

@ -40,7 +40,7 @@ static void on_stream(struct UTUN_INSTANCE *inst, const struct stream_event *eve
(void)arg;
unsigned node = 0;
while (node < 4 && nodes[node] != inst) node++;
CHECK(node < 4 && event->descriptor.group_id == GROUP1);
CHECK(node < 4 && (event->descriptor.group_id == GROUP1 || event->type == STREAM_EVENT_CLOSED));
if (event->type == STREAM_EVENT_DATA) {
CHECK(event->length == sizeof(expected) && !memcmp(event->data, expected, sizeof(expected)));
data_events[node]++;
@ -289,10 +289,22 @@ int main(void) {
CHECK(!stream_handle_active(mobile) && !stream_handle_active(desktop));
CHECK(stream_handle_reason(mobile) == STREAM_CLOSE_SOURCE_CHANGED && stream_handle_reason(desktop) == STREAM_CLOSE_SOURCE_CHANGED);
stream_close(mobile); stream_close(desktop);
/* Повреждённое собственное членство удаляет обычная проверка Merkle; буфер не должен остаться активным. */
sqlite3_stmt *broken = NULL; uint8_t bad_signature[64]; memset(bad_signature, 1, sizeof(bad_signature));
CHECK(sqlite3_prepare_v2(nodes[1]->topo_sqlite_db, "UPDATE peers_710002 SET update_sig=? WHERE node_id=?",
-1, &broken, NULL) == SQLITE_OK);
sqlite3_bind_blob(broken, 1, bad_signature, sizeof(bad_signature), SQLITE_STATIC);
sqlite3_bind_int64(broken, 2, (sqlite3_int64)nodes[1]->node_id);
CHECK(sqlite3_step(broken) == SQLITE_DONE); sqlite3_finalize(broken);
CHECK(member_sync_verify_and_purge(nodes[1], "710002") == 1);
CHECK(!stream_handle_active(other_group) && stream_handle_reason(other_group) == STREAM_CLOSE_MEMBER);
run_until(&closed_events[1], 2); stream_close(other_group); other_group = NULL;
put_member(1, 1, GROUP2);
CHECK(stream_subscribe_open(nodes[1], GROUP2, nodes[0]->node_id, &other_group) == STREAM_OK);
chat_core_delete_channel(nodes[1], "710002");
CHECK(!stream_handle_active(other_group) && stream_handle_reason(other_group) == STREAM_CLOSE_GROUP);
stream_close(other_group);
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] source epoch invalidation and channel deletion detach local handles");
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] source epoch, lost own membership and channel deletion detach local handles");
char stale[MS_USERINFO_MAX + 1]; uint8_t stale_signature[64]; uint64_t stale_version;
profile(0, GROUP1, stale, stale_signature, &stale_version);

55
tools/chatgui/package_win.sh

@ -53,20 +53,7 @@ done
STAGE=$(mktemp -d "$BUILD_DIR/installer-stage.XXXXXX")
PACKAGE_DIR="$STAGE/package"
mkdir -p "$PACKAGE_DIR/licenses" "$STAGE/output" "$BUILD_DIR/installer"
cp "$BUILD_DIR/vibechat.exe" "$PACKAGE_DIR/"
shopt -s nullglob nocasematch
for file in "$BUILD_DIR/"*.dll; do
name=${file##*/}
if [[ "$name" = Qt[56]*.dll && "$name" != "$QT_PACKAGE"*.dll ]]; then
echo "INFO: excluding DLL from other Qt version: $name"
continue
fi
cp "$file" "$PACKAGE_DIR/"
done
for file in platforms/qwindows.dll imageformats/qjpeg.dll imageformats/qwebp.dll; do
mkdir -p "$PACKAGE_DIR/${file%/*}"
cp "$BUILD_DIR/$file" "$PACKAGE_DIR/$file"
done
for file in LICENSE LICENSING THIRD_PARTY_NOTICES; do
cp "$SRC_DIR/../../$file" "$PACKAGE_DIR/licenses/"
done
@ -76,16 +63,31 @@ cp -R "$SRC_DIR/rlottie_src/licenses" "$PACKAGE_DIR/licenses/rlottie/"
# Проверяем архитектуру/полноту уже собранного комплекта, включая динамические Qt-плагины.
SYSTEM_DIR=$(cygpath -u "${WINDIR:-C:\\Windows}/System32")
source "$SRC_DIR/compiler_runtime.sh"
declare -A LICENSE_PACKAGES=()
for file in "$PACKAGE_DIR/vibechat.exe" "$PACKAGE_DIR/"*.dll "$PACKAGE_DIR/platforms/"*.dll "$PACKAGE_DIR/imageformats/"*.dll; do
declare -A INCLUDED=()
FILES=(vibechat.exe platforms/qwindows.dll imageformats/qjpeg.dll imageformats/qwebp.dll)
HAS_FFMPEG=0
for ((i = 0; i < ${#FILES[@]}; i++)); do
relative=${FILES[i]}
[[ -z "${INCLUDED[${relative,,}]:-}" ]] || continue
INCLUDED[${relative,,}]=1
name=${relative##*/}
if [[ "$name" = Qt[56]*.dll && "$name" != "$QT_PACKAGE"*.dll ]]; then
echo "ERROR: mixed Qt runtime: $relative does not belong to $QT_PACKAGE" >&2
exit 1
fi
file="$PACKAGE_DIR/$relative"
mkdir -p "${file%/*}"
cp "$BUILD_DIR/$relative" "$file"
name=${file##*/}
objdump -f "$file" > "$STAGE/headers.txt"
rg_output=$(awk '/file format/ {print $NF}' "$STAGE/headers.txt")
[[ "$rg_output" = pei-x86-64 ]] || { echo "ERROR: $name is not a Windows x64 binary: $rg_output" >&2; exit 1; }
binary_format=$(awk '/file format/ {print $NF}' "$STAGE/headers.txt")
[[ "$binary_format" = pei-x86-64 ]] || { echo "ERROR: $name is not a Windows x64 binary: $binary_format" >&2; exit 1; }
objdump -p "$file" > "$STAGE/imports.txt"
while read -r dep; do
[[ -n "$dep" ]] || continue
[[ -f "$PACKAGE_DIR/$dep" ]] && continue
if [[ -f "$BUILD_DIR/$dep" ]]; then FILES+=("$dep"); continue; fi
case "$dep" in api-ms-win-*.dll|ext-ms-win-*.dll) continue ;; esac
if [[ -f "$SYSTEM_DIR/$dep" && "$dep" != msvcp*.dll && "$dep" != vcruntime*.dll ]]; then continue; fi
echo "ERROR: incomplete installer runtime: $name requires $dep" >&2
@ -95,7 +97,10 @@ for file in "$PACKAGE_DIR/vibechat.exe" "$PACKAGE_DIR/"*.dll "$PACKAGE_DIR/platf
# Лицензии внешних библиотек берём из пакетов той же UCRT64-сборки.
[[ "$name" != vibechat.exe && "$name" != librlottie.dll ]] || continue
[[ ! -f "$SRC_DIR/ffmpeg/ffmpeg_build/install/bin/$name" ]] || continue
if [[ -f "$SRC_DIR/ffmpeg/ffmpeg_build/install/bin/$name" ]] && ! chatgui_is_compiler_runtime_dll "$name"; then
HAS_FFMPEG=1
continue
fi
runtime_file="$RUNTIME_PREFIX/bin/$name"
if [[ "$file" = "$PACKAGE_DIR/platforms/"* || "$file" = "$PACKAGE_DIR/imageformats/"* ]]; then
relative=${file#"$PACKAGE_DIR/"}
@ -117,11 +122,15 @@ for pkg in "${!LICENSE_PACKAGES[@]}"; do
done < "$STAGE/package-files.txt"
echo "INFO: included runtime license notices: $pkg"
done
if [[ -d "$SRC_DIR/ffmpeg/ffmpeg_build" ]]; then
# Архив исходников FFmpeg остаётся распакованным после build_ffmpeg.sh.
for file in "$SRC_DIR/ffmpeg/ffmpeg_build/ffmpeg-"*/COPYING* "$SRC_DIR/ffmpeg/ffmpeg_build/ffmpeg-"*/LICENSE.md; do
mkdir -p "$PACKAGE_DIR/licenses/ffmpeg"
cp "$file" "$PACKAGE_DIR/licenses/ffmpeg/"
if [[ "$HAS_FFMPEG" = 1 ]]; then
FFMPEG_SOURCE="$SRC_DIR/ffmpeg/ffmpeg_build/src"
[[ -f "$FFMPEG_SOURCE/ffmpeg/COPYING.GPLv3" ]] || { echo "ERROR: FFmpeg source license notices missing: $FFMPEG_SOURCE" >&2; exit 1; }
# Сохраняем уведомления FFmpeg и его встроенных статических кодеков.
for file in "$FFMPEG_SOURCE/"*/COPYING* "$FFMPEG_SOURCE/"*/LICENSE* "$FFMPEG_SOURCE/"*/PATENTS; do
[[ -f "$file" ]] || continue
relative=${file#"$FFMPEG_SOURCE/"}
mkdir -p "$PACKAGE_DIR/licenses/ffmpeg/${relative%/*}"
cp "$file" "$PACKAGE_DIR/licenses/ffmpeg/$relative"
done
fi

Loading…
Cancel
Save