Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions TargetBridge-Receiver/TBReceiverC/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ ifeq ($(UNAME_S),Darwin)
LDFLAGS += -framework ApplicationServices -framework AppKit -framework CoreFoundation -framework CoreGraphics -framework CoreText -framework VideoToolbox -framework CoreMedia -framework CoreVideo -framework IOSurface -framework CoreServices -framework CoreAudio -framework SystemConfiguration
endif

C_SRC = src/main.c src/net.c src/decoder.c src/display.c src/input_queue.c src/receiver_profile.c src/tb_i18n.c
C_SRC = src/main.c src/net.c src/decoder.c src/display.c src/input_queue.c src/idle_watchdog.c src/receiver_profile.c src/tb_i18n.c
OBJC_SRC = src/tb_gesture_bridge.m src/tb_display_tweaks.m
OBJ = $(C_SRC:.c=.o) $(OBJC_SRC:.m=.o)
BIN = tbreceiver
Expand All @@ -51,7 +51,7 @@ $(BIN): $(OBJ)
$(CC) $(CFLAGS) -fobjc-arc -c -o $@ $<

clean:
rm -f $(OBJ) $(BIN) $(TEST_BIN) $(TEST_INPUT_QUEUE_BIN) $(TEST_RECEIVER_PROFILE_BIN)
rm -f $(OBJ) $(BIN) $(TEST_BIN) $(TEST_INPUT_QUEUE_BIN) $(TEST_IDLE_WATCHDOG_BIN) $(TEST_RECEIVER_PROFILE_BIN)

# Unit tests for the packet parser. net.c is pure POSIX, so this needs no
# ffmpeg/SDL/pkgconf — it runs anywhere, including CI, with no hardware.
Expand All @@ -62,6 +62,7 @@ TEST_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration
endif
TEST_BIN = test_net_parser
TEST_INPUT_QUEUE_BIN = test_input_queue
TEST_IDLE_WATCHDOG_BIN = test_idle_watchdog
TEST_RECEIVER_PROFILE_BIN = test_receiver_profile

test_net_parser: tests/test_net_parser.c src/net.c src/net.h src/proto.h
Expand All @@ -70,12 +71,16 @@ test_net_parser: tests/test_net_parser.c src/net.c src/net.h src/proto.h
$(TEST_INPUT_QUEUE_BIN): tests/test_input_queue.c src/input_queue.c src/input_queue.h
$(CC) $(TEST_CFLAGS) -Isrc tests/test_input_queue.c src/input_queue.c -o $@

$(TEST_IDLE_WATCHDOG_BIN): tests/test_idle_watchdog.c src/idle_watchdog.c src/idle_watchdog.h
$(CC) $(TEST_CFLAGS) -Isrc tests/test_idle_watchdog.c src/idle_watchdog.c -o $@

$(TEST_RECEIVER_PROFILE_BIN): tests/test_receiver_profile.c src/receiver_profile.c src/receiver_profile.h
$(CC) $(TEST_CFLAGS) -Isrc tests/test_receiver_profile.c src/receiver_profile.c -o $@

test: $(TEST_BIN) $(TEST_INPUT_QUEUE_BIN) $(TEST_RECEIVER_PROFILE_BIN)
test: $(TEST_BIN) $(TEST_INPUT_QUEUE_BIN) $(TEST_IDLE_WATCHDOG_BIN) $(TEST_RECEIVER_PROFILE_BIN)
./$(TEST_BIN)
./$(TEST_INPUT_QUEUE_BIN)
./$(TEST_IDLE_WATCHDOG_BIN)
./$(TEST_RECEIVER_PROFILE_BIN)

.PHONY: all clean test
19 changes: 19 additions & 0 deletions TargetBridge-Receiver/TBReceiverC/src/idle_watchdog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "idle_watchdog.h"

enum tb_idle_watchdog_result tb_idle_watchdog_check(uint64_t now_ms,
uint64_t *last_activity_ms,
uint64_t timeout_ms,
uint64_t *elapsed_ms) {
if (!last_activity_ms || !elapsed_ms) return TB_IDLE_WATCHDOG_OK;

if (now_ms < *last_activity_ms) {
*last_activity_ms = now_ms;
*elapsed_ms = 0;
return TB_IDLE_WATCHDOG_CLOCK_REWOUND;
}

*elapsed_ms = now_ms - *last_activity_ms;
return *elapsed_ms >= timeout_ms
? TB_IDLE_WATCHDOG_EXPIRED
: TB_IDLE_WATCHDOG_OK;
}
20 changes: 20 additions & 0 deletions TargetBridge-Receiver/TBReceiverC/src/idle_watchdog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef TB_IDLE_WATCHDOG_H
#define TB_IDLE_WATCHDOG_H

#include <stdint.h>

enum tb_idle_watchdog_result {
TB_IDLE_WATCHDOG_OK = 0,
TB_IDLE_WATCHDOG_EXPIRED,
TB_IDLE_WATCHDOG_CLOCK_REWOUND
};

/* Compare timestamps without allowing an unsigned subtraction to turn a
* small backward jump into an immediate timeout. A backward jump rebases the
* last-activity timestamp so a genuinely stale connection can still expire. */
enum tb_idle_watchdog_result tb_idle_watchdog_check(uint64_t now_ms,
uint64_t *last_activity_ms,
uint64_t timeout_ms,
uint64_t *elapsed_ms);

#endif
37 changes: 26 additions & 11 deletions TargetBridge-Receiver/TBReceiverC/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "net.h"
#include "decoder.h"
#include "display.h"
#include "idle_watchdog.h"
#include "receiver_profile.h"
#include "proto.h"
#include "tb_gesture_bridge.h"
Expand Down Expand Up @@ -2054,19 +2055,33 @@ int main(int argc, char **argv) {
close_client(&a);
} else {
socket_activity = drain_result;
if (drain_result > 0) a.last_recv_ms = t;
if (drain_result > 0) a.last_recv_ms = now_ms();
if (a.close_requested) {
close_client(&a);
} else if (t - a.last_recv_ms >= TB_SENDER_IDLE_TIMEOUT_MS) {
/* The sender streams frames continuously and heartbeats
* every 2s. Total silence means it died without a FIN
* (crash, pulled cable, force sleep). Without this reap,
* the dead fd is held forever and — because the receiver
* is single-client — every future connect is locked out
* until the app is restarted. */
fprintf(stderr, "[main] no data from sender for %llu ms; closing stale session\n",
(unsigned long long)(t - a.last_recv_ms));
close_client(&a);
} else {
const uint64_t watchdog_now_ms = now_ms();
uint64_t idle_ms = 0;
enum tb_idle_watchdog_result watchdog_result =
tb_idle_watchdog_check(watchdog_now_ms,
&a.last_recv_ms,
TB_SENDER_IDLE_TIMEOUT_MS,
&idle_ms);
if (watchdog_result == TB_IDLE_WATCHDOG_CLOCK_REWOUND) {
fprintf(stderr,
"[main] monotonic clock moved backwards; "
"rebasing sender idle watchdog\n");
} else if (watchdog_result == TB_IDLE_WATCHDOG_EXPIRED) {
/* The sender streams frames continuously and heartbeats
* every 2s. Total silence means it died without a FIN
* (crash, pulled cable, force sleep). Without this reap,
* the dead fd is held forever and — because the receiver
* is single-client — every future connect is locked out
* until the app is restarted. */
fprintf(stderr,
"[main] no data from sender for %llu ms; closing stale session\n",
(unsigned long long)idle_ms);
close_client(&a);
}
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions TargetBridge-Receiver/TBReceiverC/tests/test_idle_watchdog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "idle_watchdog.h"

#include <stdint.h>
#include <stdio.h>

static int checks;
static int failures;

#define CHECK(condition) do { \
checks++; \
if (!(condition)) { \
failures++; \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \
} \
} while (0)

int main(void) {
uint64_t last = 1000;
uint64_t elapsed = UINT64_MAX;

CHECK(tb_idle_watchdog_check(10999, &last, 10000, &elapsed) == TB_IDLE_WATCHDOG_OK);
CHECK(elapsed == 9999);
CHECK(last == 1000);

CHECK(tb_idle_watchdog_check(11000, &last, 10000, &elapsed) == TB_IDLE_WATCHDOG_EXPIRED);
CHECK(elapsed == 10000);

/* Regression for issue #156: 52 ms backwards must not become
* UINT64_MAX - 51 and close an active session. */
last = 5000;
elapsed = UINT64_MAX;
CHECK(tb_idle_watchdog_check(4948, &last, 10000, &elapsed) ==
TB_IDLE_WATCHDOG_CLOCK_REWOUND);
CHECK(elapsed == 0);
CHECK(last == 4948);

CHECK(tb_idle_watchdog_check(14947, &last, 10000, &elapsed) == TB_IDLE_WATCHDOG_OK);
CHECK(elapsed == 9999);
CHECK(tb_idle_watchdog_check(14948, &last, 10000, &elapsed) == TB_IDLE_WATCHDOG_EXPIRED);
CHECK(elapsed == 10000);

printf("idle watchdog tests: %d checks, %d failures\n", checks, failures);
return failures == 0 ? 0 : 1;
}
Loading